Create cleaner, more readable, boilerplate-free reducers applicable to any JavaScript environment

Marko Rusić
Aug 19 · 3 min read
Photo by Ali Yahya on Unsplash

Introduction

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?


Good Practices

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

So 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 useReducer hook:


Conclusion

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.

Better Programming

Advice for programmers.

Marko Rusić

Written by

Full Stack Developer | Focused on JavaScript • React • React Native • TS • Node.js

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade