By some distance, the most widely used state management library in the React Universe is Redux. It has everything going for it; great documentation, active community, fantastic core team and great adoption. Perhaps more importantly, Redux design patterns allow clear separation of concerns. That makes it a natural choice in larger organisations, where being able to assess and distribute work predictably is more important than conciseness or cleverness.
My team did just that; and while we mostly succeeded and love Redux as a framework, we found canonical Redux design patterns tedious even for a team prepared to repeat code. Let me be clear, we’re not miffed with Redux (at all), but it would be useful to have a discussion around patterns and practices. The objective of this article is to offer constructive criticism and explore options.
What’s wrong with Redux?
- Repetitive code in multiple places. You’d see action names repeated inside Containers and Components, and within Action Creators and Reducers. There are way too many files in a typical application.
- Non-locality of Business Logic. We haven’t seen any benefit in splitting state management code into actions and reducers even in medium-sized projects. Being able to see things within the same viewport is a productivity booster.
- Reducers. What you want to achieve here is to reversibly change the state atom. There are many ways to do this, but the mechanism that Redux prefers seems unnecessarily elaborate, while expanding the vocabulary that a developer needs to be familiar with before becoming productive.
- Async isn’t built-in, and on the web most things are async. Redux needs middleware like redux-thunk to handle async actions. Although the popular default, redux-thunk doesn’t make synchronizing actions easy. Running an action after another has completed isn’t possible with linear code. As we’ll show later, this problem can be eliminated by adopting simpler patterns in Redux.
How do we solve this?
Let’s start with a clean slate.
Here are the three principles of Redux:
- Single source of truth
- State is read-only
- Changes are made with pure functions
In addition to these excellent goals, what else do we really want to achieve?
Just one thing. We want to separate Layout and Business Logic.
The most straight-forward way to do this would be to have only two types of files. Components (layout) and Actions (business logic and state management). Components will import Actions directly and invoke them like a normal function, and not Dispatch them. Actions will fetch data if needed and reversibly modify the store singleton.
That’s it. Everything else is too much work for too little gain. Let’s see how this can be done. You can find the code used in the following discussion at https://github.com/jeswin/redux/blob/master/examples/redux-lite.
Just two directories: Actions and Components
Components Import Actions. There is no Dispatch.
Actions directly update the Store. Don’t write a Reducer.
Under the hood, it’s still Redux
Under the hood, we’re still using a reducer. A call to updateState() creates an action with a boolean property __replaceState set to true. A predefined reducer looks out for these actions and replaces the current state atom with the new state.
All of this is invisible to application code. You don’t need to write a single reducer.
Here’s the code for store.js imported above. This is generic and could be moved into a library.
Async comes free
Here’s the fetchPosts action from https://github.com/jeswin/redux/blob/master/examples/redux-lite/src/actions/index.js
The code might have several flaws, but I’m putting it out there for discussion. We haven’t started using this pattern yet, so this is all untested.
Give it a whirl though.
Edit: The helpers used here are now available as redux-jetpack.