Conditional rendering

Show different markup with a ternary, &&, or an early return, since JSX is just an expression.

Signature
{condition ? <A /> : <B />}   {condition && <A />}

Parameters

ParameterTypeDescription
$conditionanyThe tested expression. With && the whole expression evaluates to the left operand when it is falsy, which is why numbers on the left are dangerous.
$branchReactNodeWhat 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 if above 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

Try it yourself
Loading editor…
Loading React…

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.

Example · jsx
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.

Example · jsx
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

  • Be the first to comment on this lesson.