The usual pattern for rendering lists of components often ends with delegating all of the responsibilities of each child component to the entire list container component. But with a few optimizations, we can make a change in a child component not cause the parent component to re-render.

Let's make a list of React components go from this:

animated gif showing the component containing a list of items re-rendering after a change in a single child component.

To this:

animated gif showing that changes in a child component don't cause a re-render of a parent component.

To be clear, the first example above doesn't actually cause wasted re-renders, according to react-addons-perf.

We'll be using TypeScript (for clarity and clearly defining what data our components receive) and Immutable.js. The source code of the finished app is available on github and a live demo is available here.

The problem

Let's imagine that we're building a website to list animals for adoption, and that each animal looks like this:

export interface Animal {
  readonly id: string;
  readonly name: string;
  readonly adopted: boolean;
  readonly type: 'Cat' | 'Dog';
}

Consider this react-redux implementation of rendering a list of DOM elements to display animals for adoption. The important stuff is in the Props and Handlers interfaces.

We get most of what we need to know from the Props and Handlers interfaces: AnimalTable accepts an immutable map of Animals. We see in the mapState function that it gets that data from subscribing to the store's animal property. It also receives the dispatching function to toggle an animal's adopted property (which we'll just trust is implemented properly, since it doesn't matter for this example).

In AnimalTable's render method, for each of the animals in the Animal Map it renders an AnimalListing, passing it the toggleStatus action creator and an Animal. We can guess that an AnimalListing will contain a button to invoke toggleStatus and will display the information specific to its animal.

To summarize, AnimalTable does a lot. Without even looking at the code for AnimalListing we can guess at everything it does because AnimalTable is responsible for all of the functionality of each AnimalListing. AnimalTable doesn't even do anything with the callback its passed in, it only passes it along to each AnimalListing. And does it really need the entire map of animals, complete with the entire Animal object?

We can simplify this massively. Each AnimalListing can be a connected react-redux component that subscribes only to one particular animal. But how do we know which animal to give to which AnimalListing? The key is in the second, optional argument that can be passed in to the first argument of connect (which is idiomatically called mapStateToProps). The second argument of mapStateToProps are the props that the parent of a component passes down to a connected component, which we can call OwnProps.

Connecting individual AnimalListings

If AnimalTable passes down just an id to each AnimalListing, AnimalListing could use that id to subscribe to a specific animal in the store. Let's look at an AnimalListing that implements this pattern:

The important part here are the Props and OwnProps interfaces, and the mapState function. When mapState is called on an AnimalListing, AnimalListing already has the id that AnimalTable gives it, and we use that id to assign it a specific Animal.

Also notice that we were able to move the action that dispatches toggleAdoption to the component that actually uses it, AnimalListing.

Fixing AnimalTable

With this pattern, AnimalTable really only needs an array of ids, so it could look like this:

Now AnimalTable receives just an immutable List of strings, which are animalIds it passes down to each AnimalListing. The only problem here is that we're doing a lot of logic in our mapState function, which we'll solve in the next section.

Adding reselect

To hide the logic we're now implementing in AnimalTable's mapState function, we can use the ultra-light reselect library.


// src/selectors.ts

import { createSelector } from 'reselect';
import { RootState, Animal } from './types';

const animalsSelector = (state: RootState) => state.animals;

export const animalIdsSelector = createSelector(
  [ animalsSelector ],
  (animals) => animals.map((animal: Animal) => animal.id).toList()
);

And now we can use it in AnimalTable's mapState:

import { animalIdsSelector } from '../selectors';

...

const mapState = (state: RootState): Props => ({
  animalIds: animalIdsSelector(state)
});
...

Although it isn't doing much in this example, reselect is a valuable tool when you start sorting, filtering, and otherwise manipulating data before you pass it to a component (here's an example from one of my own projects). And if a change in one selector doesn't change the result of another, your component won't re-render.

The final touch

At this point we have implemented everything we need to leverage immutable data structures to prevent re-rendering the entire AnimalTable when we adopt a single animal: a custom shouldComponentUpdate.

class AnimalTable extends React.Component<Props, never> {
  shouldComponentUpdate(nextProps: Props) {
    return !nextProps.animalIds.equals(this.props.animalIds);
  }
  ...

shouldComponentUpdate will return false if the props its receiving are equal to the props it already has. And because the AnimalTable is receiving just a List of string IDs, a change in the adoption status won't cause AnimalTable to receive a different set of IDs.