Understanding Keys in React

React
03.10.2024
keys

The nature of React is to make developers' lives effortless, and using the key attribute during the component creation process feels intuitive. While it is, the key attribute is also closely tied to core React mechanisms and should be used carefully to prevent performance issues and bugs.

In this article, I will explore React keys mechanisms with a focus on practical usage.

Purpose

The key attribute in React serves two primary practice purposes:

  1. 1

    Reconciliation Process:
    Keys help React efficiently update and reconcile lists of elements by providing a stable identity for each item.

  2. 2

    State Reset:
    Changing the key of a component forces React to treat it as a new component, effectively resetting its state.

Reconciliation

Reconciliation is the algorithm React uses to efficiently update the component tree by diffing one Fiber tree with another. The goal is to determine which parts of the tree need to be updated, created, or removed. React follows these main rules during reconciliation:

  1. 1
    Component Type Mismatch: React assumes that different component types produce substantially different trees. Therefore, if the type of component changes, React will discard the old tree and build a new one from scratch.
  2. 2
    Key-Based Diffing for Lists: When diffing elements at the same level (among siblings) of the element tree, React relies on the key attribute.

During a component's lifecycle, the reconciliation process generally follows these steps:

  1. 1
    Tree Creation: React creates two trees: the “current” tree (representing the existing state of the component tree) and the “work in progress” tree (representing the new state of the component tree).
  2. 2
    Element Identification: React identifies elements that already exist to reuse them rather than recreating them from scratch:
    • If the key attribute is present, React will assume that elements with matching keys in both the “current” and “work in progress” trees are the same.
    • If the key attribute is absent, React will use the index of the element in the list as a fallback key.
  3. 3
    Applying Changes:
    • Unmounting: React removes elements that are present in the “current” tree but not in the “work in progress” tree.
    • Creating and Mounting: React creates and mounts elements that are present in the “work in progress” tree but not in the “current” tree.
    • Re-rendering: React updates and re-renders elements that exist in both the “current” and “work in progress” trees. The key point is that React applies the memoized state based on the key value.

Try out how keys are related to state management in this playground:

Main Rule 🔑

Analyzing all of the above, we can derive the main rule for using the key attribute effectively during the reconciliation process:

"Keys should be stable, predictable, and unique."

Bad Practices ❌

Here are some practices to avoid:

  • Generating keys on the fly: Keys generated dynamically during each render will not match between re-renders, leading to inefficient reconciliation.
  • Using Math.random() or similar approaches: Keys generated using random methods may not be unique or stable, which can cause issues with element identification and reconciliation.

Good Practices ✅

Here are two common best practices for generating keys in React:

  1. 1

    Use Data from a Database: If your data comes from a database, you can use the database keys or IDs, which are guaranteed to be unique.

  2. 2

    Generate Keys Locally: For data generated within your application, use methods such as:

    • An incrementing counter
    • crypto.randomUUID() for generating unique IDs
    • A package like uuid to create unique identifiers

Between Good and Evil

A common question is: "Is it a good idea to use array item indices as keys?"

React will use the index if you don’t specify a key at all. In certain cases, using indices as keys can be acceptable, but only under the following conditions:

  • The list and its items are static.
  • The items in the list don’t have IDs, and the list will never be reordered or filtered.
  • The list is immutable.

To avoid potential bugs and ensure efficient reconciliation, it’s best to follow recommended practices and use a unique key, rather than relying on the index.

State Refresh

As mentioned earlier, there is a useful technique for refreshing the state of a component. This approach can be particularly useful when dealing with components that do not provide a straightforward way to reset their internal state.

Consider a scenario where you have an uncontrolled but stateful component from a third-party library, and at a specific time, you need to reset it to its initial state. You can achieve this by using the key attribute in the parent component and changing its value.

Here’s how it works:

  • By updating the key attribute, React will unmount the old component and mount a new one with the initial state.
  • From a UI perspective, it appears as though the component's state has been refreshed.

Test it in this playground:

Conditional rendering

One last point to mention is the case of conditionally rendering two stateful components of the same type.

Very often, in this case, we might encounter bugs because there's a common misconception that keys are only necessary or works for dynamic lists of elements. However, React performs diffing on elements at the same level of the element tree in all cases.

In such situations, whether to use a key depends on the desired behavior.

This means that the following case

tsx
isSwitched ?
	    <Counter key="first" id="first" />
	    :
	    <Counter key="second" id="second" />  

and this case

tsx
isSwitched ? 
	     <Counter id="first" />
	     :
	     <Counter id="second" />

will cause different behavior for stateful components.

In the first case, switching will result in unmounting and mounting of a new component with its initial state.

In the second case, it will be just updating with new props but keeping the previous state.



Now you know how to safely and efficiently use the key attribute.


Happy coding! 👋