useReducer()
useReducer
Hook is similar to the useState
Hook.
- It allows for custom state logic.
- Manages complex state logic.
- Uses a reducer function for state transitions.
- It solve to reduce the Redundant Logic in the component.
Syntax
const [state, dispatch] = useReducer(reducer, initialState);
reducer()
function in the useReducer is accept the state, type and they return the new State.
const reducer = (state, action) => {
switch (action.type) {
case "COMPLETE":
return state.map((todo) => {
if (todo.id === action.id) {
return { ...todo, complete: !todo.complete };
} else {
return todo;
}
});
default:
return state;
}
};
Problem with useState()
You have a counter with additional functionalities: increment, decrement, reset, and set to a specific value. Using useState
would require multiple functions and could become hard to manage.
Using useState()
:
problem using useState() hooks
- Multiple Functions: Each state change requires a separate function.
- Redundant Logic: Similar state updates lead to repetitive code.
- Maintenance: Adding new functionalities or modifying existing ones can be difficult.
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(0);
const setToValue = (value) => setCount(value);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
<button onClick={() => setToValue(5)}>Set to 5</button>
</div>
);
}
export default Counter;
Solution with useReducer()
import React, { useReducer } from 'react';
const initialState = [];
function reducer(state, action) {
switch (action.type) {
case 'add':
return [...state, { text: action.payload, completed: false }];
case 'remove':
return state.filter((_, index) => index !== action.payload);
case 'toggle':
return state.map((todo, index) => index === action.payload ? { ...todo, completed: !todo.completed } : todo);
default:
throw new Error();
}
}
function TodoList() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<button onClick={() => dispatch({ type: 'add', payload: 'New Todo' })}>Add Todo</button>
<ul>
{state.map((todo, index) => (
<li key={index}>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>{todo.text}</span>
<button onClick={() => dispatch({ type: 'toggle', payload: index })}>Toggle</button>
<button onClick={() => dispatch({ type: 'remove', payload: index })}>Remove</button>
</li>
))}
</ul>
</div>
);
}
export default TodoList;
How useReducer Solves the Problem?
- Centralized State Logic: All todo operations are handled in the reducer function.
- Simplified Component: The component dispatches actions instead of directly updating state.
- Clear State Transitions: State transitions are defined clearly in the reducer, making the logic easier to follow and maintain.