The useReducer hook is an alternative to the useState hook and is preferable when you have complex state logic or when your next state depends on your previous state.
The useReducer hook accepts a reducer type (state, action) => newState
and returns a state object paired with a dispatch method much like Redux.
Now the official useReducer documentation will show you how to define a reducer that accepts actions you will call with a dispatch method.
That is a good use case but I have another example of using useReducer
to provide partial updates to state which I find covers the rest of my use cases.
useReducer with actions and a dispatch method
The default example for useReducer
is set up for you to create a reducer function and provide it with an action. The action provided should have a type and some value to update the state.
To strongly type this feature with TypeScript we can create an enum with all of our possible action types as well as create an interface for the action.
Below is a fairly contrived example but shows the most basic example possible.
// An enum with all the types of actions to use in our reducer
enum CountActionKind {
INCREASE = 'INCREASE',
DECREASE = 'DECREASE',
}
// An interface for our actions
interface CountAction {
type: CountActionKind;
payload: number;
}
// An interface for our state
interface CountState {
count: number;
}
// Our reducer function that uses a switch statement to handle our actions
function counterReducer(state: CountState, action: CountAction) {
const { type, payload } = action;
switch (type) {
case CountActionKind.INCREASE:
return {
...state,
value: state.count + payload,
};
case CountActionKind.DECREASE:
return {
...state,
value: state.count - payload,
};
default:
return state;
}
}
// An example of using the `useReducer` hooks with our reducer function and an initial state
const Counter: FunctionComponent = () => {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
Count: {state.count}
{/* Calling our actions on button click */}
<button
onClick={() => dispatch({ type: CountActionKind.INCREASE, payload: 5 })}
>
-
</button>
<button onClick={() => dispatch({ type: CountActionKind.DECREASE, payload: 5 })}>+</button>
</div>
);
};
useReducer using the Partial type for state updates
The next way of using useReducer
is best used when you have a state object where you need to change some values inside of it directly rather than dispatching an action to handle state changes.
This way also has fewer types but does expose the state to be changed directly so take that into account when using this method.
// Interface for our state
interface LoadingState {
loaded: boolean;
loading: boolean;
error: Error | null;
}
// The example component that use the `useReducer` hook with our state
const LoadingComponent: FunctionComponent = () => {
/**
See here that we are using `newState: Partial<LoadingState>` in our reducer
so we can provide only the properties that are updated on our state
*/
const [state, setState] = useReducer(
(state: LoadingState, newState: Partial<LoadingState>) => ({
...state,
...newState,
}),
{
loaded: false,
loading: false,
error: null,
}
);
useEffect(() => {
// And here we provide only properties that are updating on the state
setState({ loading: true });
setState({ loading: false, loaded: true });
setState({ loading: false, loaded: true, error: new Error() });
}, []);
if (state.loading) {
return <p>Loading</p>;
}
return <p>{state}</p>;
};
Conclusion
These are my two methods so check out the other posts below for other methods. It's beneficial to read the wide range of ways to accomplish the same thing.
https://www.newline.co/@bespoyasov/how-to-use-usereducer-with-typescript--3918a332
https://www.sumologic.com/blog/react-hook-typescript/
https://www.benmvp.com/blog/type-checking-react-usereducer-typescript/
🌟🌟🌟
My brother and I write all the time about React, TypeScript, R, Data Science, you name it on our blog so check it out:
🌟🌟🌟
Top comments (11)
Nice article, helps me to understand useReducer with TypeScript.
lines with:
value: state.count + payload
value: state.count - payload
should be changed to:
count: state.count + payload
count: state.count - payload
and a few edit in + and - buttons.
I believe
useReducer
is meant for cases where the transitions between the states (actions) are required to understand the underlying logic. In any other case, better useuseState
.there's nothing
useReducer
can do thatuseState
can't, and vice versa. the choice is just about clarity.Exactly. And the choice should depend on whether the transitions are more meaningful to explain the task than the state itself.
On your second use case,
useReducer using the Partial type for state updates
, I think good 'oluseState
is just enough.This is an over simplistic example. What if the payload can span multiple types but depend on the action being called?
I appreciated this article, so tyvm! I do think you should implement the changes that @ratrateroo has called out though or at least reply with an explanation of why they're not valid.
Thank you
There is a typo in your first example
value: state.count + payload,
Should be :
count: state.count + payload,
Between useReducer and useState , which is better for nested objects or arrays?
UseReducer is better for that case