Our Experience with Creating Reusable Functional Components with React, Redux, and Redux-Loop

Like many other companies, we here on the Kickstarter front-end team have been rewriting our site as a React app, with Redux to handle application state. This post is about our investigations into and ultimate solution for one issue we ran into in our work: namespacing actions.

I’ll cover why we wanted to namespace our Redux actions, the variety of available approaches, and the specific constraints we were working under before detailing our solution. (But feel free to scroll down to that last section first.)

The code examples in this post for both the problem and the solution are available in their totality on CodePen.

The Problem

With Redux, you can use combineReducers to create nested reducers that only operate on a slice of state, but all reducers still respond to all actions. Often this is the point—a component can affect another component just by dispatching an action. But when we started creating multiple instances of the same component, we created a system where every instance responded to action meant for just one.

Consider these instances of a div that changes color on hover. The intention is that just the instance being hovered over should change. But that’s not how it works.

Interacting with one box affects them both. Try it yourself on CodePen.

Why not? When you use combineReducers with Redux, you are creating a nested reducer where the keys match your state keys. So for instance, something like this:

results in a reducer with a shape like this:

When an action is dispatched, each reducer is called with that action and the slice of state which corresponds to its name. In this case, boxOne is called with state.boxOne , and boxTwo with state.boxTwo . This means that if an action is dispatched by one version of a component, something like:

then it is responded to by both components. You get behavior you don’t want.

The First Pass: Common Solutions and Identifying Constraints

We began with an initial research pass. In this stage, we took a look at commonly suggested solutions from the community: using local state and three approaches to manual namespacing via action type string.

Local State

One common solution in a case like this is to give each component a local state and to let it handle its own interactions.

However, our current front-end setup uses only functional components—functions that take props and return JSX components; no class ... extends React.Component . This choice made it easier to move to React for folks with less of a Javascript background. We were free from the this keyword and from ES6 class idiosyncrasies. Pure components were simpler to test.

In addition, using redux-loop for our middleware means both state updates and side effects are triggered by the reducer. A typical reducer condition with redux-loop would look like this:

We are therefore very incentivized to keep all changes funneled through the reducer. If it can change independently, bugs can be harder to track down and behavior harder to explain.

Manual Namespacing

For the last few years, the first approach for namespacing actions without local state has been to namespace the strings manually.

This can take a few forms, for instance using the feature name as a namespace:

This can be augmented or replaced by placing all your action constants in a single big file so that they cannot clash. Though in this case, it would be possible still to assign the same string to different constants in a big file, for example:

This can be addressed by using a unique string to be sure that even if constant names are reused across files, the action is namespaced:

Unfortunately, these are manual interventions. But what about cases where we wanted to add an arbitrary number of components to a page — say a shipping country select to each reward in a project, which could range from zero to nearly infinite — what then?

One More Thing

In addition to eschewing class-based components and manual solutions, I was particularly interested in a solution that complemented the component architecture my feature team was working with—what we called amalgamated components. These were higher-level components that encapsulated a feature unit, like a payment form or a custom select element: something that would exist at the molecule or organism level in atomic CSS. It comprises the display component, which may take any number of event handlers and a wrapper component that takes state and dispatch and binds the default events.

This way, if someone later down the road needs to use their own reducer and handler functions, they may grab the inner component, but in general, other teams instantiating the component should have a very low-surface-level API to work with. They would be able to import the component, its reducer, and its default state into the top-level file for the mount node, use combineReducers , and otherwise not have to fiddle with the component.

The preferred solution would work with this approach and allow us to keep things as encapsulated as possible for easy instantiation.