Conditional Rendering and Lists
Conditional Rendering Is Just JavaScript
There is no v-if or ngIf in React. Conditional rendering uses plain JavaScript expressions inside JSX. This is a direct consequence of JSX being function calls, not templates.
function Dashboard({ user }) {
// Approach 1: Ternary (inline)
return (
<div>
{user.isAdmin ? <AdminPanel /> : <UserPanel />}
</div>
);
// Approach 2: Logical AND (show or nothing)
return (
<div>
{user.isAdmin && <AdminPanel />}
</div>
);
// Approach 3: Early return (entire component)
if (!user) return <LoginPrompt />;
return <Dashboard user={user} />;
}
Think of conditional rendering as a switch on a railroad track. The train (render cycle) follows one path or another based on the switch position (condition). The path not taken does not exist — React does not create elements for the false branch. When the switch flips, React tears down the old path and builds the new one. If you use a key prop, you can force React to rebuild even when the switch stays on the same track.
The && Trap: Rendering 0
The most common conditional rendering bug:
function Notifications({ count }) {
// BUG: When count is 0, this renders "0" on screen
return <div>{count && <Badge count={count} />}</div>;
// 0 is falsy, so count && <Badge /> evaluates to 0.
// React renders the number 0 as text.
}
JavaScript's && operator returns the first falsy value, not false. When count is 0, the expression evaluates to 0 — and React renders 0 as a text node.
// Fix 1: Explicit boolean comparison
{count > 0 && <Badge count={count} />}
// Fix 2: Double negation
{!!count && <Badge count={count} />}
// Fix 3: Ternary
{count ? <Badge count={count} /> : null}
This bug also affects empty strings and NaN. {'' && <Component />} renders an empty string (invisible but present in the DOM). {NaN && <Component />} renders the text "NaN". Always use explicit boolean checks: {items.length > 0 && <List />}, not {items.length && <List />}.
Lists and the Key Prop
When rendering arrays, React needs a way to track which items changed, were added, or were removed. The key prop is that tracking mechanism:
function TodoList({ todos }) {
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
How Keys Drive Reconciliation
When a list re-renders, React compares old and new element arrays by key:
// Previous render:
[
{ key: 'a', type: 'li', props: { children: 'Apple' } },
{ key: 'b', type: 'li', props: { children: 'Banana' } },
{ key: 'c', type: 'li', props: { children: 'Cherry' } },
]
// Next render (item 'b' removed):
[
{ key: 'a', type: 'li', props: { children: 'Apple' } },
{ key: 'c', type: 'li', props: { children: 'Cherry' } },
]
// React's reconciliation:
// - key 'a': exists in both → update props if changed
// - key 'b': exists in old, missing in new → unmount
// - key 'c': exists in both → update props if changed
Without keys, React falls back to comparing by position. Insert an item at the beginning and every element after it gets updated or remounted — even if nothing changed about them.
Why Index as Key Is Dangerous
// DANGEROUS — using index as key:
{items.map((item, index) => (
<li key={index}>{item.name}</li>
))}
The problem becomes visible when items are reordered, inserted, or deleted:
// Initial state (index as key):
// key=0: Apple key=1: Banana key=2: Cherry
// User deletes "Apple" (index 0):
// key=0: Banana key=1: Cherry
// React's reconciliation sees:
// - key 0: was "Apple", now "Banana" → UPDATE props (reuse DOM node)
// - key 1: was "Banana", now "Cherry" → UPDATE props (reuse DOM node)
// - key 2: was "Cherry", now missing → UNMOUNT
// Result: React reuses the DOM nodes of Apple and Banana,
// just changing their text content. This BREAKS if those
// nodes have internal state (input values, focus, animations).
The index-key data corruption scenario
Consider a list of controlled inputs:
function EditableList({ items }) {
return items.map((item, index) => (
<input key={index} defaultValue={item.name} />
));
}If you delete the first item, the input that had "Apple" (key=0) now gets "Banana" as its item. But the DOM input element still contains the user's typed text for "Apple" because React reused the node (same key). The displayed value does not match the data. This is a data corruption bug that is nearly impossible to diagnose without understanding keys.
Use stable, unique IDs: key={item.id}. If your data has no IDs, generate them when the data is created — not during render.
When Index Keys Are Safe
Index keys are acceptable only when ALL three conditions are true:
- The list is static (never reordered, filtered, or sorted)
- Items have no internal state (no inputs, no animations, no focus)
- Items are never inserted or deleted from the middle
In practice, these conditions rarely hold in production. Default to stable IDs.
Production Scenario: Conditional Loading States
function SearchResults({ query }) {
const [results, setResults] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// ... fetch logic
// Ordered conditions: error > loading > empty > results
if (error) {
return <ErrorState message={error.message} onRetry={retry} />;
}
if (loading) {
return <SkeletonLoader count={5} />;
}
if (!results || results.length === 0) {
return <EmptyState query={query} />;
}
return (
<ul>
{results.map(result => (
<SearchResultCard key={result.id} result={result} />
))}
</ul>
);
}
-
Wrong: Using && with values that can be 0, '', or NaN Right: Use explicit boolean checks:
count > 0 && <Component /> -
Wrong: Using array index as key for dynamic lists Right: Use stable unique IDs:
key={item.id} -
Wrong: Generating keys during render:
key={Math.random()}Right: Generate IDs when data is created, not during render -
Wrong: Forgetting the key prop in mapped arrays Right: Always provide a key on the outermost element returned from .map()
- 1Conditional rendering is plain JavaScript — ternary, &&, early return. No template directives.
- 2Watch for && with 0, '', NaN — they render as text. Use explicit boolean comparisons.
- 3Keys must be stable, unique, and predictable — use data IDs, never Math.random() or index for dynamic lists.
- 4Index keys are only safe for static lists with no state and no reordering.
- 5Keys tell React which items are the same across renders — wrong keys cause state corruption.