Let's take a look at the following 2 ways of using useReducer
hook for state management, they both do the same thing: click add button to + 1 and click subtract button to - 1:
- with switch:
const myReducer = (state, action) => {
switch (action.type) {
case 'add':
return {
count: state.count + 1
}
case 'subtract':
return {
count: state.count - 1
}
default:
return state
}
}
const Reducer = () => {
const [state, dispatch] = useReducer(myReducer, { count: 0 });
return (
<>
<button onClick={() => dispatch({ type: 'add' })}>Add</button>
<button onClick={() => dispatch({ type: 'subtract' })}>Subtract</button>
<p>{state.count}</p>
</>
)
}
- without switch
const Reducer2 = () => {
const [state, setState] = useReducer(
(state, newState) => ({ ...state, ...newState }),
{ count: 0 }
);
return (
<>
<button onClick={() => setState({count: state.count + 1})}>Add</button>
<button onClick={() => setState({count: state.count - 1})}>Subtract</button>
<p>{state.count}</p>
</>
)
}
Which one is a better way of managing the state? I prefer 2 because it is simpler, allows us to manage state in a 'class component' way. I don't understand why 1 is needed: it needs a switch statement which is complex; if one wants to add state, a new case is needed. This all seems pretty cumbersome.
EDIT: I know this is a trivial example which has no need to use useReducer
and useState
is better, but what I really want to discuss is that when there are multiple states, which one is better?
Switch statements are typically used in
useReducer
as a remnant from reducers in redux.Your second example is a good way of using an approximation of
this.setState
in a function component, sinceuseState
is only really designed for a single value as there is no shallow merging of the old state and the new. I have extended this to one step further at the end of this answer.As for your question of which is best to manage state in a
useReducer
, it really depends on what you want to use it for and how. You aren't just limited to those two types of things: you can use anything in them. I've had good luck using redux toolkit's createSlice in auseReducer
for a type-safe reducer with Immer to make immutability easier.If you write a reducer case for each part of the state, yes. It is super cumbersome and I would definitely do it a different way. The best way to use the first approach is when you have more complicated situations you need to work with or generic ways to work with more state options.
As written in the React docs:
They are a very powerful addition to function components and allow for an easier way to work with complex logic or values that are connected logically. Whether you use it or not is of course up to you and anything that is done with
useReducer
can be done withuseState
s with varying amount of boilerplate and logic.For a generic way to work with a lot of state properties:
As for slightly more complex logic, here's an example for a data fetch:
A simple one I've had use of is a force update that just increments a value to cause the component to re-render.
I modified your example 2 to add support for the function method of updating the state so it's closer to a full
setState
knockoff usinguseReducer
. I can't think of a decent way to make the callback work (the second parameter inthis.setState
)