- Home
- /
- Tutorials
- /
- React Tutorial
- /
- Conditional Rendering
Conditional Rendering
Conditional Rendering
There is no
ifinside JSX, because JSX takes expressions. Every technique here is a way of producing a value that is either some UI or nothing.
The three tools
Almost all conditional React is one of three shapes, and picking the right one is mostly about how many branches you have.
- Early return - the whole component renders something different, or nothing.
- Ternary - two alternatives in one spot.
&&- one thing or nothing.
All three, in one component
jsx
function Status({ state, count }) {
// 1. Early return: nothing to show at all.
if (state === "hidden") return null
return (
<div>
{/* 2. Ternary: one of two things */}
<p>{state === "busy" ? "Working…" : "Ready"}</p>
{/* 3. && : something or nothing */}
{count > 0 && <p>{count} pending</p>}
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<div>
<Status state="busy" count={3} />
<Status state="ready" count={0} />
<Status state="hidden" count={9} />
<p style={{ color: "#64748b" }}>Three renders above; the third produced nothing.</p>
</div>
)The && trap
&& returns its left value when that value is falsy - it does not return false. And 0 is falsy but React renders it, so {items.length && <List />} puts a bare 0 on the page when the list is empty.
Where the stray zero comes from
jsx
function Broken({ items }) {
// items.length is 0, so this renders 0 — not nothing.
return <div>Broken: [{items.length && <span>has items</span>}]</div>
}
function Fixed({ items }) {
// A real boolean on the left, and false renders nothing.
return <div>Fixed: [{items.length > 0 && <span>has items</span>}]</div>
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<div>
<Broken items={[]} />
<Fixed items={[]} />
</div>
)The rule: put a real boolean on the left of &&. Compare with > 0, or use Boolean(...), or use a ternary with an explicit null.
When to use an early return
If a condition changes the whole output, return early rather than wrapping the entire body in a ternary. It keeps the main path at the left margin and reads as a list of cases.
Guard clauses read better
jsx
function Profile({ user, error }) {
if (error) return <p style={{ color: "#b91c1c" }}>Could not load.</p>
if (!user) return <p>Loading…</p>
// The interesting path is not nested inside anything.
return (
<div>
<strong>{user.name}</strong>
<p>{user.role}</p>
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<div>
<Profile error={true} />
<Profile user={null} />
<Profile user={{ name: "Ada", role: "Engineer" }} />
</div>
)Keeping it readable
- Never nest ternaries. Two levels is already hard to read; three is unmaintainable.
- More than two branches: compute above the return, or use a lookup object.
- Extract a component when a branch grows past a few lines.
{condition ? : null}is fine and often clearer than&&.
A lookup instead of a ternary chain
jsx
function Message({ status }) {
const messages = {
idle: "Nothing to do",
loading: "Working…",
success: "All done",
error: "Something failed",
}
return <p>{messages[status] ?? "Unknown status"}</p>
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(
<div>
{["idle", "loading", "success", "error", "weird"].map((s) => (
<Message key={s} status={s} />
))}
</div>
)