3 Things I Learned About Working with Data in Redux

Scaling for Redux Applications

I was speaking to an acquaintance one day (we’ll call him John). John and his colleague started making a messaging application, in which they developed the front end using React and Redux. They were explaining to me how they had been running into performance and organizational issues as their application grew. They mentioned issues like slow rendering and constantly losing track of where functions and data came from as it flowed through the component tree. About two weeks later, our company hosted a React Meetup, and I found that many people ran into the same issues.

One thing I love about Redux is how it encourages good practices that allow the mind to simplify applications through repetition. I work on a tool at Bleacher Report that controls the movement of a high volume of data flowing through multiple layers of our organization (think articles and videos associated with players, teams, leagues, etc.). Redux has made managing all that data easier, but as our application grew, organizing that data became extremely important — not only for efficiency, but for our own sanity.

I’m going to share three tactics we’ve learned along the way that have allowed us to scale our app with more efficient rendering flows and an easier management of data by establishing fine lines between presentational components, container components, and their data.

1. Moving UI State into the Component

When we first started using Redux, we put all data into the Store just because we thought that’s how it was supposed to work. This included data that only had to do with UI. For example, imagine we have a form that submits some data when clicking “Save.” We might add an isLoading Boolean field to the Store for the sole purpose of tracking when to show a loading spinner while the data from the form is sent to the server.

This caused a couple of problems. One is that it simply just didn’t feel right putting new fields in the Store that had nothing to do with the core data retrieved from our APIs. It also introduces a dependency on the Store, forcing us to write actions and new reducer cases to handle all the different UI data across our app.

To solve these issues, we embraced putting UI state in the component rather than our Store. In a way, we treat our Store like a client-side database. So if there is data for a list of sports articles, videos, etc. that we retrieved from an API, we consider this “core data” and keep it in the Store. Sometimes this data is used for UI, such as conditional flows like checking if a piece of data is a certain type.

if (article.type === "internal") {

/* render internal article component with that data */

} else {

/* render something else */

}

We may have only used this information to decide what to render, but it still persists throughout our organization, so it belongs in the store with the rest of our core data.

However, if we are adding a field on the front end because we need logic to decide whether to show specific content or not (as discussed in the isLoading example above), we keep it in component state.

By keeping this data out of the Store, we keep our core data clean and separated from data only used for visual purposes. It also keeps that functionality isolated to this instance, shrinking the dependency on the Store, thus lowering the number of actions and reducer cases we need to create throughout our app. It is unlikely that this data will need to be present anywhere outside the component dealing with that spinner. When an action dispatches because someone clicked “Save,” the component manages the state for showing the spinner internally and removes it when the action completes.

2. Using Higher Order Components

For those who don’t know what a Higher Order Component (HOC) is or how to build them, please do yourself a favor and research them. They are amazing tools! Check out this post for example:

When a piece of our application is entirely based on presentation and has little or nothing to do with data from the parent, we like to abstract this into an HOC. For instance, say you have a set of tabs in your application. All those tabs do are toggle which tab is active or not and render the component(s) that pertain to that active tab.

As discussed in Section 1, we do not need Redux for this. Abstracting this functionality into an HOC makes an extremely modular component that is usually not dependent on any outside data or data pertaining to its children. Sometimes we pass in a simple function that dispatches as an action to update the Store, but even then, that function is passed via a prop and the Higher Order Component doesn’t care about what it does. The component just knows what data to pass it.

We have done this for modals, infinite scrolling, tabs, composing local storage functionality in children, adding emojis to any text field, etc. We have found Higher Order Components to be incredibly reusable throughout our code while providing a consistent look and feel to our application.

3. ‘Connect’ing at Lower Levels

Redux provides a powerful way to easily handle React’s lifecycle methods to a component using connect . However, when speaking to individuals like John, they typically connect only at top levels of their app. The problem here is that when a change happens in the Store for a particular container component (a component tied to data), it causes its entire sub-component tree to re-render. This can be taken care of by customizing lifecycle methods like shouldComponentUpdate , but it doesn’t have to be that difficult.

Adding connect at lower levels in your application has powerful benefits. First, it takes care of the lifecycle methods for you using shallow diffing of the Store for the data that component is listening to. Secondly, it allows you to encapsulate functionality and data right there in mapStateToProps and mapDispatchToProps . This reduces the need to traverse backwards up the component hierarchy to find where data and functionality come from via props . Win-win! But you may be wondering when and where to use connect .

In our minds, container components are tied to data in the Store. They are meant to retrieve and format data for their children, as well as pass functionality to them. Container components aren’t always at the top level. In fact, oftentimes they are in the midst of the sub-structure of the app.

Presentational components, on the other hand, do not care about the specifics of data and functionality outside of what it is suppose to show and perform from a UI/UX perspective. This is what makes presentational components so modular and reusable.

It’s important to ask yourself, “Where are the definitive lines that separate my data?” For example, a User and a Video. Typically, they are mutually exclusive and will have their own sections in the Store. When you are creating a container for some feature within your app, ask yourself if it will handle data. If so, then it is probably a container component and should use connect to massage data and functionality together for its children.

In my example below, I will show how this is done and why it is so beneficial.