skip to content

In a React component, `{cart.length && <CartSummary />}` puts a stray `0` on the page when the cart is empty. Why does that happen, and how do you fix it?

level: middleimportance: must knowfreq 78%

answer

  1. what React skips as a child
  2. truthiness is not the rule
  3. && hands back the left value
  4. zero is a number, numbers render
  5. compare, do not coerce

basics

~20 s

JavaScript's && returns its left operand when that operand is falsy, so an empty cart makes the expression evaluate to the number 0. React skips null, undefined and booleans as children but renders numbers as text, so the 0 appears. Guard with cart.length > 0 instead.

solid answer

~40 s

`&&` returns its **left** operand when that operand is falsy, so with an empty cart the expression evaluates to the number `0`, not to `false`. React's "render nothing" list is by type, not by truthiness: `null`, `undefined`, `true` and `false` produce no output, but numbers and strings are rendered as text — so the `0` lands in the DOM as a visible text node. The fix is to make the guard yield a real boolean: `cart.length > 0 && <CartSummary />`, or a ternary with an explicit `: null`. `Boolean(cart.length) &&` works too, but the direct comparison reads better and survives refactoring. The same trap bites any numeric guard, including `NaN`; a string guard leaks an invisible empty text node rather than a visible glyph, which is why it is usually reported with numbers.

code

jsx · 9 lines
jsx
function CartBadge({ cart }) {
  return (
    <div>
      {cart.length && <span>{cart.length} items</span>}
      {cart.length > 0 && <span>{cart.length} items</span>}
      {cart.length ? <span>{cart.length} items</span> : null}
    </div>
  );
}

go deeper

for a junior

Recognise the stray 0 on sight and know the safe spelling: compare explicitly, as in items.length > 0 && <List />, or use a ternary ending in : null.

for a middle

Explain both mechanisms in one breath — && returns its left operand, and React renders numbers and strings as text while skipping only null, undefined and booleans. Name which falsy values leak and which do not.

for a senior

Show how you keep it out of a codebase: prefer explicit comparisons over coercion so intent survives refactors, and point out that TypeScript will not flag it because number is a valid child. Mention the || and optional-chaining variants you have actually seen in production dashboards.

for a principal

Frame it as a class of defect rather than one bug: a silent rendering leak that types do not catch and that only shows in the empty-data state. Argue for a team convention — boolean-by-construction guards plus empty-state coverage in tests — instead of relying on reviewers to spot it.

## The shape of the bug ```jsx function Cart({ cart }) { return <div>{cart.length && <CartSummary cart={cart} />}</div>; } ``` With three items this renders the summary. With zero items it renders the character `0` — usually somewhere it looks like a typo rather than a bug, which is exactly why it survives review. Nothing here is React magic. Two independent rules combine: what `&&` evaluates to, and what React does with the value it gets as a child. ## Rule one: && yields an operand, not a boolean `a && b` evaluates `a`; if `a` is falsy it returns `a` itself and never evaluates `b`. If `a` is truthy it returns `b`. It is a value-selecting operator, not a boolean-producing one. `0 && <X />` is therefore `0`, `'' && <X />` is `''`, and `NaN && <X />` is `NaN`. Only when the left side is already a boolean (`isOpen && <X />`) does the falsy branch happen to give you `false`. ## Rule two: React classifies children by type, not truthiness When React renders a child value it looks at the type: - **string, number** — rendered as a text node. `0` is a number, so it prints. `NaN` prints as `NaN`. - **null, undefined, true, false** — rendered as nothing at all. These are the only "invisible" values. - **array** — each element is rendered in order. - **React element** — rendered as the described subtree. - **plain object** — an error: React refuses objects as children. The skip list is small and explicit. `0` is not on it, and neither is `''` — an empty string is a string, so React renders an empty text node. It is invisible, so an empty-string guard produces a silent, harmless-looking leak instead of a visible one, and the same defect passes unnoticed for months. So the combination is: `&&` hands React a number, and React faithfully renders numbers. ## The fixes, in order of preference **Compare explicitly.** Make the left operand a boolean by construction: ```jsx {cart.length > 0 && <CartSummary cart={cart} />} ``` This reads as the condition you actually meant ("there is at least one item"), and it cannot leak because `>` always produces a boolean. **Ternary with an explicit null.** Useful when there is an else branch anyway, and self-documenting when there is not: ```jsx {cart.length ? <CartSummary cart={cart} /> : null} ``` The ternary is safe for a different reason: it never returns the tested value, only one of the two branches you wrote. **Coerce.** `Boolean(cart.length) && ...` or `!!cart.length && ...` both work. They are correct but weaker code: the reader has to reconstruct which falsy value you were defending against, and a later refactor from `cart.length` to `cart.total` silently keeps the coercion while the intent drifts. **Early return.** When the whole component has nothing to show, `if (cart.length === 0) return null;` above the JSX is often clearer than any inline guard. ## Related traps in the same family - `{value || <Fallback />}` has the mirror-image problem: if `value` is a renderable-but-unwanted value such as `0`, it is falsy, so the fallback shows when you wanted the zero. If it is a non-empty string it renders that string instead of your fallback. - Optional API data: `{data?.count && <Badge count={data.count} />}` leaks a `0` for a genuinely-zero count, which is a common cause of a stray zero in dashboards. - TypeScript does not save you. `cart.length && <CartSummary />` types as `number | Element`, and `number` is a legal `ReactNode`, so the compiler is satisfied. ## Why interviewers like this question It separates candidates who have memorised the idiom from candidates who can say *why* it fails. The complete answer names both halves — `&&` returns the operand, and React renders numbers — and then picks a fix on readability grounds rather than reciting `!!`. It also opens naturally onto the wider question of what each child value type renders as, which is the same knowledge in a different frame.

  • Why does the empty-cart case show a visible `0`, while a guard like `{name && <Greeting />}` on an empty string shows nothing at all?
    Both leak the left operand, but React renders an empty string as an empty text node — there is no glyph to see. The defect is identical; only its visibility differs. That is why an empty-string guard can sit in a codebase for months while a numeric one is reported the day it ships.
  • Does `||` have the same trap?
    The mirror image of it. `{count || <Empty />}` returns the left operand when it is truthy, so a count of `5` renders `5` instead of your element, and a count of `0` falls through to the fallback you may not have wanted. When you only mean "null or undefined", `??` is the precise operator; when you mean a real branch, use a ternary.
  • What does a component render if it returns `null` or `false`?
    Nothing. Both are on React's skip list, so returning either from a component's render is the normal, supported way to render nothing — no wrapper element is created and no DOM node is produced. `return null` is the conventional spelling because it states the intent.

&& is a turnstile that lets a value through rather than a switch that outputs on or off: feed it a 0 and a 0 is what comes out the other side.

saying these in an interview costs you the question

  • Thinks && always evaluates to true or false
  • Believes React skips every falsy value
  • Says the empty string would also print visibly
  • Blames JSX instead of JavaScript's && semantics
  • Reaches for !! without being able to say why

context