React - Don't use index as a key
Over the last couple of weeks I worked on multiple projects which a large React codebase spanning from a couple thousand line of codes to more than 100k for one of them.
Something that stroke me while going through the code was the way the key
prop was used when rendering an array of elements. In way too many cases the "index" of the array was used. It really felt like it was here more to silence the warning produced by React more than for its actual job: Keys help React identify which items have changed, are added, or are removed
.
The React documentation itself says:
We don’t recommend using indexes for keys if the order of items may change. This can negatively impact performance and may cause issues with component state.
I want to focus on the last part of this sentence: may cause issues with component state.
.
The problem
The following section will demonstrate a component that has a list of numbers. Each number will be passed to child component that will take the number and will expose a button that each time it is click will multiply the number by 2:
const Parent = () => {
const [numbers, setNumbers] = useState([0, 1, 2, 3, 4]);
useEffect(() => {
// After 5s, we will remove the midle element of the array.
setTimeout(() => {
setNumbers([0, 1, 3, 4]);
}, 5000);
}, []);
return (
<main>
{numbers.map((el, index) => (
<Multiply num={el} key={index} />
))}
</main>
)
};
const Multiply = ({num}) => {
const [state, setState] = useState(num);
return (
<section>
<div>Initial number: {num}</div>
<div>New number: {state}</div>
<button onClick={() => setState(state * 2)}>Multiply</button>
</section>
);
};
The trick part here is the setTimeout
. After 5 seconds, we mutate our arrays which triggers re-render of the Multiply
components. You can see in the example bellow the issue:
The new and initial component are not synchronized anymore. During the re-render, React in an attempt to optimize the operation will actually shift the prop and delete the last element of the array. This is where the key is extremely important. It helps react keep tracks of the different components and order so you can safely have operations like this one within your components.
The solution
How can we then avoid this kind of sneaky issue? As a rule of thumb, I usually generate a unique ID for my objects. Thankfully, in most application of this kind (be it a todo app, a pet management app or a game), when you get elements from your backend API you can get the ID.
You will notice here that I use shortid
to generate a short and simple unique ID for my elements. And you can see that after 5s, my array is still looking good.
The exception
There is an exception to the rule. Basically, if the array and its elements follow a certain set of rules, you can safely use the index as the key
:
- The elements in the array do not change - they are static elements
- The array itself is static: no new elements is added, removed and replaced
- And finally the elements don't have unique IDs.
Only - and only if - those 3 conditions are met, then using the index as the key
will be safe.
See the previous example but following those rules:
(Notice that in that case we do not mutate the array).
Conclusion
As a rule of thumb, I never use index as a key. But if you end up in a situation where the array that you are about to display exposes elements without unique identifiers, I would refer you to the exception rules. If the first 2 points are met, feel free to use the index safely - otherwise generate ids for your object.
Some more documentation on the subject: