Rajat Gupta
Rajat's Blog

Rajat's Blog

useReducer in react: Everything you need to know

useReducer in react: Everything you need to know

Rajat Gupta's photo
Rajat Gupta
ยทMar 13, 2022ยท

8 min read

Subscribe to my newsletter and never miss my upcoming articles

prerequisites:

  1. Grasp of reduce method in JavaScript and
  2. useState in react

For reduce method, I recommend you to read my blog here: rajatgupta.net/reduce-method-in-javascript

Ok, Let's get started.

The useReducer Hook is similar to the useState Hook. We use useReducer because managing multiple states (like more than 3) using useStates is a hassle. that's all.

Since talk is cheap, let me show you the code. Let's make a simple counter to increment the value by 1 with useState, then I'll show you how to do it with useReduce and some more complex examples.

import "./styles.css";
import {useState} from 'react';

export default function App() {

  const [counter, setCounter] = useState(0);

  return (
    <div className="App">
     <h1>{counter}</h1>
     <button onClick={() => setCounter(counter+1)}>+</button>
    </div>
  );
}

1.gif

Now, let me show you how to do it with useReducer. Just see the code:

import "./styles.css";
import {useReducer} from 'react';

export default function App() {

function reducerFunc(state, action){
  return state+1;
}

  // Syntax:
 //const [state, dispatch] = useReduce(theReducerFunction, initialValueOfState);

  const [state, dispatch] = useReducer(reducerFunc, 0);

  return (
    <div className="App">
     <h1>{state}</h1>
     <button onClick={() => dispatch(1)}>+</button>
    </div>
  );
}

Done! Now, let's hear the conversation between Ravi and Rajat to understand how the above code is working:

Rajat: As soon as the user will click on the "+" button, the dispatch function will be called and yes dispatch is a function.

Ravi: Ok, I get it but what is the role of dispatch.

Rajat: Listen very carefully, this is the most important part. You can see the dispatch function in the useReducer syntax and as soon as the dispatch is called, it will further call the reducerFunc.

Ravi: ok, but the reducerFunc takes 2 parameters, where we'll get them.

Rajat: Good question man. The reducerFunc will automatically be called with 2 arguments. The 1st one is 0 which you can see as the 2nd parameter inside useReducer() (useReducer(reducerFunc, 0)) and it goes to the state parameter in reducerFunc and the 2nd one is 1 which is being picked up from onClick (<button onClick={() => dispatch(1)}>+</button>) and goes to the action parameter in reducerFunc.

Ravi: That's too much information to wrap my head around.

Rajat: No, it's not read it again and one more thing, the parameters you see in the reducerFunc that is state and action will behave similarly to accumulator and initialValue that we provide in the reduce method in JavaScript because the reducerFunc will behave like the function that we pass inside the reduce method in JavaScript that used to return the accumulator.

does the above conversation made sense? If it still does not make much sense don't worry, let's move on to the next example, and make a simple counter but this time there will be 2 states, one to increment and another one to decrement the counter value. The increment counter will increment the counter value by 40 and the decrement counter will decrease it by 10.

import "./styles.css";
import {useState} from 'react';

export default function App() {

  const [counter, setCounter] = useState(0);

  return (
    <div className="App">
     <h1>{counter}</h1>
     <button onClick={() => setCounter(counter+40)}>+</button>

     <button onClick={() => setCounter(counter-10)}>-</button>
    </div>
  );
}

2.gif

Now, let's make it using useReducer.

import "./styles.css";
import {useReducer} from 'react';

export default function App() {

function reducerFunc(state, action){
  switch(action.type){
    case 'INCREMENT':
      return {...state, counter: state.counter + action.payload}
    case 'DECREMENT':
      return {...state, counter: state.counter - action.payload}
    default:
      return state;
  }
}

  const [state, dispatch] = useReducer(reducerFunc,{counter: 0});

  return (
    <div className="App">
     <h1>{state.counter}</h1>
     <button onClick={() => dispatch({type: 'INCREMENT', payload: 40})}>+</button>

     <button onClick={() => dispatch({type: 'DECREMENT', payload: 10})}>-</button>
    </div>
  );
}

Again, let's hear the conversation between Ravi and Rajat.

Ravi: I understood what you told about the previous code but here you did some changes such as instead of passing a value such as 1 you passed an object in dispatch in the onClick statement. why?

Rajat: Remember, I told you at the start of this blog that useReducer is used to manage multiple states.

Ravi: yes

Rajat: In order to manage multiple states with a single useReducer, we need to differentiate between the states and based on type we do that.

Rajat: Let me tell you what's happening in the above code. As soon as the user clicks any button (- or +), dispatch is called which in turn calls the reducerFunc with state and {counter: 0} as argument and state and action as the corresponding parameters. Further, as I told you before, imagine state and action as accumulator and current value respectively (that we used to use in simple reduce in JavaScript).

Ravi: Ok so the reducerFunc is called what is happening inside reducerFunc.

Rajat: Well, that is simple.

Rajat: If user click +, the objects {counter: 0} and {type: 'INCREMENT', payload: 40} are passed as state and action respectively. then, we see the value of action.type and since it is 'INCREMENT', the first case runs and '{...state, counter: state.counter + action.payload}' is returned in which state.counter is the previous value of counter and action.payload is 40. (If you do not know what is ...state, read my blog about spread operator here: rajatgupta.net/spread-syntax-in-javascript)

Rajat: similarly, the 'DECREMENT' case is executed when the user clicks on -.

Ravi: and what is the {state.counter} that you are rendering.

Rajat: Think logically bro, the value of state is getting updated and by state I mean the number in {counter: number} is getting updated, and in order to access this number we have to do state.counter.

Ravi: Awesome, I understood. Just need to practice this problem once on my own and I recommend the ones reading this blog to do the same and then come back to read further.

Many of you must be thinking that why all this hassle when we can use useState. This is because in the above โ˜๏ธ example we are managing only 2 states, but what if we have a complex app and there are 10 states to manage then useState will be a hassle. As a rule of thumb, I suggest using useReducer when there are more than 3 states to manage.

Ok, now let's get to the next example and make this:
On clicking Add to Cart button, items should be added in the cart, and Items in Cart and Total Price should increase. However, on clicking Remove from Cart button, the opposite should happen. 3.gif

I expect you try this on your own using useReducer and give this problem at least 30 minutes and after that if you can't figure it out then jump to below code.

import { useReducer } from "react";
import "./styles.css";

const itemsInCart = [
  {
    id: 1,
    name: "kala chasma",
    price: 1000
  },
  {
    id: 2,
    name: "rumali roti",
    price: 500
  },
  {
    id: 3,
    name: "jalebi",
    price: 50
  },
  {
    id: 4,
    name: "joota",
    price: 10000
  }
];

export default function App() {
  const [state, dispatch] = useReducer(
    (state, action) => {
      switch (action.type) {
        case "ADD_TO_CART":
          return {
            ...state,
            totalItems: state.totalItems + 1,
            totalPrice: state.totalPrice + action.payload
          };
        case "REMOVE_FROM_CART":
          return {
            ...state,
            totalItems: state.totalItems - 1,
            totalPrice: state.totalPrice - action.payload
          };
        default:
          return { ...state };
      }
    },
    { totalItems: 0, totalPrice: 0 }
  );

  return (
    <div className="App">
      <h2>Items in Cart: {state.totalItems}</h2>
      <h2>Total Price: {state.totalPrice}</h2>
      <hr />
      {itemsInCart.map(({ name, price }) => (
        <div>
          <h3>
            Product: {name} || Price: {price}
          </h3>
          <button
            onClick={() => dispatch({ type: "ADD_TO_CART", payload: price })}
          >
            Add to Cart
          </button>
          <button
            onClick={() =>
              dispatch({ type: "REMOVE_FROM_CART", payload: price })
            }
          >
            Remove from Cart
          </button>
        </div>
      ))}
    </div>
  );
}

I wanna end this blog here as the logic of the above code is the same as the example before it but it does not feel complete without explaining the above code. So, here we go.

  1. customer clicks on the Add to Cart button.

  2. As soon as the click happens, the dispatch function is called which in turn calls the function defined inside useReducer (here I did not define the reducerFunc separately and used the arrow function inside useReducer as this way I like the code more) which in turn calls the function defined inside useReducer with state and action as a parameter where the state is { totalItems: 0, totalPrice: 0 } and action is { type: "ADD_TO_CART", payload: price }.

From the above assignment, you can get the values of state.totalItems, state.totalPrice and action.payload which are further used to update the value of state after every click. Finally, we render state.totalItems and state.totalPrice.

Now wrestle with the above code fro sometime and you'll get a better understanding.

Just one more thing: The names we gave that are state, action, type, payload, and dispatch are not keywords. You can give any name you want. It's just out of the convention that we used these names (and I like conventions since I don't have to think about trivial stuff and It brings people on the same page in terms of understanding the code.)

If you like this blog and have some confusion about useEffect, or any other react hooks then read my blogs here: rajatgupta.net.

That's all folks.

If you have any doubt ask me in the comments section and I'll try to answer as soon as possible.

I write 3 articles every week related to web development. Hence, you should subscribe to my newsletter if you are learning web development

If you love the article follow me on Twitter: @therajatg

If you are the Linkedin type, let's connect: linkedin.com/in/therajatg

Have an awesome day ahead ๐Ÿ˜€!

ย 
Share this