React has four idiomatic patterns:
// 1. Logical && — render right side only when left is truthy
{isLoggedIn && <Dashboard />}
// 2. Ternary — render one of two options
{isLoggedIn ? <Dashboard /> : <Login />}
// 3. Variable / if-else above return
let content = isLoggedIn ? <Dashboard /> : <Login />
return <main>{content}</main>
// 4. Early return — bail out of the component entirely
if (!isLoggedIn) return <Login />
return <Dashboard />
Each has its place:
&&— one branch only, very readable when the condition is simple.- Ternary — exactly two branches.
- Variable / if-else — complex conditions with more than two outcomes.
- Early return — guard clause at the top when the component shouldn't render at all in certain states.
Rule of thumb: use the simplest form that's still readable. If a ternary has nested ternaries, it's time for an if-else or a variable.
JavaScript's && returns the last evaluated operand — not necessarily
a boolean. React renders whatever value appears on the right side of &&
when the left side is truthy.
const count = 5
return (
<div>
{count > 0 && <Badge count={count} />}
{/* → renders <Badge /> because (count > 0) is true */}
</div>
)
When the condition is false, && short-circuits and returns false.
React treats false as "render nothing" — no output.
Rule of thumb: {condition && <Element />} is idiomatic React. The
condition should be a boolean; if in doubt, cast it: {!!value && <X />}.
&& returns the left operand when it is falsy. If that operand is 0,
React renders the number 0 (since 0 is a valid renderable value).
const messages = [] // length is 0
// ❌ Renders "0" when messages is empty
return <div>{messages.length && <MessageList items={messages} />}</div>
// ✅ Force the left side to a boolean
return <div>{messages.length > 0 && <MessageList items={messages} />}</div>
// ✅ Or use Boolean()
return <div>{Boolean(messages.length) && <MessageList items={messages} />}</div>
// ✅ Or use a ternary
return <div>{messages.length ? <MessageList items={messages} /> : null}</div>
Rule of thumb: never use count && or array.length && directly.
Always compare: count > 0 && or array.length > 0 &&. The same trap
exists for any number that could be 0.
The ternary expression (condition ? a : b) is a JavaScript expression and
is legal inside JSX {}. It's the idiomatic way to render one of two
alternatives.
function AuthStatus({ isLoggedIn, username }) {
return (
<header>
{isLoggedIn
? <span>Welcome, {username}!</span>
: <a href="/login">Sign in</a>
}
</header>
)
}
Use null for the false branch when you want to render nothing:
{isAdmin ? <AdminPanel /> : null}
// Equivalent to:
{isAdmin && <AdminPanel />}
Rule of thumb: ternaries are great for exactly two outcomes. For more than two, a variable or a helper function is cleaner.
Choose based on the number of outcomes and complexity:
| Pattern | Best for |
|---|---|
&& |
Single optional element; condition is a simple boolean |
| Ternary | Exactly two alternatives; fits on one or two lines |
if/else above return |
Multiple alternatives; complex conditions; shared logic before branching |
Early return |
Guard clause — component has nothing to show in this state |
// && — simple optional
{hasError && <ErrorBanner message={error} />}
// Ternary — two outcomes
{loading ? <Spinner /> : <Content data={data} />}
// if/else — three outcomes
let body
if (loading) body = <Spinner />
else if (error) body = <Error message={error} />
else body = <Content data={data} />
return <main>{body}</main>
// Early return — guard clause
if (!user) return null
return <Profile user={user} />
Rule of thumb: default to simplicity. Reach for && first, ternary
for two branches, if/else for three or more. Never nest ternaries more
than one level deep.
Return null. React renders nothing to the DOM but the component remains
mounted — its useEffect hooks still run and refs still attach.
function Tooltip({ visible, text }) {
if (!visible) return null
return <div className="tooltip">{text}</div>
}
Other falsy values behave differently:
undefined— React 18+ allows returningundefinedfrom a component (was an error in older versions). Treated likenull.false— valid child; renders nothing.0— renders the digit0. Careful!- Empty string
''— renders nothing visible but creates a text node.
Rule of thumb: return null (not false or undefined) when you
want a component to render nothing — it's explicit and unambiguous.
- CSS
display:none— the element is in the DOM, painted, and React's tree, but invisible. State, refs, effects, and event listeners are all live. - Conditional rendering — the element is not in the DOM. When the condition becomes false, React unmounts the component: state is reset, effects clean up, and refs detach.
// CSS approach — DOM node always present, just hidden
<div style={{ display: isVisible ? 'block' : 'none' }}>
<ExpensiveWidget /> {/* still mounted, effects running */}
</div>
// Conditional — component unmounts when isVisible is false
{isVisible && <ExpensiveWidget />}
Use cases:
- Prefer conditional rendering when you want fresh state on each appearance.
- Prefer
display:nonewhen mounting/unmounting is expensive (e.g. a heavy widget) or when you must preserve internal state across hides.
Rule of thumb: default to conditional rendering. Use CSS visibility only when remounting is measurably expensive or state preservation is required.
Use a ternary or template literal inside className:
// Ternary — toggle between two class names
<button className={isActive ? 'btn btn-active' : 'btn'}>Click</button>
// Template literal — build up from base + optional modifier
<div className={`card ${isHighlighted ? 'card-highlighted' : ''}`}>…</div>
// For multiple conditional classes, clsx/classnames library is cleaner
import clsx from 'clsx'
<div className={clsx('card', { 'card-highlighted': isHighlighted, 'card-error': hasError })}>
…
</div>
Avoid complex string concatenation inline — it becomes unreadable quickly.
Rule of thumb: for more than one or two conditional classes, add clsx
(or classnames) to the project — it handles undefined/false values
cleanly and the API is easy to scan.
Extract the guard logic into a dedicated component or hook to keep the rendering logic clean.
// Simple inline
{user.role === 'admin' && <AdminPanel />}
// Reusable gate component
function Can({ role, children }) {
const { user } = useAuth()
return user.role === role ? children : null
}
// Usage
<Can role="admin">
<DeleteButton />
</Can>
// Or a hook
function usePermission(requiredRole) {
const { user } = useAuth()
return user.role === requiredRole
}
const canDelete = usePermission('admin')
{canDelete && <DeleteButton />}
Rule of thumb: keep auth/permission logic out of individual components.
A <Can> component or usePermission hook keeps it testable and reusable
across the app.
Yes, but a switch statement can't go directly inside JSX. Extract it into a helper function or variable.
function StatusBadge({ status }) {
function renderBadge() {
switch (status) {
case 'success': return <span className="badge green">Success</span>
case 'error': return <span className="badge red">Error</span>
case 'loading': return <Spinner />
default: return null
}
}
return <div className="status">{renderBadge()}</div>
}
An object map is often cleaner than switch for this pattern:
const BADGE = {
success: <span className="badge green">Success</span>,
error: <span className="badge red">Error</span>,
loading: <Spinner />,
}
return <div>{BADGE[status] ?? null}</div>
Rule of thumb: object maps are more idiomatic React than switch
statements for rendering; switch is fine when you need fallthrough or break
behavior.
Extract the logic to a variable, early return, or helper function before
the return.
// ❌ Nested ternary — hard to read
return (
<div>
{loading
? <Spinner />
: error
? <Error message={error} />
: data
? <DataView data={data} />
: <Empty />}
</div>
)
// ✅ Variable approach
let content
if (loading) content = <Spinner />
else if (error) content = <Error message={error} />
else if (data) content = <DataView data={data} />
else content = <Empty />
return <div>{content}</div>
// ✅ Helper component
function AsyncContent({ loading, error, data }) {
if (loading) return <Spinner />
if (error) return <Error message={error} />
if (!data) return <Empty />
return <DataView data={data} />
}
Rule of thumb: if a ternary needs to be nested, refactor to if/else or extract a component. A long chain of nested ternaries is a refactoring signal, not a clever trick.
The guard-clause / early-return pattern keeps the happy path last:
function UserProfile({ userId }) {
const { data: user, loading, error } = useUser(userId)
if (loading) return <Skeleton />
if (error) return <ErrorMessage error={error} />
if (!user) return null
// Happy path — no nesting, no clutter
return (
<article>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</article>
)
}
This pattern is sometimes called "bail early, render late." Each guard handles one sad path and returns, leaving the main render clean.
Rule of thumb: load state first, error state second, empty/null state third, happy path last. This order prevents accidentally rendering undefined properties.
Short-circuit evaluation means JavaScript stops evaluating an expression as soon as the result is determined:
- In
A && B— ifAis falsy,Bis never evaluated. - In
A || B— ifAis truthy,Bis never evaluated.
React leverages && for conditional rendering because when the condition
is false, the right-hand component is not evaluated (and thus not rendered):
// UserMenu is never evaluated when isLoggedIn is false
{isLoggedIn && <UserMenu user={user} />}
// Useful for expensive renders or components that crash on missing data
{user && user.isAdmin && <AdminPanel />}
Rule of thumb: short-circuit with && when you have one optional
element and no fallback. Use || for default values, not for conditional
rendering — {value || <Fallback />} renders <Fallback /> whenever
value is falsy (including 0 and '').
?? returns the right operand only when the left is null or undefined
(not for 0, '', or false). This makes it safer than || for default
values.
// || treats 0 and '' as falsy — may swallow valid values
<p>Score: {score || 'No score'}</p>
// If score is 0, renders "No score" — wrong!
// ?? treats only null/undefined as "missing"
<p>Score: {score ?? 'No score'}</p>
// If score is 0, renders "0" — correct
// Common in JSX for optional labels, counts, or display values
<h2>{user.displayName ?? user.email}</h2>
Rule of thumb: use ?? for "provide a fallback when the value is
missing." Use || only when you intentionally want 0, false, and ''
to also trigger the fallback.
React.lazy lets you load a component's code on demand. Suspense
provides a fallback to show while the lazy component loads — effectively
a built-in conditional-rendering pattern for async code splitting.
import { lazy, Suspense } from 'react'
const AdminPanel = lazy(() => import('./AdminPanel'))
function App({ user }) {
return (
<Suspense fallback={<Spinner />}>
{user.isAdmin && <AdminPanel />}
</Suspense>
)
}
When <AdminPanel /> is first rendered, React suspends, the nearest
<Suspense> shows <Spinner />, and the bundle chunk loads in the
background. On completion React retries the render and shows the panel.
Rule of thumb: combine lazy + Suspense with normal conditional
rendering. Suspense handles the loading state of the import; your
&& or ternary handles the permission or visibility condition.
More Components interview questions
More ways to practice
The self-quiz is live. Get notified when mock interviews and new question packs drop.