What Is Conditional Rendering in React?
Advertisement
🧩 Scenario
Architecture Walkthrough
Why React Has No Template Directives
React chose JavaScript over template syntax intentionally. JSX compiles to React.createElement calls, and inside any JSX expression you can use any JavaScript expression that returns a renderable value: a string, a number, a React element, an array of elements, null, or undefined. This means all JavaScript control flow tools (if/else, ternary, logical operators) work directly, with no new syntax to learn.
The constraint is that JSX curly braces accept expressions, not statements. {if (...) {...}} is invalid because if is a statement. Ternary (? :) and logical operators (&&, ||) work because they are expressions that produce values.
Guard Clauses with Early Return
The most readable pattern for loading, error, and unauthorized states is an if statement with an early return before the main return. Each condition returns immediately and the main return handles the success path. This keeps the primary render clean with no nesting.
This pattern is called a guard clause. It is especially readable when there are three or more early exits, because each is a flat, unconditional return at the top of the function, making the happy path obvious at a glance.
Ternary and Logical AND
Ternary handles two-branch conditions inline: {isLoading ? <Spinner /> : <Content />}. It explicitly handles both the true and false case. For "show this or nothing," && is the shorthand: {isLoggedIn && <UserMenu />}.
The critical && gotcha: if the left operand is a falsy number like 0, React renders it as text. {items.length && <List />} renders 0 when the array is empty. Fix it with a boolean conversion: {items.length > 0 && <List />}.
Key Code Explained
// Pattern 1: if/else with early return — guard clauses
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading, error } = useUser(userId);
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
if (!user) return null;
// Main return: success path — no nesting, no ternary
return (
<div className="profile">
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// Pattern 2: ternary — two branches inline in JSX
function AuthButton({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
<button onClick={isLoggedIn ? logout : login}>
{isLoggedIn ? 'Sign Out' : 'Sign In'}
</button>
);
}
// Pattern 3: logical AND — "render if true, nothing if false"
function Notification({ count }: { count: number }) {
return (
<div className="icon-wrapper">
<BellIcon />
{/* BUG: renders literal "0" when count is 0 */}
{count && <span className="badge">{count}</span>}
{/* CORRECT: convert to boolean first */}
{count > 0 && <span className="badge">{count}</span>}
</div>
);
}
// Pattern 4: return null — component contributes nothing to the DOM
function AdminBadge({ role }: { role: string }) {
if (role !== 'ADMIN') return null;
return (
<span className="badge badge-admin">Admin</span>
);
}
// Real-world: composing all four patterns
function Dashboard({ userId }: { userId: string }) {
const { data, isLoading, error } = useDashboard(userId);
const { isAdmin } = useUserSession();
if (isLoading) return <DashboardSkeleton />; // guard clause
if (error) return <ErrorMessage error={error} />; // guard clause
return (
<div>
<header>
<h1>{data.greeting}</h1>
{isAdmin && <AdminBadge role="ADMIN" />} {/* && for boolean-gated UI */}
</header>
<main>
{data.items.length > 0 {/* ternary for list vs empty state */}
? <ItemList items={data.items} />
: <EmptyState />
}
</main>
</div>
);
}
Guard clauses handle loading and error at the top because these are not binary choices: they are exits. Ternary handles the list-vs-empty choice inside the return because both branches produce meaningful UI. Logical AND handles the admin badge because showing nothing when not admin is the correct silent behavior.
Tradeoffs
| Pattern | Branches | Where it lives | Best for |
|---|---|---|---|
| if/else + early return | 2+ flat | Before return | Loading, error, unauthorized guard clauses |
| Ternary (? :) | Exactly 2 | Inside JSX | Binary inline conditions |
| Logical AND (&&) | 1 (true) | Inside JSX | "Show this or nothing" — watch for falsy 0 |
| return null | 1 (false) | Inside component body | Component should not exist in the DOM |
What Interviewers Actually Check
- Whether you know all four patterns and can choose the right one for the situation
- Whether you know the falsy 0 bug with
&&and can fix it - Whether you know that returning
nullis how to render nothing - Whether you avoid nested ternaries for three or more branches
- Whether you know why
ifstatements cannot go inside JSX{}(statements vs expressions)
Follow-Up Questions
- Why does
{items.length && <List />}render0when items is empty, and why does{Boolean(items.length) && <List />}not? - How would you conditionally render a component only on the server in Next.js App Router?
- What is the
??(nullish coalescing) operator and when is it useful for conditional rendering compared to||? - Can you use a
switchstatement for conditional rendering? If not directly in JSX, how do you achieve it? - How does
React.lazyandSuspenserelate to conditional rendering for code-split components?
Common Candidate Mistakes
- Writing
{count && <Badge count={count} />}and not knowing it renders0in the DOM when count is 0 - Using deeply nested ternaries for three or more conditions when
if/elsebeforereturnis cleaner - Not knowing that returning
nullis valid and removes the component from the DOM entirely - Trying to use
ifstatements directly inside JSX{}and being surprised it does not work - Reaching for CSS
display: nonewhen the component should not be mounted at all
Interview Readiness Checklist
Before you leave this question, make sure you can answer:
- Can you use
if/elsewith early return for guard clauses before the main return? - Can you use ternary for two-branch inline conditions inside JSX?
- Can you use
&&correctly and explain the falsy 0 bug and its fix? - Can you return
nullto render nothing and explain when to prefer it over&&in the parent? - Can you explain why
ifstatements are not allowed inside JSX{}but ternary and&&are?
Summary
Conditional rendering in React is plain JavaScript control flow applied to JSX. React has no template directives; instead, any JavaScript expression that returns a renderable value can appear inside JSX curly braces. This means ternary, logical operators, and values from if/else assigned to variables all work directly.
The four patterns cover all cases. Guard clauses (if/return before the main return) handle loading, error, and unauthorized states flatly with no nesting. Ternary handles two-branch inline conditions inside JSX. Logical AND (&&) handles "show if true or nothing" with the critical caveat that falsy numbers like 0 render as text: always convert to a boolean before the &&. Returning null from a component renders nothing and removes the component from the DOM entirely, unlike CSS visibility which keeps the component mounted.
The most common interview mistake is the falsy 0 bug: {count && <Badge />} renders the number 0 when count is 0. The fix is {count > 0 && <Badge />}. The second most common is nested ternaries for three or more branches, which is a readability problem solved by if/else before the return statement.
Does React have a built-in conditional rendering directive like v-if or *ngIf?
No. React uses plain JavaScript expressions inside JSX. There is no template syntax — just if/else, ternary, and logical operators applied to JSX values.
Advertisement
Stay Updated
Get the latest frontend challenges, interview questions and tutorials delivered to your inbox.
Advertisement