Each child in a list should have a unique key prop
As a React developer, you've surely seen the following error message.
Warning: Each child in a list should have a unique "key" prop.
This error message is reported in React when we map over data without generating keys.
Let's take a look at an example of a Products
component that throws this error.
type Props = {
products: string[];
}
const Products = ({ products }: Props) => {
return (
<ul>
{products.map((product) => {
return (
<li>{product}</li>
);
})}
</ul>
);
}
export { Products };
The above example throws the error because there is no key
set on the <li>
element when mapping over the products
array.
Whenever we want to render an array of items in React, we must provide keys for each item in order to help React identify each item. Each key must be unique.
Most online tutorials will tell you to use the array index as the key for each item in order to solve the problem. Here is what that would look like.
const Products = ({ products }: Props) => {
return (
<ul>
{products.map((product, index) => {
return (
<li key={index}>{product}</li>
);
})}
</ul>
);
}
export { Products };
This is not a good idea. Using an item's index
as its key can cause problems when the order of items rendered changes over time. If items are added, deleted, or reordered, strange bugs can occur.
It's better to solve this problem in a way that is always guaranteed to be safe, without the risk of surprise bugs.
If our array does not have a stable id
per product that comes from a database, we can use the crypto.randomUUID
method.
The crypto.randomUUID
method is supported by major web browsers. It will generate a randomly generated, 36 character long unique string. This will guarantee that each item in the list gets a unique ID.
Be careful! We should not use crypto.randomUUID
to generate IDs on the fly.
// Do not do this!
<ul>
{items.map(item => {
return (
<li key={crypto.randomUUID()}>{item}</li>
);
})}
</ul>
Generating the ID directly in iterations of map
will make the key change on every component render. Every time the key
changes, the corresponding element will be recreated by React. This slows down performance and causes any user input associated to a list item with a specific key
to be lost.
Let's take a look at how we can properly use the crypto.randomUUID
method to create a productsWithIds
array from the initial products
array.
const Products = ({ products }: Props) => {
const productsWithIds = products.map(product => {
return {
name: product,
id: crypto.randomUUID(),
};
});
return (
<ul>
{productsWithIds.map(({ id, name }) => {
return (
<li key={id}>{name}</li>
);
})}
</ul>
);
}
export { Products };
Let's make use of the Products
component to test the latest changes that we've made to it.
export default function App() {
return (
<div>
<h1>Products</h1>
<Products products={['Switch', 'XBOX', 'PS5']} />
</div>
);
}
We'll notice that there is no more React error message about non-unique keys being used in our list. Even though no unique IDs were provided by the products
array itself, we generated unique keys that are both safe and stable.