You are currently viewing useReducer Hook – How To Use It? | Ultimate Guide

useReducer Hook – How To Use It? | Ultimate Guide

If you’re building a react application, you have to know how to manage state in it.

Within the application, using useContext and useState, you got the ability to share the state as global to all component which solves the prop-drilling issue. The same can be achieved using useReducer and useContext.

The useState hook provides the local state management within the component.

However, to manage a complex state it is recommended to use the useReducer hook. Because with useState, managing complex state is first of all hard to manage and second its error prone. Thats where useReducer comes into the rescue.

What is useState Hook in React?

Before we dive into useReducer hook, lets understand first the useState hook.

The useState hook allows you to add state to a functional component. This hook take initial state as argument and returns an array with 2 values – current state, update state function.

				
					// creating state variable firstName

import { useState } from "react";

const [firstName, setFirstName] = useState('');
				
			

Here, at start firstName is set as empty and gets new firstName whenever its setter function setFirstName is called.

What is useReducer Hook in React?

Like useState hook, useReducer hook is also used to manage the component local state and with useContext hook provide the global state.

useReducer hook allows you to manage more complex state logic and handle multiple state updates with a reducer function. It lets us move the state update logic from event handlers into a single function outside of our component which makes the testing and debugging easier.

When we say complex state that means state is an complex object or an array.

				
					// creating state variable firstName

import { useReducer } from "react";

const [firstName, dispatch] = useReducer(reducerFn, '');

// we need to define reducerFn which would be updating
// the state based on some action type, 
// we will see in complete example below.
				
			

Principle behind the useReducer hook

The useReducer hook follows the principle of redux. Redux is a standalone 3rd party library for managing the state of any application built in react, vue or angular.

There was a fear of using it in react application as it has its own learning curve plus it increases the overall bundle size. But its has advantages as well.

So, React developer build a native hook which is alternative of redux, follows the same principle of redux and works like same without increasing the bundle size.

Here are the principles –

  1. Single source of truth: Define a single JavaScript object for maintaining the global state, this is called store. All components will have access to the same state and any component can update it using an action.

  2. State is read-only: When Components can’t directly update the state/store. They dispatch an action, reducer function of the useReducer hook update it and returns new copy of the complete store.

  3. Changes are made with pure functions: Both action creators and reducers should be pure functions. It should not modify the original input in any shape and should always return the same output for the same input.

 

useState vs useReducer - with an example

Lets build a simple form in react using useState and useReducer both and see which one is better.

We are going to build the below form where we can set the name and email and age of the user.

On click of Increment age button, age will be incremented by 1.

useState vs useReducer

Example using useState hook

				
					import { useState } from "react";

export default function App() {
  const [user, setUser] = useState({
    name: "",
    email: "",
    age: 0,
  });

  function incrementAge() {
    setUser((oldState) => ({
      ...oldState,
      age: oldState.age + 1,
    }));
  }

  function updateInput(e) {
    setUser((oldState) => ({
      ...oldState,
      [e.target.name]: e.target.value,
    }));
  }

  return (
    <div>
      <input
        placeholder="Enter name"
        value={user.name}
        name="name"
        onChange={updateInput}
      />
      <br />
      <br />
      <input
        placeholder="Enter email"
        value={user.email}
        name="email"
        onChange={updateInput}
      />
      <br />
      <br />
      <button onClick={incrementAge}>Increment age</button>
      <br />
      <br />
      {user.name && user.email && (
        <p>
          Hello, {user.name}. Your email address is {user.email} and your age is{" "}
          {user.age}.
        </p>
      )}
    </div>
  );
}

				
			

Key Points

  1. Pretty straight-forward
  2. Lesser code than useReducer below
  3. state is an object so its not advisable to use useState
  4. event handlers are updating the state and since its an object, we might miss updating some keys, i.e error prone for larger object.
  5. If the form is larger having checkbox, select, the components gets heavier and more error-prone i.e less scalable, manageable

You can see the output here – https://codesandbox.io/p/sandbox/adoring-kilby-ghp6w2

Example using useReducer hook

				
					import { useReducer, useState } from "react";

const userReducer = (state, action) => {
  switch (action.type) {
    case "updateInput":
      return {
        ...state,
        [action.field]: action.payload,
      };
    case "incrementAge":
      return {
        ...state,
        age: state.age + 1,
      };
    default:
      return state;
  }
};

export default function App() {
  const [user, dispatch] = useReducer(userReducer, {
    name: "",
    email: "",
    age: 0,
  });

  function incrementAge() {
    dispatch({
      type: "incrementAge",
    });
  }

  function updateInput(e) {
    dispatch({
      type: "updateInput",
      field: e.target.name,
      payload: e.target.value,
    });
  }

  return (
    <div>
      <input
        placeholder="Enter name"
        value={user.name}
        name="name"
        onChange={updateInput}
      />
      <br />
      <br />
      <input
        placeholder="Enter email"
        value={user.email}
        name="email"
        onChange={updateInput}
      />
      <br />
      <br />
      <button onClick={incrementAge}>Increment age</button>
      <br />
      <br />
      {user.name && user.email && (
        <p>
          Hello, {user.name}. Your email address is {user.email} and your age is{" "}
          {user.age}.
        </p>
      )}
    </div>
  );
}

				
			

Key Points

  1. Pretty simple too
  2. More code than useReducer but the reducer function can be moved to separate file thus making the component light weight.
  3. reducer function is handling the all state logic, now component can only focus on view part. Following the design principle – Separation of concerns
  4. event handlers are now only dispatching the action.. this is more readable and chances of errors are reduced significantly.
  5. useReducer is more advantageous If the form is larger having checkbox, select. As component will still be light weight and the logic is in different file with reducer function so its more scalable, manageable.

You can see the output here – https://codesandbox.io/p/sandbox/sweet-sun-79hfw7

Ease of testing with useReducer hook

				
					test("increments the age by one", () => {
  const newState = reducer({ 
    name: "xyz",
    email: "xyz@gmail.com",
    age: 0 
  }, { type: "incrementAge" });
  
  expect(newState.age).toBe(1);
})
				
			

We can see how easy to write unit test for the state logic. But with useState where logic is written inside the component is hard to test.

Advantages of useReducer hook in react

  1. Better control over state updates: With useReducer, we have more control over how state is updated. Since the reducer function is a pure function, it’s easier to find how state is changing over time.

  2. Manage complex state is easy: When you have complex state that involves multiple values and complex logic, useState can quickly become too heavy. useReducer provides a more structured approach to managing state that’s easier to manage and reason about.

  3. Better performance: Since dispatch is a static function which won’t trigger useEffect so passing it down instead of setState callback minimises the unnecessary re-renders. Thus, it can improve the overall speed and responsiveness of the app.

  4. Ease of testing: A reducer is a pure function that doesn’t depend on your component. This means that you can export and test it separately in isolation.

  5. Debugging: When you have a bug with useState, it can be difficult to tell from which component the state was set incorrectly, and why. With the useReducer hook, you can add a console log into your reducer to see every state update, and why it happened i.e due to which action. If each action is correct, you’ll know that the mistake is in the reducer logic itself.

Conclusion

When should we use useReducer over useState?

The crux of conclusion is –

  1. We can use both based on use-cases. If state is complex i.e array or objects prefer useReducer hook and when state is just one primitive value we can use useState.

  2. For separation of concerns, we should make component light-weight, all state logic, api calling and response handling should go with useReducer.

Other than this official react documentation and Redux developers also recommended its usage.

Dan Abramov (one of the creator of Redux)

useReducer is truly the cheat mode of Hooks. You might not appreciate it at first but it avoids a whole lot of potential issues that pop up both in classes and in components relying on useState. Get to know useReducer.

React docs

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

React docs:

We recommend to pass dispatch down in context rather than individual callbacks in props.

I hope you would have understood the useReducer hook and why its preferred over the useState. 

If you like it, please share among the friends.

To read more about react, checkout other articles.