AniUI Academy

Conditional Rendering

The patterns for showing different UI based on data — the ternary, the &&; shortcut and its most infamous gotcha, early returns, and rendering nothing at all.

8 min read

Almost every component needs to show different things depending on data: a loading spinner versus real content, an error message versus none, a badge that only appears sometimes. JSX has no dedicated "if tag" — you reach for plain JavaScript, arranged around the fact that {} in JSX only accepts expressions.

The ternary, for either/or

When there are exactly two outcomes, the conditional (ternary) operator reads naturally inline:

function Status({ isOnline }) {
  return <p>{isOnline ? "Online" : "Offline"}</p>;
}

This works because condition ? a : b is an expression — it evaluates to a value, which is exactly what {} needs.

&&, for "render this or nothing"

When there's only one meaningful outcome and the other case is "show nothing," && is the common shorthand:

function Inbox({ unreadCount }) {
  return (
    <div>
      <h2>Inbox</h2>
      {unreadCount > 0 && <span className="badge">{unreadCount} new</span>}
    </div>
  );
}

&& evaluates its left side; if that's falsy, it returns the left side without evaluating the right side at all (short-circuiting), and if it's truthy, it evaluates and returns the right side. React renders false (and null/undefined) as nothing, so unreadCount > 0 && <span>... reads as "if there are unread messages, show the badge; otherwise render nothing."

The stray-zero trap

Here's the exact same pattern with a subtle but common mistake:

// Looks reasonable, has a bug
{unreadCount && <span className="badge">{unreadCount} new</span>}

When unreadCount is 0, && returns 0 — because 0 is falsy, so short-circuiting stops there and returns it as-is. But React renders numbers as text. The badge disappears (correctly), but a stray, unstyled 0 appears on the page in its place. The fix is to make sure the left side of && is always an actual boolean, not a number that happens to be falsy:

{unreadCount > 0 && <span className="badge">{unreadCount} new</span>}

This is worth internalizing as a habit — put a comparison, not a raw number or string, on the left of && whenever the value could be 0 or "".

Early returns, for more complex branching

When there are several distinct states to handle — loading, error, empty, loaded — a chain of ternaries gets unreadable fast. Since a component is just a function, an ordinary early return works, and reads better than nesting:

function UserProfile({ status, user, error }) {
  if (status === "loading") {
    return <Spinner />;
  }
 
  if (status === "error") {
    return <ErrorMessage message={error} />;
  }
 
  if (!user) {
    return <p>No user found.</p>;
  }
 
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Each if is a statement living in the function body, outside any JSX — which is exactly why it's allowed, unlike trying to put if directly inside {}.

Rendering nothing

Sometimes the correct output really is nothing. Returning null (or false or undefined) is the normal way to say that:

function Banner({ dismissed }) {
  if (dismissed) {
    return null;
  }
  return <div className="banner">Some announcement</div>;
}

This isn't an error state or a special case to work around — it's a first-class, ordinary outcome, and the component will happily render something again the next time its props or state say it should.

Try it yourself

This component exercises all three patterns: an early return, a ternary, and a correctly-guarded &&.

Try it yourself
Loading playground...

What to remember

  • Ternaries handle either/or; && handles "render this, or nothing."
  • Guard && with a real boolean comparison (count > 0), not a raw number or string, or a falsy-but-nonzero-looking value like 0 renders as visible text.
  • For several distinct states, an early return outside the JSX is clearer than nested ternaries.
  • Returning null (or false/undefined) is the normal, first-class way to render nothing.

Check yourself

4 questions · pass 3/4 to unlock Handling Events

up to 50
  1. 1.Why can't you write if (isLoading) { return <Spinner />; } else { return <Content />; } directly inside JSX curly braces?

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

  3. 3.What does returning null from a component do?

  4. 4.Which fixes the count && <Badge /> stray-zero problem?

4 left to answer