How to use the useReducer hook in React

Updating state with the useReducer hook.

When handling state in your react components you might often reach for the useState hook. That is handy for simple state values that only pertain to that component, but when you need to deal with complex state objects that can be used and updated across multiple components within your application, the useReducer hook can come in handy.

TL;DR The useReducer hook is for setting and managing state logic that is too complex for the useState hook. It accepts a reducer function to handle state updates.

When to use useReducer over useState?

  • When you are dealing with complex state objects
  • If multiple components can update state
  • If next state depends on previous state
  • Your overall react app architecture is complex

Basic usage of useReducer

The useReducer hook takes a reducer function and some initial state and returns an array with a state variable and dispatch function. In comparison to setState the state variable is the current state and the dispatch function is used to set state. The big difference is that setState in the useState hook takes the new state whereas the dispatch acception an action.

What is a reducer action?

A reducer action is a JavaScript object that describes the state change. A type property is required on all actions as this is what will tell the reducer how to update state (this is conventionally written as an uppercase string). An optional payload property is used to send some additional data that pertains to the state change.

A reducer action is a JavaScript object that describes the state change.

Putting this all together we can write a simple component that uses useReducer for setting the first name:

const initialState = { firstName: "" }

const reducerFn = (state, action) => {
  switch (action.type) {
    case "SET_FIRST_NAME":
      return {
        ...state,
        firstName: action.payload,
      }
    default:
      return state
  }
}

const Component = () => {
  const [state, dispatch] = useReducer(reducerFn, initialState)
  return (
    <div>
      <div>{state.firstName}</div>
      <button
        onClick={() => dispatch({ type: "SET_FIRST_NAME", payload: "Dwight" })}
      >
        Set name
      </button>
    </div>
  )
}

Does useReducer replace Redux?

The useReducer hook by itself does not replace Redux because it does not create a global store. You can have multiple state objects in your react application that are isolated from each other, which violates Redux's single source of true principle.

Combining useReducer with React.Context can get you closer to replicating most of Redux’s functionality because you can store all of your state in a global context.

Using useReducer with React.Context

Combining React.Context with useReducer allows you to have global state with reducer state updates. This is a great alternative to Redux that’s built right into React.

import { createContext, useReducer, useContext } from "react"

const todosDefaultState = {
  todos: [],
}

function todosReducer(state = todosDefaultState, action = {}) {
  switch (action.type) {
    case "NEW_TODO":
      return { ...state, todos: [...state.todos, action.payload] }
    case "COMPLETE_TODO":
      const updatedTodos = state.todos.map((todo) => {
        if (todo.id === action.id) {
          return { ...todo, done: true }
        } else {
          return todo
        }
      })
      return { ...state, todos: updatedTodos }
    default:
      return state
  }
}

const StoreContext = createContext(null)

export function StoreProvider({ children }) {
  const [todosState, todosDispatch] = useReducer(
    todosReducer,
    todosDefaultState
  )
  return (
    <StoreContext.Provider value={{ todosState, todosDispatch }}>
      {children}
    </StoreContext.Provider>
  )
}

export const useStore = () => useContext(StoreContext)

You can wrap your entire application with the StoreContext.Provider component and have direct access to it's state and dispatch functions.

Have some feedback on this post? .