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....