Dynamic Child Components with React

 

I’d like to suggest a simple piece of advice to fellow React devs out there. When rendering dynamic arrays of child elements within a React component…

Don’t Forget Your Keys!

tl;dr Make sure you provide a unique key to each child element being rendered. If you are using a backbone model, the model.cid is a good choice, or you can use the database id if there is one.

Providing keys to child elements is necessary because of React’s method of Reconciliation (i.e. how it calculates the DOM diff for each render). By default, React reconciles a list of children by the order in which they are rendered. However, with dynamic children, the identity and state of each child usually needs to be maintained across each render even though the order may have changed. In this case, you are encouraged to pass a key prop to each child element. By doing so, you tell React that each child should be reordered (instead of clobbered) or destroyed (instead of reused).

Let’s say you have a ResultsList component that renders a list of search results:

var ResultsList = React.createClass({
  render: function() {
    return (
      <ul>
        {this.props.results.map(function(result) {
           return <ResultItem result={result}/>;
        })}
      </ul>
    );
  }
});

var ResultItem = React.createClass({
  render: function() {
    return (
      <li>return {this.props.result};</li>
    );
  }
});

This looks right…right?

Wrong. You’ll notice that if you open up your JavaScript console, you probably have a warning that looks something like: “Each child in an array should have a unique “key” prop. Check the renderComponent call using <ResultItem>. See React Warning Keys for more information.”

This is easy to miss, especially since it probably rendered as expected at first. It also doesn’t help that React only gives us a warning. It is, however, a warning that should not be ignored. The problem is that you need to provide a unique key prop to each child component being rendered. The solution is simple:

var ResultList = React.createClass({
  render: function() {
    return (
      <ul>
        {this.props.results.map(function(result) {
           return <ResultItem key={result.id} result={result}/>;
        })}
      </ul>
    );
  }
});

var ResultItem = React.createClass({
  render: function() {
    return (
      <li>return {this.props.result};</li>
    );
  }
});

The only change is that we are now passing a key prop to each ResultItem to be rendered. The key must to be unique, since that’s how React differentiates between each child in order to make re-rendering more efficient. If you have duplicate keys, only one of those components will get rendered. In the example above, uniqueness was accomplished by using the result’s id attribute. So that was easy. Problem solved!

Wrong ways to solve this problem

On more than one occasion, I have seen people (including myself) try to get rid of this warning the wrong way. Here are a few wrong ways to do it:

1. Adding the key inside HTML of the child component itself, instead of passing it to each child component directly as a prop.

// This won't work.
var ResultItem = React.createClass({
  render: function() {
    return (
      <li key={this.props.result.id}>return {this.props.result};</li>
    );
  }
});

Directly from the React docs: “The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array”.

2. Using the index of the loop for the unique key

// This won't work either.
var ResultList = React.createClass({
  render: function() {
    return (
      <ul>
        {this.props.results.map(function(result, index) {
           return <ResultItem key={index} result={result}/>;
        })}
      </ul>
    );
  }
});

This will get rid of the warning, but it will cause problems. With this pattern, if the mapped results change, React won’t re-render the existing child components with new results. This is because the key hasn’t changed even though props.result changed, since the child element in question is still at the same index in the array. React doesn’t know the child should be receiving new props because the key remained the same.

3. Using something like underscore’s _.uniqueId()

// React's rendering is blazing fast...
// ...until you do this.
var ResultList = React.createClass({
  render: function() {
    return (
      <ul>
        {this.props.results.map(function(result) {
           return <ResultItem key={_.uniqueId()} result={result}/>;
        })}
      </ul>
    );
  }
});

This will cause problems because _.uniqueId() increments up every time it’s called, which will change the key of every item in the list every time ResultsList is re-rendered, causing all the previous children to be destroyed and new copies created, regardless of the data. Your performance will tank.


Don’t neglect warnings about keys. Don’t take stupid shortcuts to make them disappear, either. It will cause you (or the next poor soul after you) much pain and suffering. The engineers behind Revelry’s custom software development use React and write about it often. Check out this post on React testing with Jasmine, dive into React and ECMAScript 6, or learn more about using Google Maps with React.

Want to know even more about our process?

Go for a deep dive into the Lean Agile Process at Revelry.
Get in touch and let us know what we can build with you.

More Posts by Daniel Andrews:

Kubernetes, Delivered

Deploy your first app within 24 hours. Book a demo to get started.