Redux best practices

Will Becker
Lexical Labs Engineering
9 min readSep 28, 2015

--

Redux is the React framework that other flux frameworks recommend you use. It went 1.0.0 when I started writing this (and is at the time of publishing past 3.0.0!) and it’s still early days as to how to use it.

Its author, Dan Abramov, has put together some great documentation, but it doesn’t yet fully address how to deal with engineering with Redux at scale — people are starting to ask, “are there any repos of non-trivial apps that use redux”. Well, hopefully this goes somewhere to it.

We’ll talk about:

  • the full tech stack
  • what each part of Redux ends up doing
  • how to lay out a Redux project
  • how to handle async data with WebSockets.

What should I know already?

Read the redux documentation.

You should have read Dan’s article about Smart & Dumb Components.

What tools do you need for a Redux project?

Redux isn’t just Redux. It’s a whole bunch of stuff, that combines together, some of which is official and v1.0.0 and some of which is still getting nursed out of childhood.

Your tool kit will probably contain most of the following:

  • Webpack
    Use this to pull together your bundles instead of Browserify or Require and whatever else it is that you are using. Why? Because part of the initial demo for Redux showed it’s power in hot reloading. And that is built around Webpack.
    It’s so nice to be able to just hit save and see your styling update, as opposed to having to go save, refresh, get to the right screen, repeat.
  • Babel
    Partly because Redux is so functional and you can make so much use of the new ES6/7 sugar this gives you, but partly because HotReloading is now written as a Babel plugin!
  • React
    Duh. But we should say that while 0.13 is the stable on as this article goes to presses, we’re all waiting on 0.14 for the reason that some context related issues have been fixed.
    Because ES6 is giving proper classes to us now, React is deprecating the use of Mixins. To get around that, use Higher Order Components instead — which wrap up your React components in a parent that can provide information via the new (kind of secret) context mechanism. Redux makes full use of this.
  • Redux
    Double Duh.
  • React-Redux
    While strictly speaking Redux is framework agnostic, it was written with React in mind. This provides higher-order components that link React Components with Redux Stores.
  • Middleware
    You have two options here: thunks or some kind of promise library. Either way you need to async-ness in your app and this is the bit that makes it possible to do it in your action creators.
  • Request Library
    There is a reason for that async code. I’ve been using Axios for this — it’s promised based so fits in nicely with promise middleware.
  • React-Router(-redux)
    Routers are ostensibly about making your toolbar update so show where you are in an application. But the more fundamental reason for them is it gives a logical mechanism to break up your code.
    The problem with having a router is that it gives you more state — which is not in your stores. Enter Redux-Router. It makes sure that the state is in Redux, so now you can, for example, dispatch events from your SmartComponents based on the top level project you are in (that is in your route spec).

How do you use the different parts of Redux?

We all know the story about Flux being a unidirectional data framework. But even still, how do you use it?

In any app you need to:

  • Get the initial state
  • Draw stuff based on that state
  • Handle interactions with the UI
  • Make requests and keep things in state with your data stores.
  • Update things and redraw things.

In a less proscriptive framework you just dump stuff everywhere, possibly doing all of the above in a single or a couple different places.

The way I have been organising my code is as follows:

Use Routes to ensure that you have the right data for your components

This is nice because it separates rendering from data collection. Use the onEnter method of a Route to issue commands to gather what it needs to render. You don’t need this method to actually wait until the collection is done becuase you can…

Use SmartComponents to ensure that your DumbComponents are ready to render

Your SmartComponents should be the components in your Route. In your SmartComponent’s render function have a method that makes sure it has the data it needs:

render () {
if (this.hasData()) {
return this.renderComponents();
} else {
return this.renderLoadingScreen();
}
}

Use SmartComponents to do as much pre-processing as possible, so your DumbComponents can be really Dumb.

For instance, when you pass a handler to your DumbComponent, curry it with the id, so the DumbComponent doesn’t need to know it:

renderComponents () {
return <DumbComponent
onSelect={this.itemSelected.bind(this, this.props.item.id)}
>;
}

Use DumbComponents to render *everything*.

Don’t even put a <div> in a SmartComponent. It should only ever be composing DumbComponents for you. Separate your concerns — and don’t start just doing “one little thing here”!

Use SmartComponents to generate actions creators.

When a DumbComponent has an interaction from a user, it shouldn’t handle any logic itself — it should blindly call a function which is passed to it by a SmartContainer and let it do the work.

The SmartContainer should then marshal the necessary data and pass it to an action creator.

Use ActionCreators to translate application data structures to API data structures.

Your ActionCreators are responsible for converting the data in your application’s format to the format of the API to which you are communicating. This is in both directions — when making the request or handling the response.

Because the output of an action is handled by a reducer that has no knowledge of how it was called, you might find that sometimes you can’t merely return the result of an API call — you need to enrich it extra data, eg: if your action is PROJECT_UPDATE, and you pass a new project name and the project id, and the API just returns {savedAt: “<some date>”}, then you will need to pass the data from the arguments into the response:

function updateProject(projectId, projectName) {
request.put(`/project/${projectId}`, {projectName}).then(
response => Object.assign(
{projectId, projectName},
response.data
)
);
}

Use reducers to keep your state in sync.

The interesting thing here is that a reducer can handle any action at all. One neat thing to do is when a user logs out, clear all your stores.

switch (action.type) {
...
case USER_LOGOUT:
return {}
}

File Layout

How to organise your files is a complicated thing as it is more art and style than anything set in stone.

I have found that there are two separate concerns in a Redux app, and I organise my files around them.

One concern is data. Your actions (which generate requests for changes in data) can be called from anywhere (normally SmartComponents). Your reducers (which perform the change in data) however, are tied to your actions. Actions can be grouped together, around themes in your application: maybe one part is to do with user login and permissions. Another may have to do with projects that they manage. All of these things are created, retrieved, updated and deleted — and potentially a host of other actions as well — and those all should live together.

On the other hand, you have the view centric layout of your app — the Routes to different views, the SmartComponents that gather the data and handle interactions and the DumbComponents that render these.

Several of these views may call the same actions. For example a project list may allow you to edit the project names in line, but a project detail page may have a form for users to edit the project names. However these would have separate routes and different Smart and Dumb Components collecting different sets of data and presenting the data in different ways.

Thus, I layout my projects like so:

public/
index.html
client/
index.js
modules/
reducers.js
users/
constants.js
actions/
user_fetch.js
user_login.js
permissions_fetch.js
reducers/
index.js
user.js
permission.js
projects/
routes/
login/
index.js
containers/
login.js
components/
login.js
logged_in/
project_list/
project_view/

One-off folders are in bold, folders that contain a similar layout are in italics.

The modules directory is responsible for holding data-related files, broken down by sub-directories that deal with a facet of your application. You could potentially have these as separate npm repositories that you include into your project — they have no dependencies save on redux and 3rd party libraries.

Each action and reducer has a separate file for itself. There is one standard that tries to put all the contents of a module together into a single file. I personally would advocate against this on medium to large-sized projects —when things get big, it’s better to break out things into as small a chunk as possible!

To make reducers similar in structure across modules, an index.js file is included, which exports every other reducer in the folder. Then, at the top-level, there exists a single reducers.js file which includes each <module-name>/reducers. This single reducers file can then be used to produce your Redux store.

The routes directory is responsible for holding view-related files, broken down by individual route. Each route contains three things:

  • SmartComponents in the containers directory,
  • DumbComponents in the components directory and
  • An index.js file, which contains a Route.

Again, this goes down the path of breaking things down into as many small components as possible. I would recommend this (as oppoesd to having a route hierarchy written in XML) as it allows you to instantiate routes only as you need them. This also means that your route file will only including other files in relative sub-directories, which feels nice and de-coupled.

Your route file can then also be the guardian of the data in your store when you go to the route, by making use of the onEnter and onLeave methods. In here you can dispatch fetch actions that ensure that your components have the data they need. This is really useful when you have deep nested routes.
For example, given the route /app/project/10/permission, you could:

  • in /app fetch the current user’s login information
  • in /project fetch the list of projects the user has available to them
  • in /10 fetch fine grained details of Project 10
  • in /permission fetch the list of permissions available to the user to set

Then, when changing to another route, /app/project/11, you would only do a fetch for the things that have changed (the route handler for /11), so would only do a single fetch for the Project 11. This could be done as follows:

import Projects from "./containers/projects";
import ProjectDetailRoute from "routes/project_detail";
export default class ProjectList {
constructor () {
this.path = "project";
this.projectDetailRoute = new ProjectDetailRoute();
}
getChildRoutes (state, cb) {
cb(null, [this.projectDetailRoute]);
}
getComponents (cb) {
cb(null, ProjectTasks);
}
onEnter () {
this.fetchProjects();
}
fetchProjects () {
...
}
}

How to name your XXX

Naming things is hard. But so is managing state, and Redux takes the pain out of that so can we name things simply as well?

Actions: Name them them <noun>-<verb>, eg Project-Create, User-Login. The rationale behind this is that you want them grouped together by the object type, rather than the action type.

Reducers: Name them <noun>.

How do you handle async data from external sources?

Obviously there is the One True Flow (Action -> Reducer -> SmartContainer -> DumbComponent). But where exactly does your change fit into this?

Third party async data will normally come from a WebSocket channel. You will only want to listen to this in some parts of your application — eg when you are logged in, or on a specific webpage. Furthermore, the way that actions are handled, from a UI point of view, is that a user generates an event, a DumbComponent propagates this event to a SmartComponent, and that then generates an Action. Your SmartComponent is put on the page by a Route.

In this case, there is no DumbComponent to render things, however there is a Route that determines for when you should be accessing data and an Action that injects the data into Redux. The missing link is a SmartComponent that is generated by the Route to handle incoming actions and translate them into events. This SmartComponent will not need any DumbComponent to render something to the user, but should probably be independent of the other SmartComponent that does.

React-Router handles this is a nice way:

The Route can have several named components,

getComponents () {
cb(null, {view: ViewContainer, data: DataContainer};
}

The SmartComponent that then renders the components in this route can then render like this:

render () {
return <div>{this.props.view}{this.props.data}</div>
}

The DataContainer can then respond to changes in props (to perhaps listen to a different channel or route the data elsewhere) via componentDidUpdate and close a connection via componentWillUnmount.

Conclusion

I’ve been sitting on this for a couple of weeks as there is always another bit to add to it — it’s not a story that has an end — but am dumping this out so that people new to Redux can get something out of the time I’ve been hanging out on the Reactiflux Redux channel! Please comment and annotate this and I’ll keep this as a semi-living and breathing article over the next few weeks!

--

--