Matters is a startup studio building the alternative products of tomorrow. For us, it is key to be able to scale fast our development team when one of our products get good feedback on the market. Frontend is a particular area where iterating was always quite painful. Redux is a technical lib used in frontend development. It is a simple design pattern, similar to CQRS which helps preventing some specific types of bugs.
There are plenty of articles about all the Redux qualities, and if you are reading this article, you probably know a few already :). For us, it was mainly a good way to refactor and test several UIs without having to constantly refactor controllers, async code, and data bindings.
So is everything perfect with Redux? Not quite, as Redux is based on the “exotic” functional programming approach, it may be wrongly used. This article targets developers planning to use Redux at scale.
We also made a talk in the ReactJs meetup in San Francisco about the topic, and it was recorded: Link vidéo 10 tips for react redux at scale
We deployed our first Redux-based frontend in 2016 with 120 React components and 128 Redux action types. Based on this success, we decided to deploy 4 other large apps with Redux, all with more than 100+ action types. Overall 25 developers interact daily with Redux at Matters.
Here is what we learned scaling those apps.
1/ Plan 1 day of training per developer dedicated to Redux
We discovered too late in the project that some team members got misled in what Redux is. Redux is not natural or intuitive. A developer coming from a Js background is not necessarily versed in functional programming or immutability.
The best way to debunk those preconceived ideas is to read the official doc. Now I make sure that everyone books a full day to read the official documentation from start to finish before jumping in the code.
2/ Integrate Redux-dev-tools as early as possible
Redux-dev-tools is an awesome Chrome and Firefox extension which provides a great value for any developer using Redux. Not only it helps understanding Redux when you learn, but it also helps us everyday correcting bugs in production.
It will help the whole team taming the Redux beast; make sure everybody knows how and when to use it.
Usually it is sufficient to just add this middleware:
3/ Use action creators
Action creators provided us quite a lot of value for the time they took to put in place. At first, they seemed to me like a superfluous abstract layer. Oh boy I was wrong.
Action creators look like this:
What are the advantages?
- There is an explicit dependency between the component dispatching the action and the action
- There is an explicit API to interact with the state
- It provides a few really elegant shortcuts when binding those actions to React component
Check this out:
The “connect” function from react-redux indeed accepts directly a collection of action creators in its interface, making the code super readable.
It is just one of the several examples of 3rd party APIs making your life easier with action creators.
4/ Organize your file system with Redux Ducks
Redux is a library. It doesn’t enforce any specific organization of the source code. When our dev team got larger, we felt the need for more structure, and redux-ducks was just that. Redux-ducks is a convention and is not a library. It defines a standard interface for Redux-related module.
Here is the gist of it:
We really liked that it enforces action creators everywhere. It also reduced the number of dependencies and Redux files by grouping in the same file what is related together.
Overall, it was a sane convention, which perfectly sorted all of our Redux file hierarchy.
5/ Write unit tests, it’s *free*
What makes unit testing hard in a procedural or OOP code is isolating the “unit”, as well as setting a particular initial state.
Redux-based code uses a functional programming approach, and is built by composing pure functions. Pure functions are functions which output the same result if called with the same inputs. You could call them deterministic, side-effect-free etc.
- As pure functions have no internal state, setting the initial state for unit testing is just a matter of setting function arguments
- Each element is already an isolated function before composition.
We used Jest with state snapshot to go fast. Quite a lot of people prefer to test explicitly parts of the state, but snapshotting have been very sufficient for the 6 months of the project. We caught all the regressions and could refactor confidently. And for the first time, we had maintained unit tests with 100% coverage of the Redux code base.
6/ Make general APIs using payload-based action creators
We have two ways to edit users: their name, or their email address.
At first we made two different action creators:
- users/editName(name) and
We never used the two different types of actions, the reducers were quite similar and we ended up duplicating a lot of code. Worse, we couldn’t easily add a field to the user object without duplicating a bunch of code.
We decided later on to merge back those action creators into a single:
It has been sufficient. Keep it simple.
Oh last point about that: just pack what may be useful in the action, don’t go thinking an action is a “command” or a “RPC” call which should have tailored parameters. Redux actions are “facts”, if I add a user, I put in the action everything I know about this new user, the reducer will pick what it needs.
7/ Anticipate that you will have a lot of async-handling code
One error I saw in a few other projects I audited was to assume that async and side-effects code is a secondary problem and solves it with redux-thunk, which is the lowest barrier of entry. The biggest problem in my opinion with Redux thunk is that it propagates the dispatch function everywhere. That makes it way harder to figure out who dispatches an action, as suddenly regular action creators may not be side-effect free.
Redux-saga was a better bet for us. It provides high level abstraction and patterns, like debounces, retries or throttles. It leaves actions as plain objects easier to unit test, and isolate all the side-effects in nice looking sagas, leaving your Redux code pristine.
For instance, if you plan one day to have an offline mode, I highly recommend you to onboard from the start something more advanced like redux-saga. Refactoring this part later is a real pain.
8/ Normalize your state like a database
It seems obvious after you say it, but avoid duplicating information. Make sure to have only one reference to each object. For us, we followed best practices that already exist in the database world.
If you don’t know how to do it, it is probably the moment where you sit down with one person from the backoffice to get this straight, as a db guy will be able to do this task in no time.
Based on this example, retrieving one user and its groups becomes slightly more complex, which leads us to our next tip.
9/ Use selectors
Selectors are convenient functions returning parts of the state. If you normalized your state, there are huge chances that your state won’t be perfectly aligned in format with what your components consume.
For instance, with the example above, imagine we want to display a list of users and their groups by name.
It keeps your components independent from your state shape, making it possible to use straight out of the can some Material, Bootstrap or generic components. Bonus, you can use a lib like reselect to really speed up these transformations as well.
10/ Flowtype, Reasonml or Typescript can really help
This advice goes beyond simply the scope of this project, but types particularly help with state management. I know 3 ways to do that in JS:
- migrate to a superset of js like Typescript
- adopt another language like Reasonml
- retrofit your js codebase with Flowtype and add type annotations as you go
Adding just one line with //@flow at the beginning of our redux ducks modules caught a lot of missing cases without even going to the type annotation route.
It’s free and it’s worth it.
I hope all those advices will help you structuring a large Redux codebase.
We are very interested in hearing your point of view, you are welcome to share your opinion or ask for details in an email or on Twitter @batmansmk.
Thanks to Titoune and Jc Bohin.