sajad torkamani

In a nutshell

useReducer is a React hook that lets you manage state in your function components. It has the following signature:

const [state, dispatch] = useReducer(reducer, initialState, init)
  • state: the current state.
  • dispatch: function that dispatches an action.
  • reducer: a pure function that takes the previous state and an action, and returns the new state – (prevState, action) => newState.
  • initialState: the initial state.

You can also use this signature if you want to lazily initialize the initial state:

const [state, dispatch] = useReducer(reducer, initialArg, init);

initialArg will be passed as an argument to the init function.

When to use useReducer over useState

Both useReducer and useState let you manage component state so when should you prefer useReducer?

State is complex

Sometimes, your state is complex and involves multiple sub-values. This usually means complex state updates that update multiple sub-values.

useReducer lets you write your state updating logic as reducers – pure functions that take the previous state and an action, and return the next state. Because they’re pure functions, reducers are easy to test. When your state is complex, it’s useful and often essential to have good test coverage.

You need to trigger deep updates

If your component tree has several layers, and multiple components from different layers need to trigger state updates, you can combine useReducer with the Context API to pass down dispatch instead of callbacks.

For example, you can create a context and reducer:

const TodosContext = React.createContext(null)

function TodosApp() {
  // Note: `dispatch` won't change between re-renders
  const [todos, dispatch] = useReducer(todosReducer)

  return (
    <TodosContext.Provider value={dispatch}>
      <DeepTree todos={todos} />
    </TodosContext.Provider>
  )
}

Then, from any component that’s wrapped with a Context.Provider, you can access the dispatch method to trigger updates.

function DeepChild(props) {
  // If you want to perform an action, you can get dispatch from context.
  const dispatch = useContext(TodosDispatch);

  function handleClick() {
    dispatch({ type: 'add', text: 'hello' });
  }
  return (
    <button onClick={handleClick}>Add todo</button>
  );
}

Shouldn’t you just use Redux?

Combining useReducer with React.Context can go some way towards mimicking the behavior of Redux and unlocking its benefits (single source of truth = easier to debug app state, easier state persistence, etc).

It’s up to you whether you want to pull in Redux at this point. But Redux has its appeals:

Other notes

  • React guarantees that the dispatch function identity is stable and won’t change between re-renders. So, you don’t have to include dispatch in the dependencies list of useEffect or useCallback.
  • If you return the same state from a reducer as the current state, React will bail out and not render child components or fire effects. React uses the Object.is to determine if the returned state is identical to the current state.

Sources

Tagged: React