What is useReducer in React

December 11, 2020

A post by Will Mayger
Twitter . Instagram

Today, we are going to go over what is useReducer in React, as well as the basics, how to use useReducer in React, useReducer vs useState, when to use it, and useReducer vs redux.

A complete guide to what is useReducer in React and how to use it

If you have read some of my other posts, you know that I like to write about topics whilst avoiding as much technical jargon as possible so that everyone of all experience levels can benefit, learn and understand these topics, and that is what I will be doing for this post about useReducer in React!

Having the knowledge of useReducer will come in very handy in many situations, and you might find that after you have learnt what it is and how to use it that you may end up using it quite a bit.

Right, let’s get stuck in!

What is useReducer in React?

The react hook, useReducer, is an alternative to useState, both of these hooks can be used to manage state in a React functional component. The difference comes from where useReducer uses a reducer to manage the state which is essentially a function that will update different parts of your state depending on what you pass into it.

To fully understand the differences between them and what is useReducer in React, we will need to understand what are reducers and how they work. If you have had prior experience with redux then there is a good chance you have already used reducers and know how they work.

What is a reducer?

Let’s look at an example of a basic reducer using vanilla JavaScript and take it from there. We are going to simplify it down to its core elements so you can think of this example as a mix between pseudo code(code written out in normal language) and JavaScript.

export function basicReducer(state = { message: "" }, action) {
  switch (action.type) {
    case "UPDATE_MESSAGE":
      return {
        message: action.payload,
      }
    default:
      return state
  }
}

As you can see we are passing in two things into our reducer function, a state and a payload.

You can think of the state as the default state and the payload as the instructions to what we want to update and with what.

The reducer will be initialized with our default state, and whenever we want to update it we will need to pass it a new payload with the action, and the value for that action.

When we send our payload and our reducer receives the payload with the action and value, it will also be given a copy of the latest state rather than just the default.

This is something that we will let the useReducer hook handle when it comes to it, it will do this by providing us with a function that we can use called dispatch which is going to help us send our payload to our reducer along with the current state, so for the time being, just imagine that our reducer is being given the latest state.

Our JavaScript switch statement will then be run, and it will check the action against each scenario or case that we have created, if none match then it will just return the latest state unchanged.

If it is able to match an action, then it is going to run the code to add in the new payload value that goes with the action to the state and then return it which will then update the state.

It is worth noting that whatever we return from our reducer will update the state, which is why we need to still return the state even if it is unchanged.

Here is an example of dispatching an action (sending our updates to the reducer) with our payload:

dispatch({
  type: "UPDATE_MESSAGE",
  payload: "Hello world!",
})

This will then in turn update the state and then cause a re-render of our component, just like it would if we were using the useState react hook.

Understanding the basics of the useReducer react hook

Okay, now we have an understanding of what a reducer is in JavaScript, it is time to look at how we can start using the hook useReducer in React.

First let’s look at an example of useReducer to see how we can implement a simple component that uses this hook.

What is useReducer in React example:

import React, { useReducer } from "react"

export function basicReducer(state, action) {
  switch (action.type) {
    case "UPDATE_MESSAGE":
      return {
        ...state,
        message: action.payload,
      }
    default:
      return state
  }
}

export default function BasicComponentUsingReducer() {
  const [state, dispatch] = useReducer(basicReducer, { message: "" })

  return (
    <div>
      <p>{state?.message}</p>
      <button
        onClick={() =>
          dispatch({
            type: "UPDATE_MESSAGE",
            payload: "Hello world!",
          })
        }
      >
        Update message
      </button>
    </div>
  )
}

As you can see, we have our reducer function, our initialState and our useReducer and we send a new action to the reducer using the dispatch function on click.

You should start to notice some similarities with what we looked at earlier on when we were looking into reducers.

Infographic showing useReducer flow: USER CLICK => DISPATCH ACTION => REDUCE => NEW STATE => REREDNER

The first parameter we pass into useReducer is our reducer function that is going to help decide what gets updated and when depending on the payload, and the second parameter is our default state.

But, wait there is more.

We can add a third parameter to the useReducer hook which will return a default state.

Here is how this looks with an example:

import React, { useReducer } from "react"

export function basicReducer(state, action) {
  switch (action.type) {
    case "UPDATE_MESSAGE":
      return {
        ...state,
        message: action.payload,
      }
    default:
      return state
  }
}

export function initState(initialState) {
  return {
    message: initialState,
  }
}

export default function BasicComponentUsingReducer() {
  // the "" / empty string here will be passed into the initState function
  const [state, dispatch] = useReducer(basicReducer, "", initState)

  return (
    <div>
      <p>{state?.message}</p>
      <button
        onClick={() =>
          dispatch({
            type: "UPDATE_MESSAGE",
            payload: "Hello world!",
          })
        }
      >
        Update message
      </button>
    </div>
  )
}

Now, this might seem a little confusing because we already have a default state for the second parameter, so where does this function fit in?

Well, if we pass in the third function the initial state that we passed in will first get run through it before the initial state gets decided.

So if we pass in the first to parameters the flow is the following:

Initial state => Reducer => New State

But if we instead include the third parameter, the function, the flow will instead look like:

Initial State => Initial Function => Reducer => New State

After we have our useReducer initialized, it is going to return an array with our state as the first index, and then our dispatch function as the second which is going to help us be able to update the state.

This is pretty similar to the way useState will return its state and setter function.

How to use useReducer in React

Great, so now we understand the basics of reducers and the basics of the react hook useReducer. The next thing to do is going to be looking at how we can actually make use of this in React.

To do this let’s make a state based around a user object.

Here is our default state object:

const initialUserState = {
  name: "John Snow",
  email: "johnsnow@got.com",
  company: "Nights watch",
  status: "New job!",
}

Great, now as you can see we have a few different properties there and some might be more likely to change than others, because of this we are going to want to create a reducer that enables us to update different parts of this state.

For this example we will focus on updating the status of the user using the reducer, let’s take a look at how that reducer might look.

export function userDetailReducer(state, action) {
  switch (action.type) {
    case "UPDATE_STATUS":
      return {
        ...state,
        status: action.payload,
      }
    default:
      return state
  }
}

Okay great, now all that is left to do here is to add this into our useReducer to create a state, and a dispatch function so we can update it.

import React, { useReducer } from "react"

const initialUserState = {
  name: "John Snow",
  email: "johnsnow@got.com",
  company: "Nights watch",
  status: "New job!",
}

export function userDetailReducer(state, action) {
  switch (action.type) {
    case "UPDATE_STATUS":
      return {
        ...state,
        status: action.payload,
      }
    default:
      return state
  }
}

export default function Profile() {
  const [state, dispatch] = useReducer(userDetailReducer, initialUserState)

  return (
    <div>
      <h1>{state?.name}</h1>
      <p>Status: {state?.status}</p>
    </div>
  )
}

The next thing we are going to need to do here is to create an action function called updateStatus to make it easier to do.

export default function Profile() {
  const [state, dispatch] = useReducer(userDetailReducer, initialUserState)

  const updateStatus = nextStatus =>
    dispatch({
      type: "UPDATE_STATUS",
      payload: nextStatus,
    })

  return (
    <div>
      <h1>{state?.name}</h1>
      <p>Status: {state?.status}</p>
    </div>
  )
}

So, now we have our state, our reducer and a function to update the status using the dispatch function from useReducer.

Let’s now create a component that displays an input and button to the user so they can update the status

import React, { useState } from "react"

export function StatusUpdate() {
  const [nextStatus, setNextStatus] = useState("")

  const handleSubmit = e => {
    e.preventDefault()
    // TODO update status
    setNextStatus("")
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="statusUpdate"
        type="text"
        value={nextStatus}
        onChange={e => setNextStatus(e.target.value)}
      />
      <button type="submit">Update status</button>
    </form>
  )
}

Now we have our input component, we just need to provide it with our update function so it can update the status in our reducer.

Let’s add that in and take a look at our completed component with useReducer that will allow a user to update their status:

import React, { useReducer, useState } from "react"

export function StatusUpdate({ updateStatus }) {
  const [nextStatus, setNextStatus] = useState("")

  const handleSubmit = e => {
    e.preventDefault()
    updateStatus(nextStatus)
    setNextStatus("")
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="statusUpdate"
        type="text"
        value={nextStatus}
        onChange={e => setNextStatus(e.target.value)}
      />
      <button type="submit">Update status</button>
    </form>
  )
}

const initialUserState = {
  name: "John Snow",
  email: "johnsnow@got.com",
  company: "Nights watch",
  status: "New job!",
}

export function userDetailReducer(state, action) {
  switch (action.type) {
    case "UPDATE_STATUS":
      return {
        ...state,
        status: action.payload,
      }
    default:
      return state
  }
}

export default function Profile() {
  const [state, dispatch] = useReducer(basicReducer, initialUserState)

  const updateStatus = nextStatus =>
    dispatch({
      type: "UPDATE_STATUS",
      payload: nextStatus,
    })

  return (
    <div>
      <h1>{state?.name}</h1>
      <p>Status: {state?.status}</p>
      <StatusUpdate updateStatus={updateStatus} />
    </div>
  )
}

And there we have it, you can apply these principles to other ideas and concepts to get the most out of how to use useReducer in React.

If you want to take this to the next level, you can try to use useReducer with useContext to make a global state similar to redux (more on this later).

useReducer vs useState

We are just going to talk briefly about the differences between useReducer and useState now because it is asked a fair bit.

For those who like to see visually here is an infographic to explain useReducer vs useState: What is the difference of useReducer vs useState Infographic showing useReducer vs useState

useState will return the value of the state, and a setter function you can use to replace the entire state, whereas useReducer will return the value of the state and a dispatch function which will accept an action to update part of the state whilst leaving some parts untouched.

So, to put it into a single sentence, I would say the difference between the two is that one will replace the whole state and one will replace part of the state.

All in all, useReducer will give you a more granular approach to state that useState will.

When to use useReducer in React

So when should you really use useReducer in React over useState?

Well, this is going to bring us back to how useReducer provides us with a more granular approach to state than useState does.

I don’t think that it will be replacing useState and I think that for the most part you will still be using useState for most situations.

That is mainly because useState is a lot easier to use, understand and read in comparison to useReducer which requires a default state, a reducer and possibly even an init function to set the initial state.

With that being said, any time that you need to have a slightly more complex state, for example an object with properties that may get updated at different times, then useReducer will be a better candidate and will actually improve readability and ease of use.

So to answer this question, you should aim to use useState like you normally do, there is no rush to start using useReducer, but it you happen to end up in a situation where you are managing a complex state with multiple properties then it might be a good idea to start using useReducer.

useReducer react hook vs redux

Another topic we need to cover here is should you use the useReducer React hook or should you use redux.

And once again it is going to depend on what you need it for.

If you only have state in one component and you need a granular way of handling the state, for example, updating different properties at different times then you would not use redux here, and you should either use useState or useReducer depending on your preference.

However if we are talking about state management for an entire application, I would suggest sticking with redux for now.

Redux also has its own react hooks, useSelector and useDispatch and it will be able to do everything useReducer can, but you won’t have to worry about setting everything up and implementing your own context. On top of this Redux is well tested and allows for things like redux-sagas and other middlewares that you would have to recreate yourself if you decided to go with useReducer.

So, to summarize this, for application wide state management go for Redux. For small components that need some granular state management go for useReducer.

Using useReducer with useContext

Finally, to finish this article off, I just want to cover how you can use useReducer with useContext if you ever need to.

This section is assuming you know and understand context in React as well as the useContext hook. If you don’t then I have an article, How to use useContext in functional components, that will cover everything you need to know to get started with context and useContext so you can then pair it with useReducer.

Using our user example from above, we are going to modify it to add in context instead of prop drilling.

useContext with useReducer:

import React, { useReducer, useState, useContext, useEffect } from "react"

const templateFunction = () => {}

const initialUserState = {
  name: "John Snow",
  email: "johnsnow@got.com",
  company: "Nights watch",
  status: "New job!",
  updateStatus: templateFunction,
}

export function userDetailReducer(state, action) {
  switch (action.type) {
    case "UPDATE_STATUS":
      return {
        ...state,
        status: action.payload,
      }
    // create a case to create the updater function
    case "CREATE_UPDATE_STATUS_FUNC":
      return {
        ...state,
        updateStatus: action.payload,
      }
    default:
      return state
  }
}

// create context
export const UserContext = React.createContext(initialUserState)

export function StatusUpdate() {
  // replace props with useContext
  const context = useContext(UserContext)
  const [nextStatus, setNextStatus] = useState("")

  const handleSubmit = e => {
    e.preventDefault()
    // use the updater function in context
    context?.updateStatus(nextStatus)
    setNextStatus("")
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="statusUpdate"
        type="text"
        value={nextStatus}
        onChange={e => setNextStatus(e.target.value)}
      />
      <button type="submit">Update status</button>
    </form>
  )
}

export default function Profile() {
  const [context, dispatch] = useReducer(userDetailReducer, initialUserState)

  // use Effect to add in our updater function into the context
  // You could alternatively add in the dispatch and then create your own react hook
  // to access the dispatch like in redux
  useEffect(() => {
    if (context?.updateStatus === templateFunction) {
      // creat update function just like before
      const updateStatus = value =>
        dispatch({
          type: "UPDATE_STATUS",
          payload: value,
        })
      // add the function into our context
      dispatch({
        type: "CREATE_UPDATE_STATUS_FUNC",
        payload: updateStatus,
      })
    }
  }, [context?.updateStatus])

  // establish context provider
  return (
    <UserContext.Provider value={context}>
      <div>
        <h1>{context?.name}</h1>
        <p>Status: {context?.status}</p>
        <StatusUpdate />
      </div>
    </UserContext.Provider>
  )
}

As you can see, we are no longer passing down our props and instead we are using context to pass down the state and our action to be dispatched in the consumer component.

If you want to read up more about this I have a blog post from a while ago that talks about how you can use these two concepts here.

Summary

I hope this article about what is useReducer in React, how to and when to use it has helped!

If you enjoyed it please feel free to share it with friends and others who may find it useful.

Will