Conditional rendering
Show different markup with a ternary, &&, or an early return, since JSX is just an expression.
{condition ? <A /> : <B />} {condition && <A />}Parameters
| Parameter | Type | Description |
|---|---|---|
$condition | any | The tested expression. With && the whole expression evaluates to the left operand when it is falsy, which is why numbers on the left are dangerous. |
$branch | ReactNode | What to render. Returning null from a component renders nothing while keeping the component mounted in the tree. |
Return value
ReactNodeThe chosen branch. false, null and undefined all render nothing, so they are valid things for a branch to produce.
Errors
Rendering a plain object — {user} instead of {user.name} — throws "Objects are not valid as a React child".
There is no special syntax for conditions in JSX. Because a JSX tag is an ordinary JavaScript value, you pick a branch with the same operators you would use anywhere else.
Picking a form
- Early return when the whole component differs:
if (!user) return <SignIn />;. Clearest for loading, error and empty states. - Ternary when both branches render something inside surrounding markup.
- && when the alternative is nothing at all.
- A variable assigned by an
ifabove the return, when the logic has more than two cases — statements cannot live inside the braces, but nothing stops you preparing the value first.
The falsy-zero trap
&& returns its left operand when that operand is falsy. React renders nothing for false, null and undefined — but 0 is a valid child, so {count && <Badge />} prints a bare 0 on an empty list. Put a comparison on the left: {count > 0 && <Badge />}. Empty strings behave the same way.
What trips people up
Swapping between different element types unmounts the old subtree and loses its state. Rendering the same component with different props, or with the same key, preserves it.
Example
When to use it
- Rendering a spinner while data loads and the list once it arrives
- Showing an unread badge only when there is something unread
- Swapping a Sign in link for an avatar menu
- Displaying a validation message under a field that failed
Frequently asked questions
Why is my component rendering a 0?
Because a number on the left of && makes the whole expression evaluate to that number, and React renders 0 as text. Change {items.length && <List />} to {items.length > 0 && <List />}.
Can I use an if statement inside JSX?
No — the curly braces take an expression, and if is a statement. Use a ternary, or run the if above the return and store the result in a variable that the JSX then embeds.
How do I render nothing?
Return null from the component, or let the branch evaluate to null, undefined or false — React skips all of them. Returning an empty fragment works too, but null is the conventional signal that the component is intentionally rendering nothing.
More examples
More than two cases: assign to a variable first
Four outcomes rendered without a nested ternary. if statements are not allowed inside JSX braces, but they are perfectly allowed above the return, and the result is assigned to a variable the JSX then embeds. This reads far better than chained ? : once there are three or more branches, and it makes each case easy to test.
function Status({ state, error }) {
let body;
if (state === "loading") {
body = <p>Loading…</p>;
} else if (state === "error") {
body = <p role="alert">Failed: {error}</p>;
} else if (state === "empty") {
body = <p>No results.</p>;
} else {
body = <p>Done.</p>;
}
return (
<section>
<h4>Report</h4>
{body}
</section>
);
}
export default function Dashboard() {
return (
<div>
<Status state="loading" />
<Status state="error" error="HTTP 500" />
<Status state="empty" />
<Status state="done" />
</div>
);
}Conditional attributes rather than conditional elements
One button element with conditional props, instead of two buttons behind a ternary — so the DOM node is reused, focus survives the state change, and there is no chance of the branches drifting apart. Note aria-busy={busy || undefined}: passing undefined removes the attribute entirely, while false would render aria-busy="false", which is a different thing to a screen reader.
function SubmitButton({ busy, invalid }) {
return (
<button
type="submit"
disabled={busy || invalid}
aria-busy={busy || undefined}
className={busy ? "btn btn-busy" : "btn"}
>
{busy ? "Saving…" : "Save"}
</button>
);
}
export default function Form() {
return (
<form onSubmit={(e) => e.preventDefault()}>
<SubmitButton busy={false} invalid={false} />
<SubmitButton busy={true} invalid={false} />
<SubmitButton busy={false} invalid={true} />
</form>
);
}
Discussion