Leo Cristofani · Web Developer

When to use useReducer over useState

useReducer makes your state logic more declarative and flexible. Use it when you need to update multiple pieces of state at once. Use useState for independent pieces of state.

More details

useState and useReducer are React Hooks that you can use to persist state between re-renders. Changing the state with both useState and useReducer will cause a re-render.

If you only need a single piece of state or multiple independent pieces of state, choose useState to avoid the extra boilerplate work that comes with creating a reducer function and defining action types.

Use useReducer when multiple pieces of the state need to be in sync. In other words, when a single effect, action or event causes multiple pieces of state to change at once, the useReducer React Hook will make your code more declarative and flexible.

Let’s see this in action with useBeagles, a custom React Hook that fetches photos of cute beagles from a public API. We’re going to create 2 versions of useBeagles - one with useState and another one with useReducer. Then, we’ll compare the two.

The useBeagles custom hook with useState

With useState you have to imperatively change every single piece of state. Full example on Code Sandbox

// ...
export default function useStateFetchApi(url) {
  const [data, setData] = useState([])
  const [error, setError] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    setLoading(true)
    fetch(url)
      .then(res => {
        setData(res.message)
        setError(null)
        setLoading(false)
      })
      .catch(err => {
        setData([])
        setError(err.message)
        setLoading(false)
      })
    // ...
  }, [url])

  return { data, error, loading }
}

The useBeagles custom hook with useReducer

With useReduce, you describe what you want to change instead of how to change it. Full example on Code Sandbox

// ...
function useBeagles(url) {
  const [{ data, error, loading }, dispatch] = useReducer(reducer, initialState)

  useEffect(() => {
    dispatch({ type: FETCH_REQUEST })
    fetch(url)
      .then(res => {
        dispatch({ type: FETCH_SUCCESS, payload: res.message })
      })
      .catch(err => {
        dispatch({ type: FETCH_FAILURE, payload: err.message })
      })
    // ...
  }, [url])

  return { data, error, loading }
}

Thanks for reading! Have something to say? Please, leave a comment!