Telerik blogs

The useReducer() hook in React is similar to useState(), but is intended for more complex state changes. In this article, we’ll be exploring how useReducer() can be used to manage state differently than the standard useState() hook.

When working with state in a React application, we often need to manage state changes and updates in a way that can be easily understood and maintained. This is where React’s useState() and useReducer() hooks come in. These hooks provide us with tools to manage state in a functional component, allowing us to avoid the need for complex class-based component structures.

In this article, we’ll explore the useReducer() hook but before we do so, we’ll take a look at the simple useState() hook and compare and contrast the two hooks. This is because both these hooks can be used to achieve similar outcomes with different advantages and disadvantages.

useState Hook

The useState() hook is a built-in hook in React that allows us to add state to functional components. It:

  • Takes an initial state value as an argument
  • And returns an array containing the current state value and a function to update it

In this line of code: const [state, setState] = useState(initialState); - state is state variable, setState is function that updates state variable, and initialState is initial value of state variable

Let’s say we have a component that needs to keep track of a user’s name and age. We could use the useState() hook to manage these values like this:

import { useState } from "react";

const UserForm = () => {
  const [name, setName] = useState("Hassan");
  const [age, setAge] = useState(29);

  return (
    <form>
      <label>
        Name:
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </label>
      <label>
        Age:
        <input
          type="number"
          value={age}
          onChange={(e) => setAge(e.target.value)}
        />
      </label>
    </form>
  );
};

In the example above, we use the useState() hook to create two state values: name and age. We pass an initial value to each hook, and then use the returned update function to change the value when the user input changes.

User changes name from Hassan to John and age from 29 to 30

Let’s see how we can replicate the above behavior with the useReducer() hook.

useReducer Hook

The useReducer() hook is similar to useState() since it also provides a way to manage state changes and updates in a functional component but is intended to handle more complex state changes more efficiently. It:

  • Takes a reducer() function as the first argument. This reducer() function is a pure function that takes in the current state, an action, and returns a new state. It does not modify the original state, but rather returns a new state based on the action passed to it.
  • Takes an initial state value as the second argument.
  • Takes an (optional) initializer function that can be used to initialize state as the third argument.
  • And returns an array containing the current state value and a dispatch() function that can be used to trigger state changes by dispatching actions to the reducer.

In this line of code: const [state, dispatch] = useReducer(reducer,initialArg,initializer); - state is state variable, dispatch is function used to dispatch an action that will trigger a change in state, reducer is function that receives prvioues state and an action and returns the next state, initialArg is initial value of state, and initializer is optional function that can be used to initialize state

We’ll go through an example of how we can use the useReducer() hook to manage the same state values as we saw in the useState() example above. First, we’ll specify the initial state and define a reducer() function that takes in the current state and an action, and returns a new state based on the action type.

const initialState = { name: "John", age: 30 };

const reducer = (state, action) => {
  switch (action.type) {
    case "updateName":
      return { ...state, name: action.payload };
    case "updateAge":
      return { ...state, age: action.payload };
    default:
      return state;
  }
};

We then pass the reducer function and the initial state to the useReducer() hook being used in the component. The useReducer() hook returns an array containing the current state and a dispatch function.

import { useReducer } from 'react';

const initialState = { name: 'John', age: 30 };

const reducer = (state, action) => {
  switch (action.type) {
    case 'updateName':
      return { ...state, name: action.payload };
    case 'updateAge':
      return { ...state, age: action.payload };
    default:
      return state;
  }
};

const UserForm = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    // ...
  );
};

In the component markup, we can then use the state object to render the state date in our component.

We’ll also use the dispatch() function to trigger the appropriate action that is to be passed to the reducer() function to return a new state based on the action type and payload. When the user makes changes to the Name input, we dispatch the updateName action. Likewise, when the user makes changes to the Age input, we dispatch the updateAge action.

import { useReducer } from "react";

const initialState = { name: "John", age: 30 };

const reducer = (state, action) => {
  switch (action.type) {
    case "updateName":
      return { ...state, name: action.payload };
    case "updateAge":
      return { ...state, age: action.payload };
    default:
      return state;
  }
};

const UserForm = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <form>
      <label>
        Name:
        <input
          type="text"
          value={state.name}
          onChange={(e) =>
            dispatch({ type: "updateName", payload: e.target.value })
          }
        />
      </label>
      <label>
        Age:
        <input
          type="number"
          value={state.age}
          onChange={(e) =>
            dispatch({ type: "updateAge", payload: e.target.value })
          }
        />
      </label>
    </form>
  );
};

Our component behaves just like it did with our useState() example.

useState vs. useReducer

Since using the useState() and useReducer() hooks can give us the same outcome, when should we use one over the other?

One of the main advantages of using the useState() hook is its simplicity. It’s easy to understand and use, and requires minimal code to set up. However, it can become cumbersome when managing complex state changes, as we may need to write multiple useState() calls to handle different pieces of state.

On the other hand, the main advantage of using the useReducer() is that it allows us to manage complex state changes in a single, centralized place. We can define all of our state updates and actions in the reducer() function, making it easier to maintain and debug our application. However, useReducer() can be more complex to set up and understand compared to useState(), as it involves defining a reducer() function and using the dispatch() function to trigger actions.

Wrap-up

In conclusion, useState() and useReducer() are two hooks provided by React that allow us to manage state in functional components. Both hooks provide similar functionality but have different advantages and disadvantages depending on the complexity of our state management needs.

Ultimately, the choice between useState() and useReducer() will depend on the specific needs of your application. Both hooks can be useful tools in the right context, and it’s helpful to consider their pros and cons before deciding which one to use.


About the Author

Hassan Djirdeh

Hassan is currently a senior frontend engineer at Doordash. Prior to Doordash, Hassan worked at Instacart and Shopify, where he helped build large production applications at-scale. Hassan is also a published author and course instructor and has helped thousands of students learn in-depth fronted engineering tools like React, Vue, TypeScript and GraphQL. Hassan’s non-work interests range widely and, when not in front of a computer screen, you can find him at the gym, going for walks or running through the six.

Related Posts

Comments

Comments are disabled in preview mode.