Reducers in React

Gurinderpal Singh Narang
4 min readMar 8, 2024

--

What actually reducers are?

Reducers in React are like special functions used to state management (manage and organise state updates). When components have lots of state changes happening in different event handlers, it can become confusing. In such cases, reducers help by consolidating all the state update logic into a single function, making it easier to manage and understand.

Reducers need to be pure functions. Being pure means that given the same inputs, a reducer will always produce the same output. In React, reducers run during rendering, and actions are queued until the next render.

Reducers should avoid tasks like sending requests, scheduling timeouts, or performing any side effects that impact things outside the component. Additionally, they should focus on updating objects and arrays without changing them directly (no mutations). What exactly “updating objects and arrays without changing them directly” means? let’s discuss this with an example:

const initialState = {
counter: 0,
items: ['apple', 'banana', 'orange'],
};

// Reducer function
const myReducer = (state, action) => {
switch (action.type) {
case 'INCREMENT_COUNTER':
// return {
// state.counter: state.counter + 1
//}
// This is never prefered, updating the counter without changing the state directly
return {
...state,
counter: state.counter + 1,
};

case 'ADD_ITEM':
// Updating the items array without changing the state directly
return {
...state,
items: [...state.items, action.payload],
};

default:
return state;
}
};

By creating new objects or arrays instead of modifying the existing state directly, we follow the principle of immutability. This helps React efficiently track changes and ensures that unexpected side effects, bugs, or unintended consequences are less likely to occur in your application.

The useReducer hook is similar to the useState hook. useReducer takes two parameters:

  • The first parameter is the reducer function, which specifies how the state should be updated in response to actions.
  • The second parameter is the initial state object, defining the initial values for the state properties.
const [state, dispatch] = useReducer(counterReducer, { count: 0 });

Here counterReducer is the reducer function and { count: 0} is the intial state object.

Now lets discuss this with a simple example, were I build a simple shopping cart component(using useState) with the ability to add and remove items. Here basically three different functions are performed which are “Add”, “Remove” and “Clear”.

import React, { useState } from 'react';

export function ShoppingCart() {
// State for cart items
const [cartItems, setCartItems] = useState([]);

// Dummy product data
const products = [
{ id: 1, name: 'Product A', price: 20 },
{ id: 2, name: 'Product B', price: 30 },
{ id: 3, name: 'Product C', price: 25 },
];

// Event handlers
const handleAddToCart = product => {
setCartItems([...cartItems, product]);
};

const handleRemoveFromCart = item => {
setCartItems(cartItems.filter(cartItem => cartItem.id !== item.id));
};

const handleClearCart = () => {
setCartItems([]);
};

return (
<div>
<h1>Shopping Cart</h1>

{/* Product list */}
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
<button onClick={() => handleAddToCart(product)}>
Add to Cart
</button>
</li>
))}
</ul>

{/* Cart items */}
<h2>Cart Items</h2>
<ul>
{cartItems.map((cartItem, index) => (
<li key={cartItem.id + '-' + index}>
{cartItem.name} - ${cartItem.price}
<button onClick={() => handleRemoveFromCart(cartItem)}>
Remove
</button>
</li>
))}
</ul>

{/* Clear Cart button */}
<button onClick={handleClearCart}>Clear Cart</button>
</div>
);
}

Now let’s convert this using useReducer:

import React, { useReducer } from "react";

const cartReducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
return {
...state,
items: [...state.items, action.payload],
};
case "DELETE_ITEM":
return {
...state,
items: state.items.filter((item) => item.id !== action.payload.id),
};
case "CLEAR_CART":
return {
...state,
items: [],
};
default:
return state;
}
};

const intialCartState = {
items: [],
};

export default function ShoppingCart() {
// State for cart items
const [cartState, dispatcher] = useReducer(cartReducer, intialCartState);

// Dummy product data
const products = [
{ id: 1, name: "Product A", price: 20 },
{ id: 2, name: "Product B", price: 30 },
{ id: 3, name: "Product C", price: 25 },
];

// Event handlers
const handleAddToCart = (product) => {
dispatcher({ type: "ADD_ITEM", payload: product });
};

const handleRemoveFromCart = (product) => {
dispatcher({ type: "DELETE_ITEM", payload: product });
};

const handleClearCart = () => {
dispatcher({ type: "CLEAR_CART" });
};

return (
<div>
<h1>Shopping Cart</h1>

{/* Product list */}
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} - ${product.price}
<button onClick={() => handleAddToCart(product)}>
Add to Cart
</button>
</li>
))}
</ul>

{/* Cart items */}
<h2>Cart Items</h2>
<ul>
{cartState.items.map((cartItem, index) => (
<li key={cartItem.id + "-" + index}>
{cartItem.name} - ${cartItem.price}
<button onClick={() => handleRemoveFromCart(cartItem)}>
Remove
</button>
</li>
))}
</ul>

{/* Clear Cart button */}
<button onClick={handleClearCart}>Clear Cart</button>
</div>
);
}

In this example:

  • The cartReducer function handles actions to add items, remove items, and clear the entire cart.
  • The ShoppingCart component initialises the state using useReducer and renders a list of products with buttons to add them to the cart. It also displays the current cart items with the option to remove items and a button to clear the entire cart.

This example demonstrates how useReducer can be beneficial for managing complex state logic in a more organised and scalable way.

In summary, Reducers play a crucial role in maintaining clean, scalable, and predictable state management in React applications. They are particularly useful in scenarios where state logic becomes more complex or involves multiple interactions.

Thanks for Reading!!!

--

--

No responses yet