useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
(quote from https://reactjs.org/docs/hooks-reference.html#usereducer)
I'm interested in the bold part, which states that useReducer
should be used instead of useState
when being used in contexts.
I tried both variants, but they don't appear to differ.
The way I compared both approaches was as follows:
const [state, updateState] = useState(); const [reducerState, dispatch] = useReducer(myReducerFunction);
I passed each of them once to a context object, which was being consumed in a deeper child (I just ran separate tests, replacing the value by the function that I wanted to test).
<ContextObject.Provider value={updateState // dispatch}>
The child contained these functions
const updateFunction = useContext(ContextObject); useEffect( () => { console.log('effect triggered'); console.log(updateFunction); }, [updateFunction] );
In both cases, when the parent rerendered (because of another local state change), the effect never ran, indicating that the update function isn't changed between renders. Am I reading the bold sentence in the quote wrong? Or is there something I'm overlooking?
The useReducer hook is usually recommended when the state becomes complex, with state values depending on each other or when the next state depends on the previous one. However, many times you can simply bundle your separate useState statements into one object and continue to use useState .
The useReducer replaces useState . It is your state.
Can useReducer replace Redux? The useReducer hook should be used in components that have complex logic behind it. It shows as the main confusion with the Redux library, because developers tend to think that useReducer could replace the state manager library. But in fact, its use should be restricted to components.
The useState hook is used for storing variables that are part of your application's state and will change as the user interacts with your website. The useEffect hook allows components to react to lifecycle events such as mounting to the DOM, re-rendering, and unmounting.
useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.
The above statement is not trying to indicate that the setter returned by useState
is being created newly on each update or render. What it means is that when you have a complex logic to update state you simply won't use the setter directly to update state, instead you will write a complex function which in turn would call the setter with updated state something like
const handleStateChange = () => { // lots of logic to derive updated state updateState(newState); } ContextObject.Provider value={{state, handleStateChange}}>
Now in the above case everytime the parent is re-rendered a new instance of handleStateChange is created causing the Context Consumer to also re-render.
A solution to the above case is to use useCallback
and memoize the state updater method and use it. However for this you would need to take care of closure issues associated with using the values within the method.
Hence it is recommended to use useReducer
which returns a dispatch
method that doesn't change between re-renders and you can have the manipulation logic in the reducers.
If you create a callback on render and pass it to a child component, the props of that child will change. However, when the parent renders, a regular component will rerender (to the virtual dom), even props remain the same. The exception is a classComponent that implements shouldComponentUpdate, and compares props (such as a PureComponent).
This is an optimization, and you should only care about it if rerendering the child component requires significant computation (If you render it to the same screen multiple times, or if it will require a deep or significant rerender).
If this is the case, you should make sure:
While I would not recommend building a unique memoized setter for a specific component (there are a few things you need to look out for), you could use a general hook that takes care of implementation for you.
Here is an example of a useObjState hook, which provides an easy API, and which will not cause additional rerenders.
const useObjState = initialObj => { const [obj, setObj] = React.useState(initialObj); const memoizedSetObj = React.useMemo(() => { const helper = {}; Object.keys(initialObj).forEach(key => { helper[key] = newVal => setObj(prevObj => ({ ...prevObj, [key]: newVal })); }); return helper; }, []); return [obj, memoizedSetObj]; }; function App() { const [user, memoizedSetUser] = useObjState({ id: 1, name: "ed", age: null, }); return ( <NameComp setter={memoizedSetUser.name} name={user.name} /> ); } const NameComp = ({name, setter}) => ( <div> <h1>{name}</h1> <input value={name} onChange={e => setter(e.target.value)} /> </div> )
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With