useState
is a nice and simple hook for managing state in functional components (as opposed to setState
in class components). But as the complexities of your application grow, you’ll find yourself needing something a little more powerful.
Say “hello” to useReducer
!
What is useReducer
, and what problems does it solve?
useReducer
is a React hook used to manage state in your application. It’s comparable to useState
, but is designed for more complex requirements and so works in a slightly different way (more on that later).
useState
reaches its limit of being a simple, easy-to-use state management hook when you get into the arena of interdependent states or needing multiple sub-values. It just isn’t great for these use cases.
In the case of interdependent state, say you want to set one piece of state based on the value of another piece. With useState
there’s a risk (albeit a small one) that the setting function would be based on old state because the state update wasn’t processed in time. This is a prime opportunity for useReducer
.
The other opportunity to utilise the benefits of useReducer
is when you have multiple pieces of state that are related. For example, a bunch of state related to the same <form>
. We can use the useReducer
hook to essentially merge and group together those separate pieces of state, resulting in more readable code.
In summary:
useState
is a good starting point for state management, it’s great for:- Independent pieces of data
- Simple updates to state
useReducer
is a more complex state management solution which is great for:- More powerful state-setting logic
- Grouping and updating related data / state
How does useReducer
work?
useReducer
comes in two main parts; the hook creation/setup itself and its reducer function.
Setting up the hook
Inside your component, drop in useReducer()
where you manage your other state. As you can see below, it looks a lot like useState()
.
const [state, dispatchFn] = useReducer(reducerFn, initialState, initFn);
Let’s break it down:
state
– this is the state that you’d use across your component (in your functions and in rendering to the UI)dispatchFn
– this is a function that can be used to trigger an update of the statereducerFn
– this is a custom function that you can define outside of your component which takes the latest state and returns the new, updated stateinitialState
– this is, well… the initial stateinitFn
– this allows you to set the initial state programmatically with a function
The reducer function
We looked at reducerFn
above, but we can see what it looks like below.
(prevState, action) => newState
So, as we said, the reducer function takes the previous state (prevState
). It also takes the new data being passed to it in action
. Finally, it must return the new state (newState
) to be stored in the useReducer()
hook from before.
Example of useReducer
usage
Here’s a simple example that is far better at showing how to use it than it is at showing what you might actually end up using it for. It shows how you can use one state setting function – inputReducer()
– from calling dispatchChange()
on the event to dynamically set the appropriate <input>
-connected state.
import React, { Fragment, useReducer } from "react";
const inputReducer = (prevState, action) => {
const inputName = `input${action.id}`;
return { ...prevState, [inputName]: action.value };
};
const ReducerExample = () => {
const [state, dispatchChange] = useReducer(inputReducer, {
input1: "",
input2: "",
input3: "",
});
const handleChange = (event) => {
event.preventDefault();
dispatchChange({ id: event.target.dataset.id, value: event.target.value });
};
return (
<Fragment>
<h2>Reducer Example</h2>
<div>
<input type="text" onChange={handleChange} data-id="1" />
<input type="text" onChange={handleChange} data-id="2" />
<input type="text" onChange={handleChange} data-id="3" />
</div>
<p>{`${state.input1}${state.input2}${state.input3}`}</p>
</Fragment>
);
};
export default ReducerExample;