useReducer, don't useState
February 10, 2019
This blog post assumes you have a decent initial understanding of React Hooks. I highly suggest starting with the ReactJS Docs on them first.
As developers start adopting React Hooks within their applications, many will be
tempted to start with
useState as their state management preferences for local
component state. However, I would like to try an convince you that
is a better way to manage local state.
Lets start of with defining "better" in my premise from above, the definition I will use for this article will be that its:
- Easier to manage larger state shapes
- Easier to reason about by other developers
- Easier to test
So lets break down each of these three points.
Easier to manage larger state shapes
As with most of this blog post, this is mostly my opinion, however because
useState no longer shallowly merges state updates like it does within classes,
using a reducer function gives you the developer more control over the state
As an example of this expresivity that a reducer gives us, we can
to implement an undo/redo state management solution 1:
Using this reducer we can keep track of a stack of states that happen in the future and in the past, allowing the user to undo and redo their actions.
This would be fairly difficult to coordinate using
useState, thats not to say
that its impossible but the benefit of
useReducer is the explicitness of this
pattern. Which leads into the second point.
Easier to reason about by other developers
Probably a topic for another blog post, but there is no such thing as a tech-only problem in web development. Frequently, you will be building features with other developers, that have a wide variety of experience different from your own.
This is mostly a more generic topic that permeates through other topics than
just React Hooks, but the general take-away with the benefit of
useState is it builds on the concepts that many developers learned
working with Redux within React applications2. The concept of dispatching an
action and having your reducer handle the state updating logic will allow these
developers to more easily grasp this method of state management over
One thing to note in this reasoning, is that even if you are building a project all by yourself, you can consider the future you that comes back to work on the project as another engineer.
Easier to test
If there is one general topic that I have seen the most in discussions on the
original Hooks RFC, or the React repo since the 16.8 release, or even on Twitter
its how developers are really confused with how to test Hooks. I think it might
take developers a while to learn how best to test their hooks and components
using hooks, however the beauty of the
useReducer hook, is that all your
business logic for updating the state can exist in a separate function that is
exported separately from your component.
This separation of the state updating logic and rendering logic, allows you to
author tests for the state updating separate from the component rendering tests.
Using our reducer from the above snippet, we can easily test the logic for
undoing and redoing actions simply by calling our
reducer function from with
the test using some mocked state, and an action. We don't even need to
or use react at all within our test!
I don't expect to persuade most developers to only ever
useState, nor do I personally expect to only ever use the
useState, they both have benefits and fallbacks that depend entirely upon
their use. However, I do think that
useReducer when used as a replacement for
complex state management happening within an old class based component or
replacing a react-redux setup can be more maintainable.