React from the ground up

Conditional rendering

Showing one thing or another with the ordinary JavaScript operators, returning nothing on purpose, and the zero that renders when you least expect it.

Part 8 of 12

Beginner13 min readBack to Courses

Nothing new is needed

Conditions in JSX are the conditions you already know, because JSX is expressions. A ternary picks between two pictures: {isMember ? <Badge /> : <SignupLink />}. The && operator shows something or nothing: {items.length > 0 && <List items={items} />}.

That is the whole toolbox. There is no <If> component, and none is wanted — the language already has the operators, and the JSX only has to fit around them.

Nothing is a legal answer

null, undefined, false and true render nothing at all. A component may therefore return null when it has nothing to show — a badge with no count, an optional note, a wrapper with a closed panel — and React handles it without a wrapper element to clean up later.

It also means an early return is a clean way out of a render: check the empty case first, hand back null, and leave the interesting path unindented below. The reader of that function sees the two cases as two paragraphs instead of one nested expression.

The zero that shows up

One trap: {count && <Badge />} renders 0 when the count is zero. JavaScript's && returns the left side when it is falsy — and 0 is falsy but is still a value JSX will print. The fix is to compare properly: {count > 0 && <Badge />}, or use a ternary with null.

The same care applies to empty strings, which are falsy but printable, so {name && ...} shows nothing anyway — while {count && ...} shows the zero. Comparing is clearer than relying on truthiness, and it is exactly what a reader expects.

A ternary chooses a value, not a branch

The ternary is an expression, so it can sit inside another one, and inside a prop: <Badge tone={isNew ? "fresh" : "plain"} />. That is the useful case — choosing a value rather than a whole branch of markup. Once the branches contain elements, an early return or a small component reads better than a nested ? :.

A guard keeps the return flat

An early return is the plainest form of a condition: if (!user) return null; at the top, then the rest of the component written for a user that exists. Everything below the guard can use user.name without a check, and the reader does not have to hold the empty case in mind for the whole function.

The same shape works for a loading state and an error: return the spinner, return the message, and leave the successful path last and unindented.

Three ways to show or hide

function Status({ saved, error }) {
  // Early return: nothing to show is a legal render.
  if (!saved && !error) return null;

  return <p className="line">{error ? 'Could not save: ' + error : 'Saved.'}</p>;
}

function Cart({ items, isMember }) {
  return (
    <div>
      {/* && for show-or-nothing … */}
      {isMember && <span className="badge">member</span>}

      {/* … a ternary for one of two pictures … */}
      {items.length > 0 ? <p>{items.length} items</p> : <p>Your cart is empty.</p>}

      {/* … and a comparison where a count could be zero. */}
      {items.length > 0 && <button>Check out</button>}
    </div>
  );
}

What JSX does with the value

JSX does not have a special rule for conditions; it renders whatever the expression produces. Strings and numbers appear as text, elements become nodes, arrays become siblings, and null, undefined, false and true produce nothing. That short list explains almost every conditional pattern in React.

It also explains the odd cases. 0 and the empty string are printable, so {count && ...} shows a zero; an object is not printable, so {user && ...} throws when user is defined. Compare with a boolean and the question disappears.

Pick the shape that reads best

One branch that shows or hides is &&; two branches that choose a picture are a ternary; three or more are usually a small component or an early return. Nesting ternaries inside ternaries works, but the reader has to hold every condition at once, and that is the point where a function earns its name.

An early return is not a special React feature — it is JavaScript leaving the function. It reads as a guard: if there is nothing to show, say so first, and let the rest of the function assume there is something. The render stays flat and the conditions stay near the top.

Three conditional mistakes: {count && <Badge />} when count can be zero; a nested ternary that is correct but unreadable; and returning undefined from a component by accident — legal, but it hides a missing branch. If a piece is optional, make the null explicit so the next reader knows it was a decision.

Try these next

  • Add a third status to the tracker and a button that moves through all three.
  • Replace the badge && with a ternary that shows a muted "none" label instead.
  • Extract the status message into its own component and return null when it has nothing to say.

A cart with states

Run it, add and remove items, and watch the three states of the page. Then change the check-out line to use `count &&` and see the zero appear when the cart is empty.

Live Preview
Console output will appear here...

A delivery tracker with three states

Run it and move the parcel through its three states. Then add a fourth state — "returned" — with its own message and its own branch, and give it a different badge label.

Live Preview
Console output will appear here...

If a stray 0 ever appears on the page, this chapter is why: a count used directly with &&. Compare it with a real condition and the zero disappears.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does {count && <Badge />} render when count is 0?