A picture of useReducer code, a React logo, and a CopyCat logo.

React useReducer Hook: Manage App State Better

May 10, 2022
copycat
Uncategorized

The Fastest Way to Build React UI

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Table of Contents
  1. Introduction
  2. The React useReducer Syntax
  3. How the React useReducer hook works
  4. Bailing out of a dispatch
  5. useReducer with Typescript
  6. useState Vs useReducer
  7. When to use the useReducer hook
  8. When not to use React useReducer
  9. Recommend videos
  10. Interesting reads from our blogs
  11. Conclusion

Introduction

State management has been a common topic in the development world over the years. It’s a challenging part of the development process, especially for huge applications. This challenge in state management has been solved in many different ways over time and keeps evolving in positive ways from the grassroot React useReducer.

In this article, we’re going to learn from grassroots, what there is to know about the useReducer hook, how it helps you manage application state better (and logic), along with real-world examples of how it works and why you might want to use it in your next or current project for better state management.

The React useReducer Syntax

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

The React useReducer is a pure function that takes up to three arguments and returns a state and a dispatch.

These three arguments are used to determine what the state is and how the dispatch function works.


Don’t worry about understanding this upfront, we’ll go through every inch of what this means and how it works.

What is a state in React?

const [state, ...

A state in react is a piece of data that represents the current status of our application. This state can be used to provide information on the screen or perform background computations/calculations that allow our application to function. State is a fundamental idea in React.
Here’s a visual example of a state in React, holding some information about a user.

States in useReducer by Lucius emmanuel

What is a dispatch in React?

const [state, dispatch...

Dispatch in React is simply a function that takes an instruction about what to do, then passes that instruction to the reducer as an “action”. In simple terms, see dispatch as a friendly boss that likes to tell the reducer what to do and is happy about it 😎

the reducer function by Lucius emmanuel

If you’re familiar with Redux, this term dispatch might not be new to you but, we’ll go through this article assuming you’re new to both Redux and useReducer. First, reducer function.

The reducer function

const [state, dispatch] = useReducer(reducer, ...

The reducer function is a special handler that takes in two parameters. Reducer function takes in the application’s current state object and an action object. Then reducer function uses that to determine/compute the next state of our application (it returns a new state).
Remember how we talked about the dispatch telling the reducer function what to do by passing it an action 💭?. These actions for reducer function usually contain a type (what to do) and a payload (what it needs to do the job).

Here’s a typical example of what a reducer function looks like:

function reducer(state, action) {
  switch (action.type) {
    case "FIX_BUGS":
      return { totalBugs: state.totalBugs - 1 };
    case "CREATE_BUGS":
      return { totalBugs: state.totalBugs + 1 };
    case "SET_TOTAL_BUGS":
      return { totalBugs: action.payload };
    default:
      throw new Error();
  }
}

Now, after reducer function, we’re starting to connect the dots as we’ll go through what all these mean visually.

the use reducer function by Lucius emmanuel

Let’s delve into what’s happening in the switch statement. In a real-world application, you’d most likely have more complex logic in the switch but for this example, we’ll be keeping it simple.

We have three cases in our switch statement, which are case "FIX_BUGS"case "CREATE_BUGS" and case "SET_TOTAL_BUGS". These are the actions we’re explicitly trying to handle. The default only fires when we dispatch an action that doesn’t match any of our cases. See cases (or action types) as a store of all possible things a particular reducer can do. If the reducer were a person, “cases” would be the skill list on the CV.

Here’s a visual representation of how the dispatch tells the reducer what to do.

dispatch actions paired with reducer by Lucius emmanuel

The initial state

const [state, dispatch] = useReducer(reducer, initialArg, ...

The initial state in a useReducer function is the starting state of our application e.g. The initial state or the default state. In the example we used earlier, we’re setting the total bugs of our application in the reducer function based on what type of action the dispatch tells us. For this reason, we can set the starting point or default state of our bugs count to say… “0” 🤔? or maybe even “100”? 🐛.

Let’s do a hundred! 😎 {totalBugs : 100}

Lazy initialization

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

The useReducer takes in an optional third parameter we’ll call init. This init is going to be a function we’ll pass as the third argument to useReducer. This can be useful if we’d like to create the initial state lazily.

A common use case would be a situation where the initial state needs to go through some calculations to arrive at a default state or has to fetch data from an API, etc. The initial state looks something like this in code:

const init = (initalState) => {
  console.log(initalState); // 100

//... do some code magic 🪄
//...

  return inital;  // return desired state

};

How the React useReducer hook works

Now that we’ve gone through the syntax: const [state, dispatch] = useReducer(reducer, initialArg, init); from left to right, it’s time to start putting together the pieces in code.

Here’s the complete code snippet for our bugs count application.

import { useReducer, useState } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "FIX_BUGS":
      return { totalBugs: state.totalBugs - 1 };
    case "CREATE_BUGS":
      return { totalBugs: state.totalBugs + 1 };
    case "SET_TOTAL_BUGS":
      return { totalBugs: action.payload };
    default:
      throw new Error();
  }
}
const init = (inital) => {
  console.log(inital); // 100
  return inital;
};

export default function App() {
  const [state, dispatch] = useReducer(reducer, { totalBugs: 100 }, init);

  // creating a new state so we don't add extra in the reducer state
  // (this is just for examples)
  const [inputState, setInputState] = useState(0);

  return (
    <div className="App">
      <h1>useReducer</h1>
      <p>{state.totalBugs} Bugs Left 🧑‍💻</p>
      <button onClick={() => dispatch({ type: "FIX_BUGS" })}>FIX_BUGS</button>
      <button onClick={() => dispatch({ type: "CREATE_BUGS" })}>
        CREATE_BUGS
      </button>
      <input
        onChange={(e) => setInputState(+e.target.value)}
        value={inputState}
        type="number"
      />
      <button
        onClick={() =>
          dispatch({ type: "SET_TOTAL_BUGS", payload: inputState })
        }
      >
        SET_TOTAL_BUGS
      </button>
    </div>
  );
}

For a live preview of the working code. I created a sandbox.

From the walkthrough of the useReducer syntax and what each bolt and knot means, to seeing the actual code in action. We can denote that the useReducer is a function that can take up to three arguments and returns a state and a dispatch.

Here’s a simple and summarized visual representation of the useReducer lifecycle in code.

useReducer lifecycle by Lucius emmanuel

Bailing out of a dispatch

If you return the same state in your reducer hook as the current state. React is smart enough to bail out of rendering or firing effects on components depending on that state. This is because React uses the Object.is comparison algorithm and this tells React that nothing has changed.

useReducer with Typescript

useReducer is no stranger to us anymore but how do we actually use it with TypeScript 💭?. Now if you’re already familiar with TypeScript, this one should be a piece of cake, however, if you’re fairly new, don’t worry, we’ll have a quick and simple example to get us going.

What we’re going to do here is by no means a rule, you’re free to use any formula or paradigm that makes you sleep better at night 😉.

Here it is:

import { ChangeEvent, useReducer } from "react";

// for our state
interface State {
  firstName: string;
  lastName: string;
  age: number;
  language: string;
}

// list of all possible types
enum ActionTypes {
  UPDATE = "UPDATE",
  RESET = "RESET"
}

// action type
interface Actions {
  type: ActionTypes;
  payload?: {
    key: string;
    value: string;
  };
}

// if we need to do some work to provide initial state
const init = (inital: State) => {
  console.log(inital);
  return inital;
};

// the default state of our app
const initialState = {
  firstName: "",
  lastName: "",
  age: 0,
  language: ""
};

// the reducer handler function
const userFormReducer = (state: State, action: Actions) => {
  switch (action.type) {
    case "UPDATE":
      return { ...state, [action.payload.key]: action.payload.value };
    case "RESET":
      return initialState;
    default:
      throw new Error();
  }
};

export const UserForm = () => {
  const [state, dispatch] = useReducer(userFormReducer, initialState, init);

  // for input change event
  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    dispatch({
      type: ActionTypes.UPDATE,
      payload: { key: event.target.name, value: event.target.value }
    });
  };

  return (
    <div>
      <h1>TypeScript Example</h1>
      <input
        value={state.firstName}
        type="text"
        name="firstName"
        onChange={handleChange}
        placeholder="first name"
      />
      <input
        value={state.lastName}
        type="text"
        name="lastName"
        onChange={handleChange}
        placeholder="lastName"
      />
      <input
        value={state.language}
        type="text"
        name="language"
        onChange={handleChange}
        placeholder="language"
      />
      <input
        value={state.age}
        type="text"
        name="age"
        onChange={handleChange}
        placeholder=""
      />
      <button
        onClick={() => dispatch({ type: ActionTypes.RESET })}
        type="reset"
      >
        RESET
      </button>
    </div>
  );
};

To test this out yourself, live on Sandbox. I’ve also included the typescript example.

This article is not about TypeScript, so we won’t be explaining what’s going on in that code. A few things to note however is that I intentionally put all the TypeScript code in one file. In a real-world application, you’s most certainly create files for different things and your approach might be quite different (Again, whatever helps you sleep better at night 😉).

useState Vs useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);
//vs
const [state, setState] = useState(initialState)

We won’t be creating a webinar or zoom meeting to argue about this, but a rule of thumb is, that everything you can do with a useReducer you can do with a useState. In fact, the useReducer hook is just an alternative to useState with a few clear differences such as:

  • Readability
  • Ease of use and,
  • Syntax

Note 💡
React guarantees that dispatch function identity is stable and won’t change on “re-renders”. This is why it’s safe to omit from the useEffect or useCallback hook dependency list.

When to use the useReducer hook

use a useReducer hook over useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. This is fairly common when your application begins to grow in size. useReducer also lets you improve performance for components that trigger deep updates because you can pass down a dispatch instead of callbacks.

When not to use React useReducer

useReducer is awesome, but there are certain scenarios where it doesn’t make our lives any easier. Here are a few of such cases:

  • Don’t use a useReducer hook when you’re dealing with simple state logic.
  • When your application needs a single source of truth. You’ll be better off using a more powerful library like Redux
  • When prop-drilling starts to yell at you. This happens when you get trapped in the hellish world of passing down too many props (state) to and from child components to a child component that later comes to hunt you.
  • When state lifting to parent component/top-level components no longer suffices.

Recommend videos

There’s always more to learn about state management, so if you’d like to learn more tricks and hacks about React hooks or additional hooks, I’d recommend you look up the video links below

Interesting reads from our blogs

Conclusion

The useReducer is a powerful hook if used properly and I hope we’ve been able to learn about it without any confusion. It’s a great hook for state management as well. If you’d like to contribute to this post or drop feedback, feel free to leave a comment 📝 and drop a like ❣️. You can also check out CopyCat to convert Figma to React with the click of a button, We’ll speed up your sprints by eliminating sprint delays and making the handoffs painless.

Thanks for reading, you’re awesome! 😎

Happy Coding!

Related Articles

  • Uncategorized

    The Challenges of Cross-Platform App Development and How to Overcome Them

    Introduction In the world of mobile apps, having a presence on multiple platforms is crucial to reaching a wider audience. However, creating separate apps for each platform can take time, effort, and resource-intensive. That's where cross-platform app development comes in.…

    February 20, 2023
  • Uncategorized

    Learn TypeScript Map Thoroughly and Make Your App 3x Better

    Is there a map in TypeScript? Have you ever needed to store key-value pairs in your web application? If so, you've likely encountered the JavaScript object as a standard solution. However, as your application grows, the limitations of the object…

    February 22, 2023

Convert Figma To React

Convert Figma designs to production-ready React.js code. Build stunning apps and landing pages faster than your peers and competitors.

Convert Design to Code