The React useReducer hook is a powerful tool for managing complex state logic in your applications. While useState is great for simple state management, useReducer shines when you need to handle more intricate state transitions. In this guide, we’ll explore everything you need to know about the useReducer hook, from basic usage to advanced techniques like handling async actions and cleanup.
What is the useReducer Hook?
The useReducer hook is a function that allows you to manage state in a more predictable way, especially when dealing with complex state logic. It takes a reducer function and an initial state as arguments and returns the current state and a dispatch function.
const [state, dispatch] = useReducer(reducer, initialState);
Basic Example
Let’s start with a simple example to illustrate how useReducer works.
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 (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</>
);
}
In this example, we have a simple counter that increments and decrements based on the action type.
Handling Async Actions with useReducer
One of the more advanced use cases for useReducer is handling asynchronous actions, such as fetching data from an API.
Example: Fetching Data with useReducer
const initialState = { loading: false, data: null, error: null };
function reducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true };
case 'FETCH_SUCCESS':
return { ...state, loading: false, data: action.payload };
case 'FETCH_FAILURE':
return { ...state, loading: false, error: action.payload };
default:
throw new Error();
}
}
async function fetchData(dispatch) {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_FAILURE', payload: error });
}
}
function DataFetcher() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
fetchData(dispatch);
}, []);
if (state.loading) return <div>Loading...</div>;
if (state.error) return <div>Error: {state.error.message}</div>;
return <div>Data: {JSON.stringify(state.data)}</div>;
}
In this example, we use useReducer to manage the state of an asynchronous data fetch. The reducer handles different stages of the fetch operation, including loading, success, and failure.
Cleanup with useReducer
When dealing with side effects, such as subscriptions or timers, it’s important to clean up to avoid memory leaks. useReducer can be used in conjunction with useEffect to manage cleanup.
Example: Cleanup with useReducer
const initialState = { time: 0 };
function reducer(state, action) {
switch (action.type) {
case 'TICK':
return { time: state.time + 1 };
default:
throw new Error();
}
}
function Timer() {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const interval = setInterval(() => {
dispatch({ type: 'TICK' });
}, 1000);
return () => clearInterval(interval);
}, []);
return <div>Time: {state.time}</div>;
}
In this example, we use useReducer to manage a timer. The useEffect hook sets up an interval and cleans it up when the component unmounts.
useReducer vs useState
While useState is simpler and more straightforward, useReducer offers more control and is better suited for complex state logic. Here’s a quick comparison:
- useState: Best for simple state management.
- useReducer: Ideal for complex state transitions and logic.
Reading Further
- React Design Patterns
- useTransition Hook in React
- useDeferredValue Hook in React
- useSyncExternalStore Hook in React
- React useCallback Hook
- React useMemo vs Memo
FAQs
What is the useReducer hook in React?
The useReducer hook is a function that allows you to manage state in a more predictable way, especially when dealing with complex state logic. It takes a reducer function and an initial state as arguments and returns the current state and a dispatch function.
How do you handle async actions with useReducer?
You can handle async actions with useReducer by dispatching actions at different stages of the async operation (e.g., start, success, failure). This allows you to manage the state of the async operation in a predictable way.
How do you clean up with useReducer?
You can clean up with useReducer by using the useEffect hook to set up and clean up side effects, such as subscriptions or timers. The cleanup function in useEffect ensures that resources are properly released when the component unmounts.
When should I use useReducer instead of useState?
You should use useReducer instead of useState when you need to manage complex state logic or when the state transitions are not straightforward. useReducer offers more control and is better suited for these scenarios.
Conclusion
The React useReducer hook is a versatile tool for managing state in your applications. Whether you’re handling complex state logic, async actions, or cleanup, useReducer provides a predictable and powerful way to manage state. By mastering useReducer, you can write more maintainable and scalable React applications.