React useReducer (Advanced State Management)
The useReducer hook in React is an advanced alternative to useState for managing complex state logic. It is useful when the state depends on previous values or when handling multiple related state changes.
When to Use useReducer?
✅ When state logic is complex and involves multiple sub-values.
✅ When the next state depends on the previous state.
✅ When managing state transitions in an organized way.
✅ When using Redux-like patterns without installing Redux.
Basic Syntax of useReducer
useReducer takes two arguments: a reducer function and an initial state.
const [state, dispatch] = useReducer(reducer, initialState);
✔️ state: Holds the current state.
✔️ dispatch: Function used to trigger actions.
✔️ reducer: A function that determines state updates based on the action.
✔️ initialState: The starting state.
🎯 Example: Simple Counter with useReducer
React uses something called a Virtual DOM (Document Object Model). Normally, when you change something on a webpage, the whole page refreshes. But React updates only the changed parts, making websites faster and smoother.
👉 Step 1: Define the Reducer Function
A reducer function takes the current state and an action to return a new state.
function counterReducer(state, action) {
switch (action.type) {
case “increment”:
return { count: state.count + 1 };
case “decrement”:
return { count: state.count – 1 };
case “reset”:
return { count: 0 };
default:
return state;
}
}
👉 Step 2: Implement useReducer in a Component
Use useReducer inside a functional component to manage state updates.
import React, { useReducer } from “react”;
function Counter() {
const initialState = { count: 0 };
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div style={{ textAlign: “center”, padding: “20px” }}>
<h1>Count: {state.count}</h1>
<button onClick={() => dispatch({ type: “increment” })}>Increase</button>
<button onClick={() => dispatch({ type: “decrement” })}>Decrease</button>
<button onClick={() => dispatch({ type: “reset” })}>Reset</button>
</div>
);
}
export default Counter;
📝 How It Works:
✔️ useReducer initializes state with { count: 0 }.
✔️ The dispatch function triggers actions.
✔️ The reducer function updates the state based on the action type.
Handling Complex State with useReducer
useReducer is great for managing complex state logic in React apps. Below is an example of how to create a To-Do List using useReducer.
👉 Step 1: Define the Reducer Function
The reducer function takes state and an action, and updates the state based on the action type.
function todoReducer(state, action) {
switch (action.type) {
case “add”:
return […state, { id: Date.now(), text: action.payload }];
case “remove”:
return state.filter((todo) => todo.id !== action.payload);
default:
return state;
}
}
👉 Step 2: Implement useReducer in a Component
We initialize the todos state with an empty array and use dispatch to manage state changes.
import React, { useReducer, useState } from “react”;
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const [text, setText] = useState(“”);
const handleAdd = () => {
if (text.trim()) {
dispatch({ type: “add”, payload: text });
setText(“”);
}
};
return (
<div style={{ textAlign: “center”, padding: “20px” }}>
<h2>Todo List</h2>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder=”Enter a task”
/>
<button onClick={handleAdd}>Add Todo</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<button onClick={() => dispatch({ type: “remove”, payload: todo.id })}>
Remove
</button>
</li>
))}
</ul>
</div>
);
}
export default TodoApp;
📝 How It Works:
✔️ Initial State: Empty array []
✔️ Adding a Task: dispatch({ type: “add”, payload: text }) adds a new todo.
✔️ Removing a Task: dispatch({ type: “remove”, payload: todo.id }) filters out the todo.
✔️ Controlled Input: useState manages the input field.
📌 Why Use useReducer Here?
✅ The state logic is more structured.
✅ Helps manage multiple actions cleanly.
✅ Avoids complex nested useState updates.
Comparing useReducer vs useState
Feature | useState | useReducer |
---|---|---|
Simplicity | ✅ Simple | 🔹 Complex |
Best for | Small state updates | Complex state logic |
State Transition | Implicit | Explicit (action-based) |
Performance | 🔹 Good | ✅ Better for complex updates |
Optimizing Performance with useReducer
✅ Use the lazy initialization feature to optimize performance:
const [state, dispatch] = useReducer(reducer, initialArg, initFunction);
📝 Parameters Explanation
✔️ reducer → A function that defines how the state changes based on dispatched actions.
✔️ initialArg → The initial state or an argument that can be processed by initFunction.
✔️ initFunction (Optional) → A function that lazy initializes the state.
📌 Memoize the reducer using useCallback to prevent unnecessary re-renders.
Conclusion
✅ useReducer is best for managing complex state logic.
✅ It helps structure state transitions in an organized and scalable way.
✅ It works well with Redux-like patterns without needing external libraries.
✅ Use it when multiple state updates depend on previous values.