React Lists & Keys

Published on

A simple problem that was causing noticeable performance issues for an old react app.

import { v4 } from "uuid";
const example = ({ items }) => {
  return items.map((i) => {
    return <div key={v4()}>{items.text}></div>;
  });
};

At first glance this seems innocuous enough, a simple call to get an ID for a list item. I assumed v4() was doing something clever to ensure it was getting consistent keys. Having recently taken over the project I made a note to investigate it later and moved on to a more urgent task. Spoiler: ooooh boy, this was not something clever.


In the context of rendering a list of objects, the key prop is used to help React track items in a list as they are updated, added, or removed. When quickly iterating on a project you've probably seen the below error.


Warning: Each child in an array or iterator should have a unique "key" prop.

The React docs have thoroughly documented how to properly generate a key prop for an item in an array. Ultimately, the best case is to derive the key from the list item itself.


const ExampleList = ({ data }) => {
  return data.map((d) => {
    return <Example key={d.id} {...d}></Example>;
  });
};

const data = [
  {id: '1',...},
  {id: '2',...},
  {id: '3',...}
]

<ExampleList data={data} />;

Back to our issue, by calling v4() as part of the map function, we're guaranteeing a different id for each render. If a single item changes we're telling react to re-render the whole list from scratch, as none of the keys will match. This will cause a significant amount of noise in the UI. This ironically silences React's warning about setting a key prop, even though the above snippet has similar if not worse performance than not setting one at all.


Our contrived example causes a lot of re-renders, but wouldn't cause that many problems with a static set of props. Some of the project's other technical debt exacerbated this problem... You don't need to dig too far into the example below. Re-rendering a list of items often re-fetches its content eg. images. With a noisy redux store, things get out of hand quickly.

import { v4 } from "uuid";
import connect from "react-redux";
import * as s from "./selectors";

const ExampleCart = ({
  header,
  costTotal,
  secondsUntilOfferExpires,
  items,
}) => {
  return (
    <div>
      <h1>{header}</h1>
      <h2>{costTotal}</h2>
      <h3>
        <strong>Act now!</strong>you have {timeUntilOfferExpires} seconds.{" "}
      </h3>
      {items.map((i) => {
        return (
          <div key={v4()}>
            <h1>{item.name}</h1>
            <img src={item.cart_img} />
          </div>
        );
      })}
    </div>
  );
};

const mapStateToProps = (state) => ({
  header: s.getHeaderForStore(state),
  costTotal: s.getTotalForCart(state),
  timeUntilOfferExpires:
    s.getTimeOfferExpires(state) - new Date().getTime() / 1000, // Don't do this
  items: s.getItemsForCart(state),
});

export default connect(mapStateToProps)(ExampleCart);

Any ways, you never quite know what you're going to get when working with existing web applications, but you'd be surprised how often there are low-hanging big-wins if you have a solid understanding of the basics.


#TODO: Insert network chart with all of the images being fetched and re-fetched and re-re-fetched....