Understanding React's Reconciliation & Keyed Lists
React optimizes UI updates through a process called reconciliation, where it efficiently updates the DOM based on state changes. One of the most well-known optimizations is using keys in lists to help React identify which items have changed, been added, or removed.
However, many developers assume that adding a unique key
to list items completely prevents unnecessary re-renders. In reality, React may still re-render list items even when their keys remain stable. Let’s dive into why this happens and how to fix it.
1. When Do Keyed Lists Re-render Unnecessarily?
Even with correct keys, React can still trigger unnecessary re-renders due to:
(a) Parent Component Re-renders the List
If the parent component re-renders, React will re-evaluate all child components, including list items, even if their keys have not changed.
const ListComponent = ({ items }) => {
return (
<ul>
{items.map((item) => (
<ListItem key={item.id} item={item} />
))}
</ul>
);
};
In this case, if ListComponent
re-renders, all ListItem
components will also re-render, even if their props haven’t changed.
Fix: Memoization
Wrap the ListItem
component in React.memo
to prevent unnecessary re-renders:
const ListItem = React.memo(({ item }) => {
console.log("Rendering:", item.id);
return <li>{item.name}</li>;
});
Now, ListItem
will only re-render when its item
prop actually changes.
(b) New Array Instances on Every Render
If the list is derived from a prop or state that gets re-created on every render, React will treat it as a new list, even if the items haven't changed.
const App = () => {
const items = [{ id: 1, name: "Item 1" }, { id: 2, name: "Item 2" }];
return <ListComponent items={items} />;
};
Even though the items
array looks the same, it is a new array on every render.
Fix: Use useMemo
Memoizing the list prevents React from treating it as a new array:
const App = () => {
const items = useMemo(() => [
{ id: 1, name: "Item 1" },
{ id: 2, name: "Item 2" }
], []);
return <ListComponent items={items} />;
};
Now, React will recognize items
as the same reference across renders, preventing unnecessary list updates.
(c) Inline Function Props Causing Re-renders
If an inline function is passed as a prop to a list item, it creates a new function on every render, causing child components to re-render unnecessarily.
const ListComponent = ({ items }) => {
return (
<ul>
{items.map((item) => (
<ListItem key={item.id} item={item} onClick={() => console.log(item.id)} />
))}
</ul>
);
};
Since a new onClick
function is created on every render, ListItem
will re-render even if item
remains unchanged.
Fix: Use useCallback
Wrap the function in useCallback
to maintain a stable reference:
const ListComponent = ({ items }) => {
const handleClick = useCallback((id) => console.log(id), []);
return (
<ul>
{items.map((item) => (
<ListItem key={item.id} item={item} onClick={() => handleClick(item.id)} />
))}
</ul>
);
};
Now, the onClick
function reference remains the same, preventing unnecessary re-renders.
2. When Keyed Lists Do Need to Re-render
There are scenarios where React correctly re-renders list items:
- Item properties change: If an item’s props change, React must re-render it.
- List order changes: If items are re-ordered, React updates the DOM accordingly.
- Items are added or removed: React efficiently updates only the affected items.
These behaviors are expected and correct—but unnecessary re-renders due to avoidable mistakes should be optimized.
3. Summary & Best Practices
✅ Use React.memo
to prevent re-renders of list items when props don’t change.
✅ Use useMemo
to avoid unnecessary re-creations of lists.
✅ Use useCallback
for event handlers to maintain stable function references.
✅ Ensure keys are unique and stable to help React track list items correctly.
By following these best practices, you can drastically improve performance in your React applications by reducing unnecessary re-renders in keyed lists.
Top comments (0)