Sitemap

React useReducer vs useState: When to use What?

2 min readJul 13, 2025

--

When building React applications, choosing the right state management hook can make your code cleaner, more maintainable, and scalable. While useState is perfect for simple state updates, useReducer shines when things get more complex.

In this post, we’ll dive deep into both useState and useReducer, understand their use cases, and compare them side-by-side with real-world examples.

🪴 What is useState?

useState is a Hook that lets you add React state to functional components.

const [state, setState] = useState(initialState);

When to Use useState

  • State is primitive or independent (boolean, string, number).
  • You have few state variables.
  • Updates are simple and direct.

🧪 Example: A Counter with useState

import React, { useState } from 'react';

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

return (
<div>
<h2>{count}</h2>
<button onClick={() => setCount(prev => prev + 1)}>Increment</button>
</div>
);
}

What is useReducer?

useReducer is a hook used for complex state logic, similar to how Redux works. It lets you manage state transitions in a more predictable and scalable way.

const [state, dispatch] = useReducer(reducer, initialState);

reducer is a function that receives state and action, and returns a new state.

✅ When to Use useReducer

  • State has multiple sub-values (object/array).
  • You want to encapsulate state logic in one place.
  • The state update logic is complex or interrelated.
  • You’re building a scalable feature (like a form, wizard, cart, etc).

Example: Counter with useReducer

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}

function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);

return (
<div>
<h2>{state.count}</h2>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}

Real-World Use Case: Shopping Cart

Get Hasinur Rahman’s stories in your inbox

Join Medium for free to get updates from this writer.

useState (Not Scalable)

const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
const [discount, setDiscount] = useState(0);

Now you have to keep items, total, and discount in sync across multiple setState calls — messy.

useReducer (Scalable)

const initialState = {
items: [],
total: 0,
discount: 0
};

function reducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
const updatedItems = [...state.items, action.payload];
const newTotal = state.total + action.payload.price;
return {
...state,
items: updatedItems,
total: newTotal
};
case 'APPLY_DISCOUNT':
return {
...state,
discount: action.payload
};
default:
return state;
}
}

Which One Should You Use?

Think of it like this:

  • 🧁 Simple logic, few states? → useState
  • 🎂 Complex, related state? → useReducer

✅ Use useState when:

  • You’re building a simple form
  • You’re toggling a modal
  • You’re managing a simple value like count, text, or isOpen

✅ Use useReducer when:

  • You’re building a multi-field form
  • You’re managing a shopping cart
  • You’re handling complex business logic
  • You want better testability and scalability

--

--