React: Understanding Key Prop
SeongKuk Han
Posted on May 22, 2022
I was preparing for interview questions. I googled one of the questions 'What is virtual DOM?'.
It's kind of classic for one of the questions of React
developers, right?
I was reading relevant posts for it, and all of a sudden, I just got the following questions about key
prop.
- if an element's key gets different, even if there are no other changes, would the element be replaced with the new one?
- Even if an element's attributes or text is changed, would the element be the same as the previous one?
React
has a virtualDOM in memory for comparing with the RealDOM and it updates necessary parts that need to.
That's all I knew about how React
works for rendering. I searched for more information and I read about Reconciliation
in the React
documentation.
Reconciliation is the process that React works for updating DOM.
in the document
If we used this in React, displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive. Instead, React implements a heuristic O(n) algorithm based on two assumptions:
Two elements of different types will produce different trees.
The developer can hint at which child elements may be stable across different renders with a key prop.
I knew that I must not use an index as a key
prop, because it would go something wrong if the keys are same.
But I wasn't sure what would happen in there because I had never tested anything for it, so, I decided to dig in about the key
prop today.
Keys help React
to identify which elements have changed
import { useState } from 'react';
function ExampleA() {
const [value, setValue] = useState(false);
const toggle = () => setValue(!value);
return (
<div>
{value ? (
<div
style={{ color: 'red' }}
onClick={() => {
alert('hello');
}}
>
Hello
</div>
) : (
<div>Bye</div>
)}
<button onClick={toggle}>Toggle</button>
</div>
);
}
export default ExampleA;
Someone may be able to think that it renders a different div element depending on value. (in RealDOM)
They're same div tags. It must be good to change the attributes. not the element.
I saved the element into a variable. and pressed the toggle button then I checked the variable.
They're the same.
But what if the key is different?
import { useState } from 'react';
function ExampleA() {
const [value, setValue] = useState(1);
const toggle = () => setValue(value > 0 ? 0 : 1);
return (
<div>
{value ? <div key={value}>Hello</div> : <div key={value}>Bye</div>}
<button onClick={toggle}>Toggle</button>
</div>
);
}
export default ExampleA;
Here is the code.
The element that was in RealDOM has been removed and the new one is created.
Rendering arrays using the .map
with index as keys
import { useEffect, useState, useMemo } from 'react';
function Time({ time }: { time: string }) {
useEffect(() => {
console.log({ time });
}, [time]);
return <div>{time}</div>;
}
function ExampleB() {
const [times, setTimes] = useState<string[]>([]);
const addItem = () => {
setTimes([new Date().toString(), ...times]);
};
const elements = useMemo(() => {
return times.map((time, timeIdx) => <Time key={timeIdx} time={time} />);
}, [times]);
return (
<div>
<button type="button" onClick={addItem}>
Add Item
</button>
<hr />
{elements}
</div>
);
}
export default ExampleB;
Let's see what happens when we add items,
Every time we add an item, all the items are updated.
const elements = useMemo(() => {
return times.map((time) => <Time key={time} time={time} />);
}, [times]);
I changed the key to time
. And Let's see again.
Now, it works well, why didn't it work correctly?
Look at these pictures.
index
as a key
time
as a key
The key
is used for distinguishing elements. Even if it didn't look like anything wrong, we have to take care of it.
Let's see another example.
import { useState, useMemo } from 'react';
function ExampleC() {
const [times, setTimes] = useState<string[]>([]);
const addItem = () => {
setTimes([new Date().toString(), ...times]);
};
const elements = useMemo(() => {
const handleDelete = (timeIdx: number) => () => {
setTimes((prevTimes) => prevTimes.filter((_, idx) => idx !== timeIdx));
};
return times.map((time, timeIdx) => (
<div key={timeIdx}>
<div>time: {time}</div>
<div>
<label>memo</label>
<input type="text" />
</div>
<button type="button" onClick={handleDelete(timeIdx)}>
Delete
</button>
<hr />
</div>
));
}, [times]);
return (
<div>
<button type="button" onClick={addItem}>
Add Item
</button>
<hr />
{elements}
</div>
);
}
export default ExampleC;
There are three items, and I'll delete second one.
The text of the second input
is 'BBBB' not 'CCCC'.
Why?
React
recognizes key 3
is removed, so, the input box that has 'CCCC' is deleted because the input box is a child of key 3
, and yea, the time of key 2
would change from '00:02' -> '00:01'.
Conclusion
I often used index
as a key
prop if there was no edit or delete features, because it looked working fine.
Now, I got to know it may have not, and I will deal with the key
prop more carefully.
I hope this will be helpful for someone :)
Happy Coding!
Posted on May 22, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.