usereducer and useReducer vs useState

Table of contents
- Q) Why to use useReducerHook ? what beneifits its provides over useState? is there any re-rendering benefits.
- Why Use useReducer Hook?
- Benefits Over useState
- Does useReducer Reduce Re-Renders?
- When to Use useReducer Instead of useState
- Example: useState vs. useReducer
- Key Differences in the Example
- Final Thought
- If state updates are simple, useState is fine. But for complex logic, performance optimizations, and better organization, useReducer is a great choice! π
- How useReducer Batches State Updates to Reduce Re-renders
- Example: Using useState (Multiple Re-renders)
- Example: Using useReducer (Single Re-render)
- Conclusion
- π Example: Using useState with useEffect (Multiple Re-renders)
- β Using useReducer (Better Performance)
- Why is useReducer Better Here?
- Final Thoughts
- dispatch` does not change across renders, meaning child components relying on dispatch wonβt cause unnecessary re-renders.
- Understanding How dispatch Prevents Unnecessary Re-renders in useReducer
- π Problem with useState: setState Changes on Every Render
- π΄ Issue
- β Solution with useReducer: dispatch Stays the Same Across Renders
- π’ Why useReducer is Better Here
- Final Takeaway
- why people say using useReducer with context is more good then using useState with context ?
- π Why useReducer with Context API is Better than useState with Context API?
- π― When to Use useReducer with Context API?
useReducer β alternative of useState hook
for small app β use useState
for larger apps β use useReducer along with contextApi
import "./styles.css";
import { useReducer } from "react";
export default function App() {
// state -> means current state
const reducer = (state, action) => {
console.log("action", action);
console.log("state", state);
if (action.type === "INCREMENT") {
return state + 1;
} else if (action.type === "DECREMENT") {
return state - 1;
}
};
const [count, dispatch] = useReducer(reducer, 0);
console.log("useReducer", useReducer(reducer, 0));
return (
<div className="App">
{count}
<button onClick={() => dispatch({ type: "INCREMENT" })}>
Increment{" "}
</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>
{" "}
Decrement{" "}
</button>
</div>
);
}
Q) Why to use useReducerHook ? what beneifits its provides over useState? is there any re-rendering benefits.
Why Use useReducer
Hook?
The useReducer
hook is an alternative to useState
for managing state in React. It is particularly useful when:
State Logic is Complex β When state transitions depend on the previous state or involve multiple sub-states.
Multiple Related State Updates β When multiple state values need to be updated together (e.g., form handling).
Better Performance β In some cases,
useReducer
can help avoid unnecessary re-renders by batching updates.Easier Debugging & Testing β The reducer function encapsulates state transitions, making it easier to test and maintain.
Benefits Over useState
Feature | useState | useReducer |
Complex State Logic | Not Ideal β State logic is often scattered across multiple setState calls. | Better β Centralized state logic inside a reducer function. |
Multiple State Updates | May cause multiple re-renders. | Can batch updates within a single dispatch call, reducing re-renders. |
Derived State | Requires multiple useEffect or calculations inside the component. | Derived state can be computed inside the reducer. |
Readability & Maintainability | Simple for basic states. | More structured and scalable for complex state logic. |
Performance | Can trigger unnecessary re-renders if not handled properly. | Helps reduce unnecessary re-renders when used correctly. |
Does useReducer
Reduce Re-Renders?
Yes, but not always. Hereβs why:
When using
useState
, updating state always causes a re-render, even if the new state is the same as the old one.With
useReducer
, React re-renders only if the returned state is different from the previous one.If the reducer function returns the same state (i.e.,
return state
), React does not trigger a re-render.dispatch
does not change across renders, meaning child components relying ondispatch
wonβt cause unnecessary re-renders.
When to Use useReducer
Instead of useState
Use useReducer
when:
The state has multiple sub-values that change together (e.g., form state, complex UI state).
State transitions are complex (e.g., state depends on the previous state).
You want to optimize performance and avoid unnecessary re-renders.
You prefer a more structured way of handling state updates.
Use useState
when:
The state is simple (boolean, string, number, or small objects).
Thereβs no complex logic for state transitions.
Example: useState
vs. useReducer
Using useState
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Using useReducer
import { useReducer } from "react";
const initialState = 0;
const reducer = (state: number, action: { type: string }) => {
switch (action.type) {
case "increment":
return state + 1;
default:
return state;
}
};
function Counter() {
const [count, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
</div>
);
}
Key Differences in the Example
useState
: Directly modifies state (setCount(count + 1)
).useReducer
: Uses a centralized state update function (dispatch({ type: "increment" })
).useReducer
keeps state transitions predictable and separate from the UI.
Final Thought
If state updates are simple, useState
is fine. But for complex logic, performance optimizations, and better organization, useReducer
is a great choice! π
Meaning of this
Multiple Related State Updates β When multiple state values need to be updated together (e.g., form handling). ?
How useReducer
Batches State Updates to Reduce Re-renders
One key advantage of useReducer
over useState
is that it can batch multiple state updates within a single dispatch
call, thereby reducing unnecessary re-renders.
Example: Using useState
(Multiple Re-renders)
When using useState
, each setState
call triggers a re-render. If we update multiple state variables in sequence, React will re-render after every update, which may hurt performance.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(0);
const handleIncrement = () => {
setCount(count + 1); // Causes 1st re-render
setDoubleCount((count + 1) * 2); // Causes 2nd re-render
};
return (
<div>
<p>Count: {count}</p>
<p>Double Count: {doubleCount}</p>
<button onClick={handleIncrement}>Increment</button>
</div>
);
}
Issue
When
handleIncrement
is called:setCount(count + 1)
updatescount
, triggering a re-render.Then
setDoubleCount((count + 1) * 2)
updatesdoubleCount
, causing another re-render.
Result: Two re-renders for one user action.
Example: Using useReducer
(Single Re-render)
With useReducer
, we can batch updates in the reducer function, so React will only re-render once after all state changes.
import { useReducer } from "react";
type State = {
count: number;
doubleCount: number;
};
type Action = { type: "increment" };
const initialState: State = { count: 0, doubleCount: 0 };
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "increment":
const newCount = state.count + 1;
return { count: newCount, doubleCount: newCount * 2 }; // Both updates happen together
default:
return state;
}
};
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<p>Double Count: {state.doubleCount}</p>
<button onClick={() => dispatch({ type: "increment" })}>Increment</button>
</div>
);
}
Why is This Better?
Only One Re-render β Both
count
anddoubleCount
are updated together inside the reducer function.State Updates Are Grouped β The state is updated in a structured way rather than through separate function calls.
Better Performance β Reducing unnecessary re-renders can make the app smoother, especially for complex UI.
Conclusion
useState
can cause multiple re-renders if multiple state updates are called in sequence.useReducer
groups all related state updates in a single function, ensuring only one re-render.This is especially useful when managing complex state that depends on multiple values.
Please Explain this one
Derived State*Requires multiple useEffect
or calculations inside the component.Derived state can be computed inside the reducer.
Understanding Derived State: useState
vs. useReducer
Derived state is when one part of the state depends on another. With useState
, you often need to use useEffect
to recalculate it whenever a state value changes. However, useReducer
allows you to compute derived state within the reducer function, avoiding extra re-renders.
π Example: Using useState
with useEffect
(Multiple Re-renders)
Consider a scenario where we track the number of items in a cart and need to compute the total price based on item quantity and price.
import { useState, useEffect } from "react";
function ShoppingCart() {
const [items, setItems] = useState([{ name: "Apple", price: 10, qty: 1 }]);
const [totalPrice, setTotalPrice] = useState(0);
useEffect(() => {
// Recalculate total price whenever items change
const total = items.reduce((acc, item) => acc + item.price * item.qty, 0);
setTotalPrice(total);
}, [items]);
const addItem = () => {
setItems([...items, { name: "Banana", price: 5, qty: 2 }]); // Adds a new item
};
return (
<div>
<h3>Total Price: {totalPrice}</h3>
<button onClick={addItem}>Add Item</button>
</div>
);
}
Problems with useState
+ useEffect
Extra Re-renders: Every time
items
change,useEffect
triggers another state update (setTotalPrice
), causing an additional re-render.Unnecessary Side Effects: The logic to compute
totalPrice
is separate from the state update (setItems
), making it harder to manage.
β
Using useReducer
(Better Performance)
With useReducer
, we calculate the totalPrice
inside the reducer function, avoiding extra re-renders caused by useEffect
.
import { useReducer } from "react";
// Define initial state
const initialState = {
items: [{ name: "Apple", price: 10, qty: 1 }],
totalPrice: 10,
};
// Define actions
type Action = { type: "ADD_ITEM"; item: { name: string; price: number; qty: number } };
// Reducer function
const cartReducer = (state: typeof initialState, action: Action) => {
switch (action.type) {
case "ADD_ITEM":
const updatedItems = [...state.items, action.item];
const newTotal = updatedItems.reduce((acc, item) => acc + item.price * item.qty, 0);
return { items: updatedItems, totalPrice: newTotal };
default:
return state;
}
};
function ShoppingCart() {
const [state, dispatch] = useReducer(cartReducer, initialState);
return (
<div>
<h3>Total Price: {state.totalPrice}</h3>
<button onClick={() => dispatch({ type: "ADD_ITEM", item: { name: "Banana", price: 5, qty: 2 } })}>
Add Item
</button>
</div>
);
}
Why is useReducer
Better Here?
β
No Need for useEffect
β The totalPrice
is derived inside the reducer, so we donβt need extra useEffect
logic.
β
Fewer Re-renders β Both items
and totalPrice
update in a single state update, reducing unnecessary renders.
β
Centralized State Logic β The reducer cleanly handles both state updates and derived state, making the code more maintainable.
Final Thoughts
If you only track individual values,
useState
is fine.If state depends on other state values,
useReducer
is a cleaner and more efficient approach.useReducer
reduces unnecessary re-renders by computing derived state inside the reducer function instead of usinguseEffect
.
dispatch` does not change across renders, meaning child components relying on dispatch
wonβt cause unnecessary re-renders.
Understanding How dispatch
Prevents Unnecessary Re-renders in useReducer
One key optimization of useReducer
over useState
is that the dispatch
function never changes between renders. This helps prevent unnecessary re-renders in child components that depend on dispatch
.
π Problem with useState
: setState
Changes on Every Render
With useState
, the setState
function changes across renders, which can cause unnecessary re-renders in components that receive it as a prop.
Example: useState
Causes Unnecessary Re-renders
import { useState } from "react";
function Parent() {
const [count, setCount] = useState(0);
console.log("Parent re-rendered");
return (
<div>
<h3>Count: {count}</h3>
<Child onClick={() => setCount(count + 1)} />
</div>
);
}
const Child = ({ onClick }: { onClick: () => void }) => {
console.log("Child re-rendered");
return <button onClick={onClick}>Increment</button>;
};
export default Parent;
π΄ Issue
Each time
setCount
updates the state, Parent re-renders.Since
onClick={() => setCount(count + 1)}
creates a new function reference on every render, Child re-renders unnecessarily.
β
Solution with useReducer
: dispatch
Stays the Same Across Renders
In useReducer
, the dispatch
function never changes between renders, preventing unnecessary re-renders in child components.
Example: useReducer
Prevents Unnecessary Re-renders
import { useReducer } from "react";
const reducer = (state: number, action: { type: "increment" }) => {
switch (action.type) {
case "increment":
return state + 1;
default:
return state;
}
};
function Parent() {
const [count, dispatch] = useReducer(reducer, 0);
console.log("Parent re-rendered");
return (
<div>
<h3>Count: {count}</h3>
<Child onClick={() => dispatch({ type: "increment" })} />
</div>
);
}
const Child = ({ onClick }: { onClick: () => void }) => {
console.log("Child re-rendered");
return <button onClick={onClick}>Increment</button>;
};
export default Parent;
π’ Why useReducer
is Better Here
dispatch
never changes between renders, unlikesetState
, which creates a new function reference every time.Even though
Parent
re-renders whencount
updates, thedispatch
function remains the same, preventingChild
from re-rendering.
Final Takeaway
With
useState
, function references change on every render, leading to unnecessary child re-renders.With
useReducer
,dispatch
remains the same, reducing unnecessary re-renders and improving performance.
why people say using useReducer with context is more good then using useState with context ?
Using useReducer
with Context API
is generally preferred over useState
in global state management because of performance optimizations and better state management structure. Letβs break it down.
π Why useReducer
with Context API is Better than useState
with Context API?
1οΈβ£ Prevents Unnecessary Re-renders
With useState
+ Context
, every time the state updates, all components that consume the context re-render, even if they don't need the updated state.
π With useReducer
, we can pass the dispatch
function down to components instead of the state itself, which means components donβt need to re-render when the state updates.
π Problem with useState
+ Context API
import React, { createContext, useState, useContext } from "react";
// Create a Context
const CounterContext = createContext<{ count: number; setCount: React.Dispatch<React.SetStateAction<number>> } | null>(null);
const CounterProvider = ({ children }: { children: React.ReactNode }) => {
const [count, setCount] = useState(0);
return <CounterContext.Provider value={{ count, setCount }}>{children}</CounterContext.Provider>;
};
// Child component that uses the context
const DisplayCounter = () => {
console.log("DisplayCounter Re-rendered");
const { count } = useContext(CounterContext)!;
return <h3>Count: {count}</h3>;
};
// Child component that increments count
const IncrementButton = () => {
console.log("IncrementButton Re-rendered");
const { setCount } = useContext(CounterContext)!;
return <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>;
};
// Main App
const App = () => (
<CounterProvider>
<DisplayCounter />
<IncrementButton />
</CounterProvider>
);
export default App;
π΄ Problem: Every Component Re-renders on State Change
When
setCount
updates the state, bothDisplayCounter
andIncrementButton
re-render, even thoughIncrementButton
does not depend oncount
.This leads to unnecessary re-renders and worsens performance.
β
Solution: useReducer
with Context API (Optimized)
Instead of passing the state and setState
, we pass state
and dispatch
, which does not cause components to re-render unnecessarily.
import React, { createContext, useReducer, useContext } from "react";
// Define the reducer function
const reducer = (state: number, action: { type: "increment" }) => {
switch (action.type) {
case "increment":
return state + 1;
default:
return state;
}
};
// Create a Context
const CounterContext = createContext<{ state: number; dispatch: React.Dispatch<{ type: "increment" }> } | null>(null);
// Context Provider
const CounterProvider = ({ children }: { children: React.ReactNode }) => {
const [state, dispatch] = useReducer(reducer, 0);
return <CounterContext.Provider value={{ state, dispatch }}>{children}</CounterContext.Provider>;
};
// Child component that only reads the count
const DisplayCounter = () => {
console.log("DisplayCounter Re-rendered");
const { state } = useContext(CounterContext)!;
return <h3>Count: {state}</h3>;
};
// Child component that only triggers state change
const IncrementButton = () => {
console.log("IncrementButton Re-rendered");
const { dispatch } = useContext(CounterContext)!;
return <button onClick={() => dispatch({ type: "increment" })}>Increment</button>;
};
// Main App
const App = () => (
<CounterProvider>
<DisplayCounter />
<IncrementButton />
</CounterProvider>
);
export default App;
π’ Why is useReducer
with Context API Better?
β
IncrementButton
does NOT re-render when count
changes, because it only has access to dispatch
(which never changes).
β
Only DisplayCounter
re-renders when count
updates.
β
More structured approach to managing complex state changes (good for bigger applications).
π― When to Use useReducer
with Context API?
β
When managing complex state with multiple related state values.
β
When avoiding unnecessary re-renders is important.
β
When state transitions have multiple actions (e.g., ADD_ITEM
, REMOVE_ITEM
, UPDATE_ITEM
).
Would you like an example with a more complex state, like a shopping cart? π
Subscribe to my newsletter
Read articles from Surya Prakash Singh directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Surya Prakash Singh
Surya Prakash Singh
A full stack developer from India. Html | Css | Javascript | React | Redux | Tailwind | Node | Express | Mongodb | Sql | Nosql | Socket.io