Lists Keys and Conditionals
Patterns for collections, identity, empty states, and branching UI without messy nested ternaries.
Search across all documentation pages
Patterns for collections, identity, empty states, and branching UI without messy nested ternaries.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
map turns data into elements. Each child in the array needs a key so React can match identities across updates.
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);Prefer business keys (database ids, slugs) that stay stable for the same logical item as the list reorders or filters.
{posts.map((post) => (
<ArticleCard key={post.id} post={post} />
))}Index keys confuse identity when items insert, delete, or reorder - local state and focus jump to the wrong row.
// Avoid when the list can reorder:
// items.map((item, index) => <Row key={index} item={item} />)Derive the visible list first, then map. Keep filter logic out of JSX attribute soup.
const active = items.filter((i) => i.active);
return active.map((i) => <Row key={i.id} item={i} />);Handle zero-length collections explicitly so users see guidance instead of a blank region.
if (items.length === 0) return <p>No results.</p>;
return <ItemList items={items} />;Key each level with its own stable id. Do not reuse parent keys for children.
{sections.map((s) => (
<section key={s.id}>
<h2>{s.title}</h2>
{s.items.map((item) => (
<Item key={item.id} item={item} />
))}
</section>
))}Early returns for loading and error keep the main success tree flat and readable.
if (error) return <ErrorBanner error={error} />;
if (isLoading) return <Spinner />;
return <Profile user={user} />;Map a status string (or discriminant) to UI with a dictionary or switch for clarity over nested ternaries.
const view = {
idle: <Idle />,
loading: <Spinner />,
ready: <DataView data={data} />,
}[status];
return view;Guard nested fields when data may be partial. Combine with fallbacks for display text.
return <p>{user?.profile?.bio ?? "No bio yet."}</p>;?? falls back only for null or undefined, preserving empty strings or 0 when those are valid.
const label = title ?? "Untitled";
const count = total ?? 0;When each item renders multiple siblings, wrap them in a keyed Fragment.
import { Fragment } from "react";
{rows.map((row) => (
<Fragment key={row.id}>
<dt>{row.label}</dt>
<dd>{row.value}</dd>
</Fragment>
))}Sort a copy (toSorted or [...arr].sort) so you never mutate state or props in place.
const sorted = [...items].sort((a, b) => a.name.localeCompare(b.name));For thousands of rows, window the list (e.g. a virtualization library). React will not invent windowing for you.
// When items.length is huge, render only the visible window
// via a list virtualizer instead of mapping the full array.Update one item immutably, then map - keys keep row state aligned with the right entity.
setItems((items) =>
items.map((i) => (i.id === id ? { ...i, done: !i.done } : i)),
);Reduce items into groups, then map groups and their children for section headers.
const groups = Map.groupBy(items, (i) => i.category);
return [...groups].map(([cat, list]) => (
<section key={cat}>
<h2>{cat}</h2>
{list.map((i) => <Row key={i.id} item={i} />)}
</section>
));Stack versions: React 19 · TypeScript (strict) · stable keys over indexes
Reviewed by Chris St. John·Last updated Jul 18, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥