Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

useState vs useReducer

Tags:

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?

like image 310
JDansercoer Avatar asked Feb 12 '19 09:02

JDansercoer


People also ask

Is useReducer better than useState?

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 .

Does useReducer replace useState?

The useReducer replaces useState . It is your state.

Is useReducer better than Redux?

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.

Should I use useState or useEffect?

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.


2 Answers

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.

like image 117
Shubham Khatri Avatar answered Sep 22 '22 17:09

Shubham Khatri


When you need to care about it

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:

  1. Your child is a class component that extends PureComponent
  2. Avoid passing a newly created function as a prop. Instead, pass dispatch, the setter returned from React.useState or a memoized customized setter.

Using a memoized customized setter

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

like image 22
Ben Carp Avatar answered Sep 21 '22 17:09

Ben Carp