Getting Started with React's UseReducer Hook.

Ajay Ajay
5 min read

If you are getting started with react, there is almost 100% chance that you are practicing React with hooks. (The 100% stat i just made up :D,but i feel strongly this could be true) .

And in all Fairness,the simple good "useState" hook will manage your state fine. I've heard stories of indie developers ,running a million dollar SAAS products without ever upgrading from useState to useReducer.

useReducer has its benefits and is a must to know if you are working on large code-base with lots of states in components to manage.So let's get started.

Bigger picture of implementing useReducer.

useReducer has more boilerplate code than useState, but with increasing number of states in a component it becomes easy to maintain the state using useReducer. Let's get started with broader picture of useReducer implementation.

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

As we can see useReducer takes in a "reducer" function and "initial" state as an arguments.

And it returns "updated state" and a "dispatch" function.

"dispatch" function as name suggests is function utilized to dispatch "actions", based on the actions the state is updated as desired and a re-render is triggered.

Action in turn are values of any data type ,i.e it can be string,Number,function,Object etc.

But we can say it's a convention to use actions in an object form with two properties named "type" defining the action name and "payload" is the data an action carries in order to update state . If no data is required to update state "payload" key can be omitted. Idea is to have minimal necessary information in action that the reducer needs to compute the next state.

{type:"Add",payload :1}

Now lets focus on reducer function,its a function that handles all the updating state based on different actions dispatched.

A reducer function takes state and action as parameter ,in the function we update the state based on action and returned value is the new state .

const intialState = {
name:"xyz",
age:0
};
function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {

      return {
        ...state,
        age: state.age + 1
      };
    }

*Important Note - Notice above ,state is not mutated,instead we are returning a new object ,where last state is spread ,and age property is increased by 1 if action.type is "incremented_age".

Recap :

Let's quickly recap the broad implementation of useReducer.

call useReducer at top of the function to manage state with a Reducer.

import { useReducer } from 'react';

function reducer(state, action) {
  // ...
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, {name:"John", age: 42 });
  // .../

Example

Let's look at a short example of how useReducer can replace use State.

Take a look at the example of code below which uses useState to manage state in a a date counter Component.

import { useState } from "react";

function DateCounter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  // This mutates the date object.
  const date = new Date("june 21 2027");
  date.setDate(date.getDate() + count);

  const dec = function () {
    // setCount((count) => count - 1);
    setCount((count) => count - step);
  };

  const inc = function () {
    // setCount((count) => count + 1);
    setCount((count) => count + step);
  };

  const defineCount = function (e) {
    setCount(Number(e.target.value));
  };

  const defineStep = function (e) {
    setStep(Number(e.target.value));
  };

  const reset = function () {
    setCount(0);
    setStep(1);
  };

  return (
    <div className="counter">
      <div>
        <input
          type="range"
          min="0"
          max="10"
          value={step}
          onChange={defineStep}
        />
        <span>{step}</span>
      </div>

      <div>
        <button onClick={dec}>-</button>
        <input value={count} onChange={defineCount} />
        <button onClick={inc}>+</button>
      </div>

      <p>{date.toDateString()}</p>

      <div>
        <button onClick={reset}>Reset</button>
      </div>
    </div>
  );
}
export default DateCounter;

In the code above we are using our most common useState to manage count and step states.Now there are functions like

const reset = function () {
    setCount(0);
    setStep(1);
  };

where we have to use both setStates.in bigger components with a dozen of states this can look messy and hard to manage, with use reducer we can use appropriate actions to manage multiples values of state with appropriate action .

let's see the final code ,where we declare an initial state,remove useState and utilize useReducer, write an appropriate reducer function and replace setStates with dispatching appropriate reduce function.

import { useReducer } from "react";

const initialState = { count: 0, step: 1 };

function reducer(state, action) {
  console.log(state, action);

  switch (action.type) {
    case "dec":
      return { ...state, count: state.count - state.step };
    case "inc":
      return { ...state, count: state.count + state.step };
    case "setCount":
      return { ...state, count: action.payload };
    case "setStep":
      return { ...state, step: action.payload };
    case "reset":
      return initialState;
    default:
      throw new Error("Unknown action");
  }
}

function DateCounter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, step } = state;

  // This mutates the date object.
  const date = new Date("june 21 2027");
  date.setDate(date.getDate() + count);

  const dec = function () {
    dispatch({ type: "dec" });
  };

  const inc = function () {
    dispatch({ type: "inc" });
  };

  const defineCount = function (e) {
    dispatch({ type: "setCount", payload: Number(e.target.value) });
  };

  const defineStep = function (e) {
    dispatch({ type: "setStep", payload: Number(e.target.value) });
  };

  const reset = function () {
    dispatch({ type: "reset" });
  };

  return (
    <div className="counter">
      <div>
        <input
          type="range"
          min="0"
          max="10"
          value={step}
          onChange={defineStep}
        />
        <span>{step}</span>
      </div>

      <div>
        <button onClick={dec}>-</button>
        <input value={count} onChange={defineCount} />
        <button onClick={inc}>+</button>
      </div>

      <p>{date.toDateString()}</p>

      <div>
        <button onClick={reset}>Reset</button>
      </div>
    </div>
  );
}
export default DateCounter;

As we can see rather than setting states in respective function,state is managed centrally in reducer function,and actions are dispatched from different events(functions) . This makes code more readable and manageable .

use Reducer has few more uses,where it can be used in more advanced ways to managed state all through the app ,when we use it with useContext hook,but this will be topic of another blog .

Till then keep on coding .

Remember perfection is the enemy ,make small progress everyday .Happy coding .

Note - code examples above are from official react documentation .

and Jonas Schmedtmann's udemy react course https://www.udemy.com/course/the-ultimate-react-course/learn/lecture/37350954#overview

2
Subscribe to my newsletter

Read articles from Ajay directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ajay
Ajay

I am web developer from India, who loves everything related to web development, martial arts, writing, playing outdoor games and hanging out with honest minded folks