1. Introduction
When working with lists in React, you've probably seen a warning about missing key props. Many developers ignore it whereas many misunderstood it that adding key to dynamic array prevent array items from re-rendering and improves performance. But that's not how key works. So, In this article we'll break down what key is, why React needs it, and how to use it correctly.
2. The Problem
2.1. Key In Conditional Rendering (Same Type vs Different Type)
Consider this component :
function DifferentTypeConditionalInputs() {
const [active, setActive] = useState("first")
return (
<div style={{ padding: 20 }}>
<button
onClick={() =>
setActive((a) => (a === "first" ? "second" : "first"))
}
>
Toggle
</button>
<div style={{ marginTop: 12 }}>
{active === "first" ? (
<InputComponent placeholder="First input" /> // {type : InputComponent}
) : (
<input placeholder="Second input" /> // {type:Input}
)}
</div>
</div>
)
}
If we look at this code from reconciliation perspective, this is what our component returns. The first input component returns an object with type InputComponent i.e. {type:InputComponent} whereas the second input returns an object with type Input i.e {type : Input}. Now, when you type some value on First input and click the toggle button then React compares the type of two input and if they are different then it will unmount the previous active component and remounts the second input. As expected, the react unmounts the first input which causes it's state to lose as well. And mounts the second component. But the problem starts when the type of both input is same.
Let's look at another example :
function SameTypeConditionalInputs() {
const [active, setActive] = useState("first")
return (
<div style={{ padding: 20 }}>
<button
onClick={() =>
setActive((a) => (a === "first" ? "second" : "first"))
}
>
Toggle
</button>
<div style={{ marginTop: 12 }}>
{active === "first" ? (
<input placeholder="First input" /> // {type : Input}
) : (
<input placeholder="Second input" /> // {type : Input}
)}
</div>
</div>
)
}
In the above code, the first and second input returns type {type:Input}. And when the toggle button is clicked then react compares between two input type. But it does not find any difference in them so instead of destroying and remounting the previous input(i.e. first one). It simply takes the existing input component, updates it with the new data and re-renders it. Everything that is associated with the existing input like dom element or state is still there. Here it is not remounting the second input it is just re-rendering with the first input value.
So, What's the solution : Well one of the ways to handle this is to simply pass a key attribute.
<div style={{ marginTop: 12 }}>
{active === "first" ? (
<input placeholder="First input" key = {active} />
) : (
<input placeholder="Second input" key = {active} />
)}
</div>
Since, the type of both input is same so React checks for key attribute. Since the key attribute is different for both the inputs. So it unmounts the first input and mounts the second input.
2.2. Key In Arrays (Why React throws warning when not using key in list and what's the problem using index as key in an array.)
Well, we all have use key at some point of time when rendering list in react. Today we gonna deep dive into why we use key in react and to find out if it's performance reason or something else?
Let's take an example where a component renders fruits name in an input field and takes array index as key.
import { useState } from "react";
import "./styles.css";
const getRandomFruit = () => {
return `fruit-${Math.floor(Math.random() * 100)}`;
};
export default function App() {
const [items, setItems] = useState(["Apple", "Banana", "Orange"]);
const addItem = () => {
setItems((prev) => [getRandomFruit(), ...prev]);
};
const removeFirst = () => {
setItems((prev) => prev.slice(1));
};
console.log("items", items);
return (
<div style={{ padding: 20, maxWidth: 400 }}>
<div style={{ marginBottom: 12 }}>
<button onClick={addItem}>Add at first</button>{" "}
</div>
<div style={{ marginBottom: 12 }}>
<button onClick={removeFirst}>Remove First</button>{" "}
</div>
<div>{JSON.stringify(items)}</div>
<ul>
{items.map((item, index) => (
<SomeInput key={index} initialValue={item} index={index} /> // input component that has internal state
))}
</ul>
</div>
);
}
function SomeInput({
initialValue,
index,
}: {
initialValue: string;
index: number;
}) {
const [value, setValue] = useState<string>(initialValue);
return (
<div>
{index}
<input value={value} onChange={(e) => setValue(e.target.value)} />
</div>
);
}
What do you think what will when you add or remove item in the list. Will the new item added in proper place, or it's gonna render in different place or it's not going to render at all ?
Alright Let's debug it.
I have provided sandbox to check yourself
Comeback once you explore it know what is happening inside react
*Case 1 : When adding new item
*

*Case 1 : When removing item from list
*

As in the figure, This is what react does the scene. Even though the actual items are updated when adding or removing. Rendering of component is different than we thought. And we all know what's the solution
Solution : We should always use unique key so that it compare the key attribute to know which one to re-render when type is same. Also when using key, remember to use stable key else the items will be remounted instead of re-rendering.
Tricky Way to use Key as State Resetter
Using key as state resetter means we are intentionally changing a component's key so React unmounts the old component and mounts a new one, resetting all it's internal state. Remember in the above problem we were passing key as an attribute in first problem. Remember here when react mounts it's internal state is reset to initial value and on re-rendering it's internal state are preserved.
<div style={{ marginTop: 12 }}>
{active === "first" ? (
<input placeholder="First input" key = {active} /> // Re-mounted on active change
) : (
<input placeholder="Second input" key = {active} /> // Re-mounted on active change.
)}
</div>
Summary
React always re-renders components when state or props change.
The 'key' prop does not prevent re-render and is not a performance optimization.
Instead 'key' defines component identity during reconciliation. It tells React whether a component should be reused or recreated.
Top comments (0)