What the heck is useReducer()?

Ashif KhanAshif Khan
7 min read

Prerequisutes:

- Basic understanding of JavaScript

- Basic understanding of array functions

- Basic understading of React

- Basic understading of hooks in react

Introduction

In simple words, useReducer is a hook used for state management in react.

Don't we have useState for managing states in react?

Well! We have useState for maintaining states in react, useReducer is an alternative to maintain states in react.

So, What's the difference between useReducer and useState?

useState is built on top of useReducer, so useReducer is a more efficient hook compared to useState.

If both are built for managing state, then when should I use useState, and when should I use useReducer?

confused-brahmanandam.gif

For this we'll go through some examples, understand useReducer in depth and come back to the question. So for now, we know that useReducer is a hook to manage states in react.

For understanding useReducer, let's understand reducers in JavaScript first.

If we jump back to vanilla JavaScript, we have some built-in functions on array for example filter, map, sort, reduce etc.

Let's understand reduce function in depth to understand useReducer even better.

If we look at the MDN docs for reduce, it says :

Screenshot from 2022-06-03 21-31-23.png

Let's understand reducer with an example:

const array = [10, 20, 30, 40, 50];
//first we have an array of five numbers

const reducer = (accumulator, currentValue) => accumulator + currentValue;
// second we have a es6 function called reducer which accepts two parameters,
//  accumulator and currentValue and return a single value which is the sum of two parameters

console.log(array.reduce(reducer)); //expected output is 150
// third we have a console.log statement which prints the sum of all the numbers in the array

console.log(array.reduce(reducer, 100)); //expected output is 250
// another form of the reduuce method which accepts an initial value as a second parameter(for the accumulator parameter)

What we learned so far?

  • The reduce method takes two parameters, the first parameter is the reducer function and the second parameter is an initial for the accumulator to start with.

  • The reducer function itself takes two parameters and reduces them to a single value and returns the value. In our example, the reducer takes two values and returns one value, which is the summation of the array.

We were here to learn useReducer. So why did we learn all this?

So there is a huge similarity between what we learned and what we are going to learn. To understand something very well, fundamental is the key.

yes-baby.gif

reduce vs useReducer

  • With reduce method we have two parameters a reducer function and an initial value for the accumulator similarly, with useReducer we again have two parameters, the first parameter is again a reducer function but here the second parameter is the initial state.

  • With the reduce method, the reducer will accept two parameters which are the accumulator and the item value and returns a single value, with useReducer the reducer function again accepts two parameters but this time the parameters are the current state and the second parameter is called as action and the reducer here will return a new state.

  • The reduce method returns a single value. Whereas the useReducer hook return a pair of values. First value is the new state and second value is called a dispatch method which is used to specify the action.

This might sound like rocket science if you're going through this for the first time, but you are definitely understood what we are talking about as we see more examples.

Screenshot from 2022-06-03 22-15-32.png

Let's understand useReducer by creating a counter.

There are three basic requirements for a counter :

  1. Incrementing the count value.
  2. Decrementing the count value.
  3. Reset the value.

We'll see the code for the counter now:

  • First thing first let's start with creating a functional component, We are going to create three buttons i.e. Increment, decrement and reset.
import React from 'react'

export const Counter = () => {
  return (
      <div>
          <button>Increment</button>
          <button>Decrement</button>
          <button>Reset</button>
    </div>
  )
}
  • Second, we will need a count variable to display the value of the count in the application. And for this second step we need useReducer.

-Third, we will need to import the useReducer from react. To import useReducer, we will write

import { useReducer } from 'react'

Now we can use useReducer.

useReducer in our functional component

Just like any other hook, we need to call useReducer in our functional component. Now let's try to get back to the basics, where we learned useReducer accepts two arguments. The first one is a reducer function and the second one is the initial state.

But we haven't defined our reducer yet. So let's create our reducer function and remember useReducer takes two arguments. So let's define our second argument, which is our initial state for the counter.

Defining the reducer and initial value for our counter.

  • Our counter will start from the value of Zero. The initial value which is our initial state for the counter is also going to be zero.

  • Next, We will define the reducer function. Which will be an arrow function. We learned that a reducer function accepts two values and returns one value. The two values accepted are the current state and the action. These are the parameters to the reducer function.

Lets Understand it with the program

import React from 'react'
import { useReducer } from 'react'

const initialState = 0
const reducer = (state, action) => {
    return newState
}
export const Counter = () => {
    useReducer(reducer, initialState)
    return (
        <div>
            <button>Increment</button>
            <button>Decrement</button>
            <button>Reset</button>
        </div>
    )
}

So till here we learned that the reducer function accepts the current state and returns the new state. But for this transition to happen (taking the current state and returning the new state), we need something, So for us this something will be the action parameter passed to the reducer function. We can think of action as an instruction to the reducer function. Based on the instructions given, the reducer will perform the necessary state transition.

think-smart.gif

For our example, we can have three actions, Increment, Decrement and Reset.

The convention to execute code based on the action is to use switch statements. So inside the reducer function we are going to add a switch statement.

import React from 'react'
import { useReducer } from 'react'

const initialState = 0
const reducer = (state, action) => {
    switch (action) {
        case 'increment':
            return state + 1
        case 'decrement' :
            return state - 1
        case 'reset' :
            return initialState
        default:
            return state
}
export const Counter = () => {
    useReducer(reducer, initialState)
    return (
        <div>
            <button>Increment</button>
            <button>Decrement</button>
            <button>Reset</button>
        </div>
    )
}

It's good to have a default state in the reducer.(optional)

whatever-sassy.gif

The final step:

For the final step, we need to get the value to display in our application, and we also need a way to call the reducer function with the appropriate action. Which happens to be really simple as that is exactly what useReducer returns.

endgame-now.gif

Similar to useState, useReducer also returns a pair of values which we can use by array destructing syntax.

const [count,dispatch]=useReducer(reducer, initialState)

So useReducer returns the current state which we have called as count paired with a dispatch method. This dispatch method will allow us to execute the code corresponding to a particular action in the code.

In the JSX we can now render the count value and to each of the button we can add click handler.

import React from "react";
import { useReducer } from "react";

const initialState = 0;
const reducer = (state, action) => {
  switch (action) {
    case "increment":
      return state + 1;
    case "decrement":
      return state - 1;
    case "reset":
      return initialState;
    default:
      return state;
  }
};
export const Counter = () => {
  const [count, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch("increment")}>Increment</button>
      <button onClick={() => dispatch("decrement")}>Decrement</button>
      <button onClick={() => dispatch("reset")}>Reset</button>
    </div>
  );
};

As we can see that the argument to the dispatch method is the action that is specified in the reducer function.

So, we have finally wrote our counter application with the useReducer. One thing to know here is You dont need to follow the same pattern to write your useReducer function.

dance-kid.gif

Getting back to the question: When should I use useState, and when should I use useReducer?

the-answer-is-here-brock-vereen.gif

useReducer() is an alternative to useState(). For simple state management, simply use useState(). user Reducer is always recommended when state is bigger and you need to manage complex states.Wrapping up, you don't need to jump to useReducer as soon as you have multiple useState hooks in your component. Many times, it's enough to just bundle all the state values in one object. However, if it becomes hard to figure out what's going on, useReducer can be a very helpful tool!

7
Subscribe to my newsletter

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

Written by

Ashif Khan
Ashif Khan