React is a component-based JavaScript library widely used for building user interfaces (UI). The two main state management hooks in React are useState and useReducer. In this tutorial, we will take a deep dive into useReducer.
1. What is useReducer?
useReducer is one of React’s built-in hooks used to manage complex state logic. This hook updates the state by calling a specific function whenever there is a state change. While useState is suitable for simple state management, useReducer is better suited for more complex logic or when multiple state changes are needed.
2. Basic Usage of useReducer
The basic way to use useReducer is as follows.
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
As seen in the example above, useReducer uses two arguments:
the reducer function and the initial state. The reducer function takes the current state and action as parameters and returns the new state. The dispatch function updates the state using the action object.
3. Advantages of useReducer
- Complex state management: useReducer is a powerful tool for managing multiple states. It allows for clear delineation of various actions and states.
- Refactoring and scalability: Because the reducer function is maintained independently, it makes refactoring state logic easier.
- Testability: The reducer function is a pure function, making it relatively easy to unit test.
4. Comparison of useReducer and useState
While both useState and useReducer help manage states, each is suited for different situations based on its usage.
Feature | useState | useReducer |
---|---|---|
Simple state | Advantageous | Disadvantageous |
Complex state | Disadvantageous | Advantageous |
State change logic | Disadvantageous | Advantageous |
Separation of states | Disadvantageous | Advantageous |
5. Practical Example: Creating a ToDo List App
Now we will create a simple ToDo list app using useReducer. This app will include features to add and remove tasks.
import React, { useReducer } from 'react';
const initialState = { todos: [] };
function reducer(state, action) {
switch (action.type) {
case 'add':
return { ...state, todos: [...state.todos, action.payload] };
case 'remove':
return { ...state, todos: state.todos.filter((_, index) => index !== action.payload) };
default:
throw new Error();
}
}
function TodoApp() {
const [state, dispatch] = useReducer(reducer, initialState);
const [input, setInput] = React.useState('');
const addTodo = () => {
dispatch({ type: 'add', payload: input });
setInput('');
};
return (
<div>
<h2>ToDo List</h2>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button onClick={addTodo}>Add</button>
<ul>
{state.todos.map((todo, index) => (
<li key={index}>
{todo} <button onClick={() => dispatch({ type: 'remove', payload: index })}>Remove</button>
</li>
))}
</ul>
</div>
);
}
The above TodoApp component provides functionality for users to add and remove tasks. It manages state using useReducer, defining logic to add or delete tasks based on each action (type).
6. Advanced Usage of useReducer
Beyond simple state management, useReducer supports advanced features. Here are some of them:
6.1 Loading Initial State
This is a way to load initial state asynchronously. To do this, we use the useEffect hook.
import React, { useReducer, useEffect } from 'react';
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/todos');
const data = await response.json();
data.forEach(todo => dispatch({ type: 'add', payload: todo }));
};
fetchData();
}, []);
// Remaining app code...
}
6.2 Nested State Management
You can implement structured state management by combining multiple reducers. When used with the context API, global state management can be handled easily.
function mainReducer(state, action) {
return {
counter: counterReducer(state.counter, action),
todos: todoReducer(state.todos, action),
};
}
// State and dispatch are provided with multiple reducers.
const [state, dispatch] = useReducer(mainReducer, initialState);
7. Conclusion
useReducer is a powerful tool for building complex state management logic in React. Especially when multiple state changes are necessary, useReducer can enhance the readability and maintainability of your code. Based on what we learned in this tutorial, try utilizing useReducer in your own React applications.
If you have additional examples you want to practice or questions, please leave a comment. We support your journey in React development!