paint-brush
Redesigning Reduxby@ShMcK
56,253 reads
56,253 reads

Redesigning Redux

by Shawn McKayFebruary 28th, 2018
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Shouldn’t state management be a solved problem by now? Intuitively, developers seem to know a hidden truth: state management seems harder than it needs to be. In this article, we’ll explore some questions you’ve probably been asking yourself:

People Mentioned

Mention Thumbnail
featured image - Redesigning Redux
Shawn McKay HackerNoon profile picture

Shouldn’t state management be a solved problem by now? Intuitively, developers seem to know a hidden truth: state management seems harder than it needs to be. In this article, we’ll explore some questions you’ve probably been asking yourself:

  • Do you need a library for state management?
  • Is the popularity of Redux deserved? Why or why not?
  • Could we make a better state management solution? If so, how?

Does state management need a library?

Being a front-end developer is not just about moving pixels around; the real art of development is knowing where to store your state. Short answer: it’s complicated, but not that complicated.

Let’s take a look at our options when using a component-based view framework/library like React:

1. Component State

State that exists inside of a single component. In React, think state updated with setState.

2. Relative State

State passed from a parent to a child. In React, think props passed as properties on a child component.

3. Provided State

State held in a root provider, and accessed by a consumer somewhere down the component tree, regardless of proximity. In React, think of the context API.

A lot of state belongs in the view, as it reflects the UI. But what about all the other code that reflects your underlying data and logic?

Putting everything inside of views can lead to a poor separation of concerns: it ties you to a javascript view library, it makes code harder to test, and perhaps the greatest annoyance: you have to continually think and readjust where you store your state.

State management becomes complicated by the fact that designs change and it’s often hard to tell which components will need which state. The most straightforward choice is to just provide all of the state from the root component, at which point you’re probably better off just going with next option.

4. External State

State can be moved outside of your view library. The library can then “connect” using the provider/consumer pattern to keep in sync.

Perhaps the most popular state management library is Redux. Over the past two years, it has grown massively in popularity. So why so much love for a simple library?

Is Redux more performant? No. It gets slightly slower with each new action that must be handled.

Is Redux simpler? Certainly not.

Simple would be pure javascript:

So why isn’t everyone just using global.state = {}?

Why Redux?

Under the hood, Redux really is just the same as TJ’s root object — only wrapped within a pipeline of utilities.

The Redux Store Pipeline

In Redux, you can’t directly modify the state. There is only one way in: dispatch an action into the pipeline that eventually updates the state.

There are two sets of listeners along the pipeline: middleware & subscriptions. Middleware are functions that can listen to actions passed in, enabling tools such as a “logger”, “devtools”, or a “syncWithServer” listener. Subscriptions are the functions used to broadcast these state changes.

Finally, reducers update functions that can break down state changes into smaller, more modular and manageable chunks.

Redux may actually be simpler for development than having a global object as your state.

Think of Redux as a global object with pre/post update hooks, and a simplified way of “reducing” the next state.

But isn’t Redux too complicated?

Yes. There are several undeniable signs of an API in need of improvement; these can be summed up with the following equation:

Consider time_saved to represent the time you may have spent developing your own solution, while time_invested equates to the hours invested in reading documentation, taking tutorials, and researching unfamiliar concepts.

Redux is essentially a simple and small library with a steep learning curve. For every developer that has overcome and benefitted from Redux as a deep dive into functional programming, there has been another potential developer lost and thinking “this isn’t for me, I’m going back to jQuery”.

You don’t need to understand what a “comonad” is to use jQuery, and you shouldn’t necessarily need to comprehend functional composition to handle state management.

The purpose of any library is to make something more complicated seem simple through abstraction.

To be clear my intent is not to troll Dan Abramov. Redux became too popular, too early in its infancy.

  • How do you refactor a library already used by millions of developers?
  • How can you justify publishing breaking changes that effect countless projects around the world?

You can’t. But you can provide amazing support through extensive docs, educational videos and community outreach. Dan Abramov for the win here.

Or perhaps there’s another way.

Redesigning Redux

I would argue that Redux deserves a rewrite. And I come armed with 7 areas that should be improved.

1. Setup

Let’s take a look at a basic setup from the real world Redux example on the left.

Many developers have paused here, after just the first step, staring blankly into the abyss. What’s a thunk? compose? Can a function even do that?

Consider if Redux were based on configuration over composition. Setup might look more like the example on the right.

2. Simplified Reducers

Reducers in Redux could use a switch away from the unnecessarily verbose switch statements we’ve grown used to.

Assuming that a reducer is matching on action type, we can invert the params so that each reducer is a pure function accepting state and an action. Maybe even simpler, we could standardize actions and pass in only state and a payload.

4. Async/Await over Thunks

Thunks are commonly used for creating async actions in Redux. In many ways, the way a thunk works seems more like a clever hack than an officially recommended solution. Follow me here:

  1. You dispatch an action, which is actually a function rather than the expected object.
  2. Thunk middleware checks every action to see if it is a function.
  3. If so, the middleware calls the function and passes in access to some store methods: dispatch and getState.

Really? Is it not bad practice for a simple action to be a dynamically typed as an object, or function, or even a Promise?

Like the example on the right, can’t we just async/await?

5. Two Kinds of Actions

When you think about it, there are really two kinds of actions:

  1. Reducer action: triggers a reducer and changes state.
  2. Effect action: triggers an async action. This might call a Reducer action, but async functions do not directly change any state.

Making a distinction between these two types of actions would be more helpful and less confusing that the above usage with “thunks”.

6. No More Action Types As Variables

Why is it standard practice to treat action creators and reducers differently? Can one exist without the other? Does changing one not effect the other?

Action creators and reducers are two sides of the same coin.

const ACTION_ONE = 'ACTION_ONE' is a redundant side effect of the separation of action creators and reducers. Treat the two as one, and there is no more need for large files of exported type strings.

7. Reducers That Are Action Creators

Grouping the elements of Redux by their use, and you’re likely to come up with a simpler pattern.

It’s possible to automagically determine the action creator from the reducer. After all, in this scenario the reducer can become the action creator.

Use a basic naming convention, and the following is predictable:

  1. If a reducer has a name of “increment”, then the type is “increment”. Even better, let’s namespace it: “count/increment”.
  2. Every action passes data through a “payload” key.

Now from count.increment we can generate the action creator from just the reducer.

Good News: We Can Have A Better Redux

These pain points are the reason we created Rematch.

Rematch is a wrapper around Redux that provides a simpler API, without losing any of the configurability.

Rematch: The Redux Framework

See a complete Rematch example below:

I’ve been using Rematch in production for the past few months. As a testimonial, I’ll say:

I have never spent so little time thinking about state management.

Redux isn’t going away, and shouldn’t. Embrace the simple patterns behind Redux with less of learning curve, less boilerplate and less cognitive overhead.


Try Rematch, see if you don’t love it. And give us a star to let others know you do.