Home » useReducer in React

useReducer in React

useReducer in React

Introduction

The useReducer in React is similar to useState() since it also provides a way to manage state changes and updates in a functional component but is intended to handle more complex state changes more efficiently. It:

  • Takes a reducer() function as the first argument. This reducer() function is a pure function that takes in the current state, an action, and returns a new state. It does not modify the original state, but rather returns a new state based on the action passed to it.
  • Takes an initial state value as the second argument.
  • Takes an (optional) initializer function that can be used to initialize state as the third argument.
  • And returns an array containing the current state value and a dispatch() function that can be used to trigger state changes by dispatching actions to the reducer.

Creating simple counter using useState or useReducer in React

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.

Simple Counter in React:

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>
  );
}
JavaScript

Simple counter using useReducer in React:

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>
  );
}
JavaScript

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

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

Role of Dispatch in useReducer in React:

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.

ok, but the reducerFunc takes 2 parameters, where we’ll get them.

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.

That’s too much information to wrap head around.

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>
  );
}
JavaScript

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>
  );
}
JavaScript

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?

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

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

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). Ok so the reducerFunc is called what is happening inside reducerFunc.

Well, that is simple.

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.

similarly, the ‘DECREMENT’ case is executed when the user clicks on -.

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

What is the {state.counter} that you are rendering?

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

Awesome, We 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 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.

Add to Cart Button using useReducer in React

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.

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>
);
}
JavaScript

I’ll conclude this blog here, as the logic of the above code mirrors the previous example but feels incomplete without explanation. So, here we go: when the customer clicks the “Add to Cart” button, the dispatch function is called, triggering the useReducer function. I prefer using an inline arrow function for simplicity. The useReducer function receives the state ({ totalItems: 0, totalPrice: 0 }) and action ({ type: “ADD_TO_CART”, payload: price }). These update state.totalItems and state.totalPrice with each click. We then render these values. Spend some time with the code for better understanding. Lastly, state, action, type, payload, and dispatch are not keywords; they are conventional names for clarity. Using conventions avoids trivial decisions and aids code comprehension.

Conclusion

The useReducer hook in React provides a powerful way to manage state in functional components, particularly when dealing with complex state logic and state transitions. By encapsulating state and state transition logic within a reducer function, useReducer promotes cleaner and more maintainable code, especially for larger applications where state management becomes more critical. While it may add some initial complexity compared to useState, useReducer offers more flexibility and control, making it a valuable tool for state management in React applications.

Frequently Asked Question

1. What is the useReducer hook in React?

useReducer is a hook in React used for managing complex state logic in functional components. It’s an alternative to useState that provides more control over how state updates are handled, especially for state transitions that depend on the previous state.

2. How does useReducer work?

It takes a reducer function and an initial state as arguments and returns the current state and a dispatch function. The dispatch function is used to dispatch actions to update the state based on the logic defined in the reducer.

3. When should I use useReducer instead of useState?

useReducer is preferable when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. It’s particularly useful for managing state transitions in more predictable and maintainable ways, especially for larger applications.