π Conditional Rendering Patterns
A component rarely shows the same thing every time. Logged in or out? Loading, loaded, or errored? Cart empty or full? React has no <if> tag β instead you use plain JavaScript to decide what JSX to return. This lesson covers the four patterns that handle every case: the && operator, the ternary, early returns, and element variables.
Week 4 · Day 4 (Thursday: Lists and Conditional Rendering) · Lecture 3
π― Learning Objectives
By the end of this lesson, you will be able to:
- Show or hide an element with the
&&operator β and avoid its falsy-0trap - Choose between two branches inline with the ternary operator
- Use early returns to peel off loading, error, and empty states cleanly
- Assign JSX to an element variable to keep complex
returnmarkup readable - Render nothing safely by returning
null - Pick the right pattern for a given situation and avoid deeply nested ternaries
Estimated Time: 55 minutes
Practice: Build a data view that switches between loading, error, empty, and content states.
In This Lesson
Why Conditional Rendering?
Your UI is a function of state. When state says "still loading," you show a spinner; when it says "here's the data," you show the list; when it says "something broke," you show an error. Because JSX is just JavaScript expressions, you steer it with the same tools you already know: &&, the ternary ? :, if statements, and variables.
The trick is knowing which of the four patterns fits. A one-sided "show this only if" wants &&. A two-sided "this or that" wants a ternary. A whole component that branches into several full-screen states wants early returns. And a chunk of markup you want to compute before the return wants an element variable. Let's take them one at a time.
Pattern 1: The && Operator
Use condition && <element> when you want to render something only when a condition is true, and render nothing otherwise. It works because of how JavaScript's && evaluates: if the left side is truthy, the expression becomes the right side; if the left side is falsy, the expression becomes the left side (which React ignores when it is false, null, or undefined).
function Inbox({ unreadCount, isAdmin }) {
return (
<header>
<h1>Inbox</h1>
{/* Renders the badge only when there are unread messages */}
{unreadCount > 0 && (
<span className="badge">{unreadCount} new</span>
)}
{/* Renders the admin link only for admins */}
{isAdmin && <a href="/admin">Admin</a>}
</header>
);
}
β οΈ The falsy-0 trap β the classic beginner bug
If the left side is a number that can be 0, you will render a literal 0 on the page. React renders false/null/undefined as nothing β but 0 is a real value it happily displays.
// β When items.length is 0, this puts a stray "0" on screen.
{items.length && <ItemList items={items} />}
// β
Force a real boolean on the left side.
{items.length > 0 && <ItemList items={items} />}
// β
Or coerce with a double-bang.
{!!items.length && <ItemList items={items} />}
Rule of thumb: never put a bare number on the left of && in JSX. Compare it (> 0) so the left operand is always true or false.
You can stack several independent && lines for a component that layers optional pieces:
function Profile({ user, isLoading, error }) {
return (
<div>
{isLoading && <Spinner />}
{error && <ErrorBanner message={error} />}
{user && !isLoading && !error && (
<section>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</section>
)}
</div>
);
}
That last block hints at a smell: when you find yourself writing user && !isLoading && !error, the states are really mutually exclusive, and early returns (Pattern 3) will read far better.
Pattern 2: The Ternary
Use condition ? <a> : <b> when you must choose between two alternatives β an either/or. Unlike &&, the ternary always renders one branch or the other, so it is the tool for "this or that," including toggling a className.
function ConnectionStatus({ isOnline }) {
return (
<span className={isOnline ? 'status online' : 'status offline'}>
{isOnline ? 'Online' : 'Offline'}
</span>
);
}
function AuthButton({ user, onLogin, onLogout }) {
return user
? <button onClick={onLogout}>Log out</button>
: <button onClick={onLogin}>Log in</button>;
}
Ternaries shine for small inline choices. They become unreadable when nested. This works but nobody enjoys maintaining it:
// β Nested ternaries β hard to read, easy to break.
function Dashboard({ user }) {
return (
<div>
{user ? (
user.isAdmin ? <AdminPanel /> : <UserPanel />
) : (
<GuestPanel />
)}
</div>
);
}
β Flatten nested ternaries into early returns
function Dashboard({ user }) {
if (!user) return <GuestPanel />;
if (user.isAdmin) return <AdminPanel />;
return <UserPanel />;
}
Same logic, read top to bottom like a checklist. Reserve ternaries for a single, shallow either/or; reach for early returns the moment a second condition appears.
Pattern 3: Early Returns
When a whole component swaps between several distinct, full states, put if statements before the main return and return early for each special case. Each guard handles one case and exits, so by the time execution reaches the bottom you are on the guaranteed "happy path." This is the cleanest pattern for the loading/error/empty/content quartet you will write constantly.
import { useState, useEffect } from 'react';
function OrdersView() {
const [orders, setOrders] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchOrders()
.then(setOrders)
.catch((err) => setError(err.message))
.finally(() => setLoading(false));
}, []);
// Each guard peels off one state and returns.
if (loading) return <Spinner label="Loading ordersβ¦" />;
if (error) return <ErrorBanner message={error} />;
if (orders.length === 0) return <EmptyState label="No orders yet" />;
// Happy path: we know orders is a non-empty array.
return (
<ul>
{orders.map((order) => (
<li key={order.id}>{order.summary}</li>
))}
</ul>
);
}
π‘ Guards must come after all Hooks
The Rules of Hooks require every useState/useEffect to run on every render, in the same order. So call your Hooks first, at the top of the component, and only then do your early returns. Never put a return above a Hook, or React will complain about a changing hook order.
Pattern 4: Element Variables
Sometimes you want to compute a piece of markup with ordinary if logic, then drop it into a larger layout that is shared across all cases. Assign the JSX to a variable before the return, then reference {variable} inside the markup. This keeps the surrounding structure (a header, a footer, a wrapper) written once, with only the middle varying.
function MessagePanel({ status, data }) {
// Build the body with plain if/else β no JSX gymnastics.
let body;
if (status === 'loading') {
body = <Spinner />;
} else if (status === 'error') {
body = <p role="alert">Something went wrong.</p>;
} else {
body = <DataTable rows={data} />;
}
// The wrapper is written once; only `body` changes.
return (
<section className="panel">
<h2>Activity</h2>
{body}
<footer>Last updated just now</footer>
</section>
);
}
A close cousin is the object-map (lookup) pattern, which replaces a long switch with a plain object keyed by the state value:
function StatusIcon({ status }) {
const icons = {
success: <CheckIcon />,
warning: <WarnIcon />,
error: <ErrorIcon />,
};
// Fall back to a default for any unknown status.
return icons[status] ?? <InfoIcon />;
}
π When to reach for element variables
Use an element variable when the branching logic is more than a line or two and the result sits inside shared surrounding markup. If the whole component just becomes a different thing per state, early returns are simpler. If you're picking one of many by a key, the object-map is tidiest.
Rendering Nothing
A component is allowed to render nothing: return null and React renders an empty result (no DOM). This is how a component politely opts out.
function WarningBanner({ show, children }) {
if (!show) return null; // render nothing at all
return <div className="warning">{children}</div>;
}
β οΈ Returning null vs not mounting
Returning null still mounts the component and runs its Hooks β it just outputs nothing. That is fine and often desirable. But if the component is expensive, consider not rendering it at all from the parent ({show && <Expensive />}) so it never mounts in the first place. Do not return false or undefined expecting the same clean result in every context β null is the explicit, intentional choice.
Choosing a Pattern
A quick decision guide you can keep in your head:
| Situation | Pattern | Example |
|---|---|---|
| Show something only if true (one-sided) | && | {isAdmin && <AdminLink />} |
| Choose between exactly two options | Ternary | {on ? 'On' : 'Off'} |
| Whole component has several full states | Early returns | if (loading) return β¦ |
| Compute markup, drop into shared layout | Element variable | let body = β¦; return <div>{body}</div> |
| Pick one of many by a key | Object map | icons[status] |
| Render nothing | return null | if (!show) return null; |
These are not rigid rules β they are the readable default for each shape of problem. The guiding principle is the same one you have met all week: keep the branching logic obvious, and keep JSX shallow.
Practice & Quiz
ποΈ Exercise 1: Fix the zero trap
Goal: This cart badge prints a stray "0" when the cart is empty. Fix it so it renders nothing when there are no items, and the count otherwise.
function CartBadge({ items }) {
return (
<div className="cart">
π
{items.length && <span className="count">{items.length}</span>}
</div>
);
}
π‘ Hint
The left operand of && is a bare number that can be 0. Turn it into a real boolean with a comparison.
β Solution
function CartBadge({ items }) {
return (
<div className="cart">
π
{items.length > 0 && (
<span className="count">{items.length}</span>
)}
</div>
);
}
ποΈ Exercise 2: Four-state data view
Goal: Given loading, error, and users (an array), render a spinner, an error message, an empty state, or the list β using early returns.
β Solution
function UserList({ loading, error, users }) {
if (loading) return <p>Loadingβ¦</p>;
if (error) return <p role="alert">Error: {error}</p>;
if (users.length === 0) return <p>No users found.</p>;
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
π― Quick Quiz
Question 1: Which pattern best fits "show this element only when the user is an admin"?
Question 2: Why does {count && <Badge />} sometimes render a literal 0?
Question 3: You have a component that must show loading, error, empty, and content states. What reads best?
Best Practices & Pitfalls
β Do
- Use
&&for one-sided show/hide, ternary for a single either/or - Prefer early returns for mutually exclusive full-component states
- Put a comparison (
> 0) on the left of&&when the value could be0 - Extract complex branching into a helper function, element variable, or object map
- Call all Hooks before any early return
β Don't
- Nest ternaries more than one level deep
- Put a bare number on the left of
&&in JSX - Return
undefinedwhen you mean "render nothing" β returnnull - Place a
returnabove auseState/useEffectcall
β οΈ Mount vs hide
// Unmounts when hidden β loses internal state each time.
{show && <Panel />}
// Stays mounted, just visually hidden β keeps state, costs memory.
<div style={{ display: show ? 'block' : 'none' }}>
<Panel />
</div>
Conditional rendering unmounts the hidden branch, which resets its state and stops its effects. If you need a frequently toggled panel to remember scroll position or input, hide it with CSS instead of unmounting it.
Summary
π Key Takeaways
&&renders one-sided "only if true" β but guard against the falsy-0trap with a comparison- Ternary picks between exactly two branches; keep it shallow, never nested
- Early returns handle several mutually exclusive states and read like a checklist
- Element variables (and object maps) compute markup with plain
iflogic for a shared layout - Return
nullto render nothing; remember Hooks run before any early return
π Additional Resources
- react.dev β Conditional Rendering
- react.dev β The logical AND operator and the 0 pitfall
- MDN β Conditional (ternary) operator
π What's Next?
You can now render the right amount of UI (lists) and the right UI for the state (conditionals). Next you'll learn how React encourages you to build UI: Composition vs inheritance β why React apps combine small components with children and props instead of extending classes, and how that keeps your component tree flexible.
π Four patterns, mastered!
Show, hide, branch, and swap states with confidence β and you'll never ship a stray 0 again.