Modern React 3: Rendering lists and stable keys
This is part 3 of the Modern React development series.
Most interfaces repeat something: messages, table rows, search results, tabs, menu items, notifications. React renders those repetitions by mapping data to JSX and using keys to preserve each item’s identity across renders.
Concept
A key is a stable identifier for one item among its siblings. React uses keys when a list changes so it can match old children to new children and preserve the right state, focus, and DOM nodes.
Terms
- List rendering: Turning an array of data into an array of React nodes.
- Key: A string or number that identifies one rendered sibling in a list.
- Sibling identity: The identity React compares among children that share the same parent.
- Reorder: A list change where the same items appear in a different order.
Mental model
Think of keys as name tags on moving boxes. If boxes move around the room, React can still tell which box is which. If the name tag is only the current position, the label changes every time the order changes.
How it is used
Keys matter in sortable tables, drag and drop lists, filtered search results, editable rows, accordions, and any repeated component with local state. A good key usually comes from the data source, such as a database ID, slug, or stable domain identifier.
How to use it
- Map over data and return one top-level node for each item.
- Put
keyon the node returned directly from the map call. - Use an ID from the data whenever the item can be inserted, removed, filtered, or reordered.
- Only use an array index when the list is static and will never reorder or keep per-item state.
Example: Task list with item identity
import type { ReactElement } from "react";
type Task = { id: string; title: string; done: boolean;};
export function TaskList({ tasks }: { tasks: Task[] }): ReactElement { return ( <ul> {tasks.map((task) => ( <li key={task.id}> <label> <input type="checkbox" defaultChecked={task.done} /> {task.title} </label> </li> ))} </ul> );}The key comes from task.id, so React can keep the checkbox state attached to the same task when the list changes.
Example: Grouped list keys
import { TaskList } from "./TaskList";
type Project = { id: string; name: string; tasks: Task[];};
export function ProjectTaskList({ projects }: { projects: Project[] }) { return ( <div> {projects.map((project) => ( <section key={project.id}> <h2>{project.name}</h2> <TaskList tasks={project.tasks} /> </section> ))} </div> );}Launch
Retrospective
Keys are scoped to siblings. Project sections need keys among project sections, and task rows need keys among task rows.
Details to watch
- Scope: Keys only need to be unique among siblings, not globally unique across the whole app.
- Placement: The
keybelongs on the element returned by the map, not inside the child component it calls. - Index keys: Indexes describe position, not identity. They fit static lists such as three fixed footer links.
- State preservation: Changing a key tells React that this is a different component instance and resets state below it.
Series navigation
- Previous: Part 2: Props, children, and component boundaries
- Next: Part 4: Events and local state
- Series index: Modern React development