Skip to main content

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.