We're planting a tree for every job application! Click here to learn more

Redesigning Redux

Shawn McKay

19 Dec 2022

6 min read

Redesigning Redux
  • JavaScript

Redesigning Redux

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 try to answer some questions you’ve probably been asking yourself:

  • Do you really 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 really need a library? Being a front-end developer is not just about moving pixels around; the true 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:

    Redesigning Redux# 1.png

    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 constantly think and readjust where you store your state.

    State management is complicated by the fact that designs change and it’s often hard to tell which components will need which state. The simplest 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. In fact, it gets slightly slower with each new action that must be handled.

    Is Redux simpler? Certainly not.

    Simple would be pure JavaScript:

    Redesigning Redux# 2.JPG

    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.

    Redesigning Redux# 4.JPG

    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:

    Redesigning Redux# 7.png

    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.

    Setup

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

    Redesigning Redux# 8.png

    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.

    Simplified Reducers

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

    Redesigning Redux# 9.png

    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.

    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?

    Redesigning Redux# 10.png

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

    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”.

    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?

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

    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.

    Reducers That Are Action Creators

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

    Redesigning Redux# 12.png

    Its 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.
  • Redesigning REDUX# 123.png

    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.

    Redesigning REDUX# 13.png

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

    RR# 14.png

    See a complete Rematch example below:

    RR# 15.png

    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.

    Did you like this article?

    Shawn McKay

    See other articles by Shawn

    Related jobs

    See all

    Title

    The company

    • Remote

    Title

    The company

    • Remote

    Title

    The company

    • Remote

    Title

    The company

    • Remote

    Related articles

    JavaScript Functional Style Made Simple

    JavaScript Functional Style Made Simple

    Daniel Boros

    12 Sep 2021

    JavaScript Functional Style Made Simple

    JavaScript Functional Style Made Simple

    Daniel Boros

    12 Sep 2021

    WorksHub

    CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
    hello@works-hub.com

    Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

    108 E 16th Street, New York, NY 10003

    Subscribe to our newsletter

    Join over 111,000 others and get access to exclusive content, job opportunities and more!

    © 2024 WorksHub

    Privacy PolicyDeveloped by WorksHub