April 17, 2019
Redux has been the standard for state management in React applications for years. However, with the introduction of React Hooks in 16.8, we now have a built-in alternative: useReducer
.
The big question: Can useReducer
replace Redux?
In this post, we’ll compare useReducer
with Redux and go through a simple Todo app example to see when it makes sense to use useReducer
instead of Redux.
useReducer
vs. Redux: What’s the Difference?
At first glance, useReducer
and Redux seem similar because both:
- Use a reducer function to update state.
- Follow an immutable state update pattern.
- Use actions to describe state changes.
But there are key differences:
Feature | useReducer | Redux |
---|---|---|
Built-in? | ✅ Yes (React) | ❌ No (external library) |
Boilerplate | ✅ Minimal | ❌ More setup required |
Global State? | ❌ No (local state only) | ✅ Yes (via Redux store) |
Middleware | ❌ Not supported | ✅ Supports middleware (e.g., Redux Thunk) |
DevTools Support | ❌ No | ✅ Yes (Redux DevTools) |
Best For | Local, component-level state | Complex, global state management |
When to Use useReducer
✔ Small to medium apps with simple state logic
✔ Component-level state that doesn’t need to be shared
✔ Avoiding extra dependencies (lighter bundle size)
When to Use Redux
✔ Large applications with deeply nested state
✔ State shared across multiple components
✔ Middleware requirements (async actions, logging, etc.)
Building a Simple Todo App with useReducer
To see useReducer
in action, let’s build a simple Todo app where we can add and remove tasks.
Step 1: Define the Reducer Function
function todoReducer(state, action) {
switch (action.type) {
case "ADD":
return [...state, { id: Date.now(), text: action.text }];
case "REMOVE":
return state.filter((todo) => todo.id !== action.id);
default:
return state;
}
}
Step 2: Create the Todo App with useReducer
import React, { useReducer, useState } from "react";
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [text, setText] = useState("");
const addTodo = () => {
if (text.trim()) {
dispatch({ type: "ADD", text });
setText("");
}
};
return (
<div>
<h2>Todo List</h2>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button onClick={addTodo}>Add Todo</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<button onClick={() => dispatch({ type: "REMOVE", id: todo.id })}>
Remove
</button>
</li>
))}
</ul>
</div>
);
}
export default TodoApp;
Why Use useReducer
Here Instead of useState
?
🔹 Keeps state logic centralized – Instead of multiple useState
calls, all updates go through a single reducer.
🔹 Easier to scale – If we add more actions (like marking tasks as complete), we only modify the reducer.
🔹 Follows Redux-like patterns – Helps in structuring state updates predictably.
Conclusion: useReducer
or Redux?
React’s useReducer
provides a lightweight alternative to Redux for handling local component state. It’s perfect for medium-sized applications where Redux might feel like overkill.
🚀 Rule of thumb:
- Use
useReducer
when state is local to a component. - Use Redux when state needs to be shared across multiple components.
If your app grows and requires global state, you can still combine useReducer
with React Context API or move to Redux when needed.