⚛️ React Q70 / 113

How does React handle reconciliation with keys?

AI-Powered Answer ✓ Answered

React's reconciliation process is its way of updating the UI efficiently. When a component's state or props change, React needs to determine what parts of the actual DOM to update. This is done through a 'diffing' algorithm comparing the new Virtual DOM tree with the previous one. Keys play a crucial role in optimizing this process, especially when dealing with lists of elements.

What is Reconciliation?

Reconciliation is the algorithm React uses to update the browser's DOM. Instead of directly manipulating the browser DOM, React uses a 'Virtual DOM', which is a lightweight representation of the actual DOM. When state or props change, React creates a new Virtual DOM tree and compares it with the previous one. This comparison process is called 'diffing'.

The diffing algorithm aims to find the minimal set of changes required to update the actual DOM, thereby minimizing expensive DOM operations.

The Problem Without Keys

When rendering lists of elements without stable identifiers, React struggles to efficiently update the DOM if the list items are reordered, added, or removed. By default, if no keys are provided, React uses the item's index in the array as its key. This works fine for static lists that never change, but leads to inefficiencies and potential bugs in dynamic lists.

For example, if a list item is moved from the bottom to the top, React might incorrectly assume that the first item was modified and the subsequent items also changed, rather than recognizing that the old first item moved down and a new item appeared at the top. This can cause unnecessary re-renders, state loss within list items, or incorrect component lifecycles.

How Keys Solve the Problem

Keys are special string attributes that you need to include when creating lists of elements in React. They help React identify which items have changed, are added, or are removed. Keys provide a stable identity to each element in a list.

  • Unique Identification: Keys must be unique among sibling elements within the same list. They don't need to be globally unique across the entire application.
  • Stable Identity: Keys should be stable, meaning they should not change between re-renders for the same logical item. If a key changes, React will treat the old component as destroyed and a new one as created, potentially leading to performance issues and loss of component state.

During reconciliation, when React encounters lists, it uses keys to match child elements in the old tree with child elements in the new tree. Here's how it generally works:

  • If a key exists in the new tree but not in the old tree, React creates a new component instance.
  • If a key exists in both trees, React moves or reorders the existing component instance to its new position. If its props have changed, it will update the component.
  • If a key exists in the old tree but not in the new tree, React destroys the corresponding component instance.

Best Practices for Keys

  • Use stable, unique IDs: The best keys are unique identifiers from your data source, such as database IDs or unique IDs generated by a library (e.g., uuid). These IDs are inherently stable and unique.
  • Avoid using array indices as keys: Only use array indices as keys if the list is static and its items will never be reordered, filtered, or added/removed. Using indices for dynamic lists can lead to unpredictable behavior and performance issues.
  • Do not generate random keys: Generating random keys on every render will cause every component to be re-created on every render, destroying performance and component state.
  • Keys are for React's internal use: Keys are not passed as props to your components. If you need to access the ID within your component, pass it explicitly as a separate prop.

Example Code with Keys

Consider a list of items where each item has a unique id property:

jsx
function ItemList({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}> 
          {item.name}
        </li>
      ))}
    </ul>
  );
}

In this example, item.id serves as a stable and unique key. If items array is reordered, React can efficiently move the <li> elements in the DOM without re-rendering their content, preserving their internal state if they were complex components.