Using typed action creators with react's useReducer hook
With the advent of react hooks we now have very fast access to CQRS in our UI - dispatch actions (Command) and create a new read state (Query) for the UI based on the previous state and the action. If you follow e.g. Dan Abramov on twitter you will have seen plenty of examples à la:
In the world of Typescript the type of useReducer allows us to type the State
as well as the type of the Action
which is usually a union of all possible actions. As a kind of baseline I’ll the canonical react app, the counter :):
Action
is a type that takes away a little bit of the tediousness of defining the typical actions that you’d write in your application. Once you use State
and Action
in your reducer definition you’ll get type-safety around the state and the encountered actions. In addition, you get an exhaustive matching if you either nail down the return type of the reducer to the State or use something like the defaultGuard
which will produce a compile error if there are possible actions that’d end up in the default case.
You can then use the reducer like such:
An IDE like VS Code will have your code completion demands covered quite nicely. All, in all, a nice experience with the compiler helping you along.
If you prefer to dispatch actions with action creators and want to have some help with that, you can also use libraries that are usually used in combination with redux - since the dispatch and the reducer function from useReducer
are kind of the same thing™, such libraries that help defining action creators and reducers should work with useReducer
, too, right?
Personally I know of two libraries that fit the bill, typesafe-actions and unionize.
Using typesafe-actions
Let’s define the action creators and the reducer (state stays as before):
The compile-time assurances around state and actions stay preserved.
Let’s improve the experience and create a hook that instead of the dispatch
will provide the action creators already bound to the dispatcher.
The function bindActionCreator
is pretty much taken from bindActionCreators
which can be found in the redux codebase. It constructs a function that passes the arguments provided to the action creator and dispatches the result.
The Object.keys(actions).reduce
code constructs an object that has the same shape like the actions passed in but instead provides functions bound to the dispatch
function returned by useReducer
. All of this is wrapped in a useMemo
hook since dispatch and actions will not change throughout the lifetime of the component using this hook.
How, is this used then? Based on the previous code that introduced the actions and the reducer, we can write the following component:
Using unionize
The major differences in usage in this particular scenario are the definition of the action creators and the reducer. Here they are for completeness’ sake.
As you can see, there are neat ways to leverage the redux ecosystem for the new react hooks world. The useReducer
hook in Typescript can give you the same assurances with regard to type safety as redux does and with the types being quite similar you can build upon those types with action creator libraries available out there.