So, what is a reducer?
It’s a pure function that takes current state and action and returns a new state based on them. That’s it!
The idea of reducers was greatly popularized in the React ecosystem by Redux library, and nowadays it’s pretty much the standard way of thinking about application state.
In this piece, we’ll examine the famous Counter app example and it’s reducer to see what can we do to make it nice and tidy. In the end, we’ll see how easy it is to adopt that knowledge in the React app.
This is our app:
It contains one piece of state, current count and two actions, increment and decrement. The typical reducer may look like this:
What is wrong with this approach, and how can we improve it?
First of all, our action types (increment/decrement) are hard-coded strings. Having those is genuinely a bad idea because it can easily lead to all kinds of bugs and typos, it makes refactoring a lot harder, etc.
Second, our default case returns the same state. Why is this bad? If our reducer gets called with an unknown action, it means that something is wrong and we should be notified about that.
The solution to these issues could look like this:
Reducer Without Switch/Case
Another very useful improvement to our reducer can be made by getting rid of the switch/case statement. That way we can have more readable, testable, and elegant code.
Here’s the example:
Now, instead of manually creating our reducers every time, we can make
createReducer helper function to handle that for us. It should take
handlers object, and
initialState as arguments, and produce reducer as the return value.
Luckily, we already did all heavy lifting in the previous example, so our code should now look like this:
Automatically Merge State
createReducer is now responsible for our reducer creation, and in that process, we can do even more. One particularly nice feature would be merging of current and next state. That way we don’t have to manually merge states in all of our handler functions. Instead, we can only return the state changes that we want to apply.
Merging states is not particularly important for our Counter app since it only has one piece of state. But in a real-world scenario, there is almost always a need for merging a current state to the next one. If we don’t, we might lose parts of our state, and so change the structure of it, which can lead to unexpected behaviors and bugs.
The simplest way to accomplish merge would be to use a spread operator, but since that only merges the first level of properties it’s not a good fit for our use case. We want to support deeply nested properties as well. For that, we can use Lodash’s merge function, which performs deep merges in an efficient manner. Here’s what our code should look like with that adjustment:
How To Apply in a React Application
Here’s a working example of a React app that utilizes these techniques to make a nice, clean reducer and apply it using
With simple abstraction like
createReducer function, we can make our lives much easier and write simple, readable, but yet powerful reducers.
Thank you for reading. If you have any questions or feedback, let me know in the comments.