30
loading...
This website collects cookies to deliver better user experience
useEffect
has become a challenge for many. This hook offers an elegant way to declare side effects in our code, but it comes with a price: we need to define its inner dependencies and this can be an issue sometimes.function Parent(){
return <Child onChange={doSomething} />
}
function Child({ onChange }){
const [state, setState] = useState()
useEffect(()=> {
onChange(state)
}, [state, onChange])
return ...
}
react-table
. I needed a component that could encapsulate React Table’s API in order to replace other tables with my new component, but React Table holds all the table’s state in a custom hook. If we want to provide a handler for the selected rows in the table, the best way to do this is with a useEffect
.// our new Table component with react-table
function Table({ onChangeSelection }) {
const [value] = useTable(config)
const { selected } = value.state
useEffect(() => {
onChangeSelection(selected)
}, [selected, onChangeSelection])
// ...
}
// a component that needs the selection
function Page() {
const [selection, setSelection] = useState({})
// this will cause an infinite loop:
// a re-render in Page creates a new handleSelection
// a new handleSelection triggers Table's useEffect
// Page will re-render if the new value is a new object instance
const handleSelection = (value) => setSelection(value)
return (
<div>
<OtherComponent selection={selection} />
<Table onChangeSelection={handleSelection} />
</div>
)
}
Table
component provides a handler to keep track of changes in the selected state, while Page
uses that handler to keep the state updated. A useEffect
will tell when the Table
’s state changes and call the handler. But to do this properly, the dependencies array has to include the state that we’re subscribing to and the handler. useCallback
. This way we keep the same handler between renders. Since the handler remains the same, the effect won’t be triggered and the handler will only be called when the selection state changes.function Table({ onChangeSelection }) {
const [value] = useTable(config)
const { selected } = value.state
useEffect(()=> {
onChangeSelection(selected)
}, [selected, onChangeSelection])
// ...
}
function Page() {
const [selection, setSelection] = useState({})
// useCallback keeps the same instance of handleSelection between renders
// useEffect will only be triggered when the selection changes
const handleSelection = useCallback((value) => setSelection(value), [])
return (
<div>
<OtherComponent selection={selection} />
<Table onChangeSelection={handleSelection} />
</div>
)
}
Table
's implementation or documentation, it’s quite likely that you would create an infinite loop before finding out that you need a useCallback
.// basic usage of EventEmitter
import EventEmitter from 'events'
const emitter = new EventEmitter()
const hello = (value) => console.log('hello', value)
emitter.on('change', hello)
emitter.emit('change', 'there') // log: hello there
// keep same emitter instance between renders
const emitter = useRef(new EventEmitter())
// create a dispatch function that doesn't change between renders
const dispatch = useCallback((...payload) => {
emitter.current.emit('aRandomEventName', ...payload)
}, [])
// subscribe our emitter to state changes
// notice dispatch remain the same between renders
// only state will trigger the effect
useEffect(() => {
dispatch(state)
}, [state, dispatch])
// subscribe the handler to the events
// this effect decouples our handler from the state change
useEffect(()=> {
emitter.current.on('aRandomEventName', handler)
// don't forget to unsubscribe the handler
return ()=> {
emitter.current.off('aRandomEventName', handler)
}
}, [handler, dispatch])
import EventEmitter from "events";
import { useCallback, useEffect, useRef } from "react";
export default function useListener(listener = () => {}) {
const emitter = useRef(new EventEmitter());
useEffect(() => {
const currentEmitter = emitter.current;
currentEmitter.on("event", listener);
return () => {
currentEmitter.off("event", listener);
};
}, [listener]);
const dispatch = useCallback((...payload) => {
emitter.current.emit("event", ...payload);
}, []);
return [dispatch, emitter];
}
function Table({ onChangeSelection }) {
const [value] = useTable(config)
const [dispatch] = useListener(onChangeSelection)
const { selected } = value.state
useEffect(()=> {
dispatch(selected)
// dispatch won't change when onChangeSelection changes
}, [selected, dispatch])
// ...
}
function Page() {
const [selection, setSelection] = useState({})
return (
<div>
<OtherComponent selection={selection} />
{/* we can use inline functions for handlers with ease now */}
<Table onChangeSelection={(value) => setSelection(value)} />
</div>
)
}