useState vs. useReducer: When to Use Each State Management Hook in React

Thomas Sentre
4 min readSep 12, 2023

--

Photo by davisuko on Unsplash

React, the popular JavaScript library for building user interfaces, equips developers with a variety of tools and features to efficiently manage state within their components. Among these tools, two essential hooks stand out: useState and useReducer. These hooks play pivotal roles in the world of React state management, but understanding when to use each one can be the key to writing clean, maintainable code.

Understanding the Basics

React exposes two hooks for managing state: useState and useReducer. Both of these hooks are used to manage state in a component. The difference between the two is that useState is a hook that is better suited to manage a single piece of state, while useReducer is a hook that manages a more complex state.

useState

Let’s start by exploring useState. It’s typically your go-to choice when you need to handle straightforward, single-state variables within a component. It’s simple to use and is designed to manage basic state changes seamlessly. Here’s a quick example of how to use useState:

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

const increment = () => {
setCount(count + 1);
};

return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}

export default Counter;

In this example, we’re using useState to create a count state variable, which starts at 0. When the “Increment” button is clicked, it updates the count state, causing the component to re-render with the updated value.

Now that we’ve explored the use case of useState, it’s essential to understand that useState is not limited to handling simple states only; it’s versatile enough for various scenarios. Here’s an example demonstrating the use of useState in a more complex setting:


import React, { useState } from 'react';

function ShoppingCart() {
const [cart, setCart] = useState([]);

const addToCart = (product) => {
setCart([...cart, product]);
};

return (
<div>
<h2>Shopping Cart</h2>
<ul>
{cart.map((item, index) => (
<li key={index}>{item.name}</li>
))}
</ul>
<button onClick={() => addToCart({ name: 'Product X' })}>Add Product</button>
</div>
);
}

export default ShoppingCart;

In this example, we use useState to manage a shopping cart, which is an array of products. When the “Add Product” button is clicked, it updates the cart state by adding a new product to it.

useReducer

useReducer becomes particularly valuable when dealing with complex state scenarios. It’s the hook of choice for managing complex states, such as those involving multiple actions, interrelated variables, or advanced form management. Let’s see how it can be employed for a more intricate use case:

import React, { useReducer } from 'react';

// Reducer function
const cartReducer = (cart, action) => {
switch (action.type) {
case 'ADD_TO_CART':
return [...cart, action.payload];
case 'REMOVE_FROM_CART':
return cart.filter((product) => product.id !== action.payload.id);
default:
return cart;
}
};

function ShoppingCart() {
const [cart, dispatch] = useReducer(cartReducer, []);

const addToCart = () => {
dispatch({
type: 'ADD_TO_CART',
payload: { id: Math.random(), name: 'Product X' },
});
};

return (
<div>
<h2>Shopping Cart</h2>
<ul>
{cart.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
<button onClick={addToCart}>Add Product</button>
</div>
);
}

export default ShoppingCart;

In this updated example, we use useReducer to manage the shopping cart state. The reducer handles adding and removing items from the cart, offering a more structured approach to managing complex state changes.

When to Choose Which?

Now that we’ve seen examples of both useState and useReducer, let’s discuss when to use each one.

Use useState when:

  • Managing Simple State: If your component’s state is relatively simple and doesn’t involve multiple variables or complex logic, useState is the more straightforward choice.
  • Component-Specific State: When the state you’re managing is specific to a single component and doesn’t need to be shared or synchronized with other components, useState is a good fit.
  • Performance Matters Less: For smaller-scale applications or components where performance optimization isn’t a primary concern, useState is often sufficient.

Use useReducer when:

  • Managing Complex State Logic: If your component’s state involves multiple variables, complex logic, or transitions between different states, useReducer can help you maintain a more structured and maintainable codebase.
  • Sharing State: When you need to share state between multiple components or synchronize state changes across different parts of your application, useReducer provides a centralized state management solution.
  • Optimizing Performance: For larger-scale applications or components where performance optimization is crucial, useReducer can be more efficient, as it allows you to handle state updates more predictably.

Summary

Both useState and useReducer are powerful tools for managing state in React. The key to making the right choice lies in understanding your specific requirements. Use useState for simpler, component-specific state, and turn to useReducer when you need to handle more complex state logic or share state between components. By making informed decisions, you can write cleaner, more maintainable React code and ensure your applications run smoothly.

--

--

Thomas Sentre

Software Developer | Content Creator | Cloud Enthusiast