A more convenient approach to Redux

Introducing Redux Updaters

Redux is great. It provides a way to keep state centralized throughout your entire app, making it easier for multiple components to work with the same data and to keep them in sync. No hassle. The single store can be used to initialize your app with an entire state that was previously persisted or rendered on the server. It also comes with handy tooling, allowing you to track state changes while the program runs and even lets you step back in time. It’s easy to test and debug. Amazing. The days of writing hard to maintain, spaghetti-driven, overly complex single-page applications are over. Especially in combination with React’s one-way data flow, Redux has greatly improved the quality of our codebases at Oberon. And as you are reading this, you’ve probably had a similar experience. Hopefully, you have noticed how much easier it has become to solve state management problems — or you don’t even see them as problems anymore. Then after using Redux for a while, perhaps you too started to wonder why somewhere along the way it all started to feel like a bit of a hassle after all — looking at your project’s codebase and noticing just how many lines of code you actually had to write to add a bit of Redux state management to your app. Or perhaps you haven’t really thought about it much, but the idea of reducing the codebase of your next project by completely removing the need to write reducers, actions, and action creators and still have the full benefit of Redux, might pique your interest.

The idea

In computer science, we always build things level upon level. At the lowest level, we have machine code, created by a compiler written in a low-level programming language, that translates code written in a higher-level language to CPU instructions. There are many levels above that, each solving a particular type of problem, making life easier for the level above. Somewhere at the top, we have our beautiful and relatively understandable front-end JavaScript code, running on top of the browser, often even one level above some kind of framework. As I see it, Redux sits somewhere on the framework level, taking away the problems of state management to make life easier when trying to create a JavaScript app. But something’s not quite right there. Redux is mostly an architecture, and writing code to create actions and reducers feels like writing a program in C when I should be using Java (or insert your latest cool high-level language here). There are two main reasons why it feels this way:

  • There are strict rules you have to follow when implementing actions and reducers. Forgetting these rules would have you easily create a mess of inconsistent action structures and state mutating reducers. And there is nothing in place to warn you about it or keep you from doing it (bad news!).
  • You will end up writing a lot of repetitive code. Most actions (should) look very similar and even the reducers seem to do the same tricks again and again.

What we need is a level on top of Redux. Something that provides a simple API to using Redux and that takes care of these problems for us. A few solutions have already emerged, such as redux-actions and redux-toolkit. They focus mainly on reducing the amount of repetitive “boilerplate” code you’ll need to write. Stricter code will result as a side-effect and redux-starter-kit even has a built-in mechanism that prevents you from mutating the state. Nice!

But we’re still dispatching actions and creating reducers to handle these actions. When developing applications in React, we work on components — we reason from the perspective of components and data that the component needs. We interpret the data as the state of the component, along with additional state properties that may result from user interactions. At a certain point, you will notice that you will need to share some state with other components. A simple example would be a menu button. Say you have a MenuButton component that is dedicated to toggling a menu somewhere else in the app. There is no direct connection between the MenuButton and the Menu component, so they will need to have some shared state, let’s call it ‘isOpen’.

So you’re working on the MenuButton and come up with this isOpen property you want to communicate to the Menu component. Implementing the action/reducer pattern for such a simple task seems tedious. In fact, for most state updates, if not all, the Redux pattern is more complicated than it should be. I get it though. Redux is a great improvement on patterns such as Flux, that implement a pub/sub-like pattern to communicate state updates between components. But I wanted to take this a step further. I want to be able to just update the isOpen property in the shared state from my MenuButton component. I investigated how this would be possible and concluded that in the end, most reducers edit state properties in just a few basic ways. These are mostly simple operations such as directly updating a property, toggling a boolean, incrementing a number, adding a value to an array, etc.. Just basic JavaScript operations. Essentially, most reducers do the same things (or could do the same things) and you could, therefore, create only one reducer and a fixed set of actions that do all of this for you — leaving it up to the components to decide how and when the state gets updated. This idea resulted in the redux-updaters package.

The result

The redux-updaters package consists of one reducer and a set of updater functions that create Redux actions for you to dispatch. First, you will need to add the reducer to your store.

The reducer

import { combineReducers } from 'redux';
import { createReducer } from 'redux-updaters';
import defaultState from './defaultState';
const reducer = createReducer(defaultState);
const rootReducer = combineReducers({app: reducer});

Two things to notice here:

  • A default state is given to the reducer. The default state defines your state structure and the default values. It is where you will add your properties that you want to store in the Redux state.
  • The reducer is added to the root reducer under the key “app”. This means all your state properties will be stored under state.app. This is optional; if you want to manage your entire state using redux-updaters, then you can simply use this reducer as your root reducer. Adding a root key, however, is recommended to keep your app flexible. It allows you to add another library that operates on a part of your state on a separate key, should you ever need that. For example, we might also want to add react-api-data, which has its own reducer to add to the store.

That’s it for the reducer part. You’re done writing all your reducer code now for the rest of your app! Simply create your store with this root reducer and you’re all set up. Let’s have a look at the defaultState.

The default state

So the default state defines your initial state. Generally, you will start with just an object {} and add properties with default values when you need them. For example, in our earlier example of adding an isOpen property for the menu button, we could simply add it as a property to the default state, but let’s add it under a separate menu key for the sake of clarity:

const defaultState = {
menu: {
isOpen: false,
},
};

Now, let’s use an updater to update this value.

The updaters

Updaters are nothing more than action creators that create specific actions for the reducer to handle. This means you need to dispatch the return value. Redux-updaters comes with a set of updaters, with update at the core, allowing you to update a state value the way you want to. The rest is there to make life easier. For example, we can use the toggle updater to toggle a boolean, which comes in handy here. All updaters need to know which property to update, so we need to provide the path to the property to update:

import { toggle } from 'redux-updaters';// somewhere in your code with access to dispatch
dispatch(toggle('menu.isOpen'));

That’s it! The state will now be updated, the value of isOpen will flip from false to true. The dispatched action will be visible in Redux dev tools, so state debugging and time travel will work as usual. There are plenty of updaters available for all basic actions, and if you need to do something more advanced, you can always calculate a result value yourself and update the state using update.

Check out the docs and source code on https://github.com/oberonamsterdam/redux-updaters

Oberon

We make the online world beautiful, easy and insightful

Thanks to Gert Braun and Kelly de Haan

Richard van Willegen

Written by

Developer at Oberon, writing about front-end stuff.

Oberon

Oberon

We make the online world beautiful, easy and insightful

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