skip to content

Expressions and Conditional Rendering

Conditional rendering in React is just expressions, so the interesting part is what each value renders as. The classic interview trap is a stray `0` appearing on screen from an && guard, which tests whether you know which values React renders and which it skips.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In JSX, what does React render for `{value}` when value is a string, a number, true, null, undefined, an array, or a plain object?

level: juniorimportance: must knowfreq 62%

answer

  1. React dispatches on the child's type
  2. only four values are invisible
  3. numbers and strings both become text
  4. arrays flatten into the child list
  5. objects fail loudly, on purpose

basics

~20 s

Strings and numbers render as text. Arrays render each element in order. Null, undefined, true and false render nothing at all. A plain object is not a valid child and React throws an error instead of rendering it.

solid answer

~40 s

React classifies a child by its type. Strings and numbers become text nodes — including `0` and `NaN`, which is what makes falsy guards leak. Arrays are rendered element by element, so `{[a, b]}` behaves like writing the two children in sequence. `null`, `undefined`, `true` and `false` render nothing; they are the only values that disappear, which is why `return null` is the idiomatic "render nothing". A plain object is rejected — React raises an error saying objects are not valid as a React child — so you have to pick a field, format it, or serialise it yourself. That last case is the everyday bug: rendering `{user}` instead of `{user.name}`, or dropping a `Date` or an `Error` straight into the tree.

code

jsx · 13 lines
jsx
function Values() {
  return (
    <div>
      <p>[{'text'}]</p>
      <p>[{42}]</p>
      <p>[{0}]</p>
      <p>[{true}]</p>
      <p>[{null}]</p>
      <p>[{undefined}]</p>
      <p>[{['a', 'b']}]</p>
    </div>
  );
}

go deeper

for a junior

Memorise the short invisible list — null, undefined, true, false — and know that everything else you can see is a string or a number. Rendering an object is an error, so reach for a field of it.

for a middle

State the rule as type dispatch rather than truthiness, and derive the consequences on the spot: why return null works, why a numeric guard leaks, why an array needs no wrapper.

for a senior

Show how you keep the object-child error out of production — formatting at the edge where data enters, so components receive display-ready primitives rather than raw API objects or Error instances.

for a principal

Treat it as a boundary-design question: decide where raw domain objects get converted to renderable primitives, and make that layer explicit so the failure cannot re-enter through every new component that touches the same payload.

## Braces embed a value, and React decides what to do with it `{expr}` inside JSX means "evaluate this JavaScript expression and use the result as a child". The braces impose no filtering of their own — whatever the expression produces is handed to React, which then dispatches on the value's type. Knowing that table is the whole of conditional rendering in React, because every conditional idiom ends up producing one of these values. ## The table **Strings** render as a text node, exactly as typed. `{'Ada'}` and the literal text `Ada` in JSX are the same output. An empty string produces an empty text node — no visible output, but a node all the same. **Numbers** render as text too. `{42}` renders `42`, `{0}` renders `0`, and `{NaN}` renders `NaN`. There is no special case for zero, which is precisely why a falsy guard can print a stray digit. **null and undefined** render nothing. No node, no placeholder, no whitespace. **Booleans** render nothing — both `true` and `false`. This is a deliberate convenience so that boolean-producing guards can be written inline without emitting the word `false`. **Arrays** are flattened into the child list: each element is rendered in order, using these same rules recursively. `{['a', 'b']}` renders `ab`; `{[null, 'a']}` renders `a`. **React elements** — what JSX itself produces — render as the subtree they describe. **Plain objects** are not valid children. React raises an error stating that objects are not valid as a React child, naming the object's keys to help you find it. The same applies to a `Date`, a `Map`, or an `Error` instance: they are objects, so they are rejected rather than stringified. ## Why the object case is the one you actually hit Almost every occurrence in a real codebase is the same slip: the data arrived as an object and you rendered the whole thing. ```jsx // throws: objects are not valid as a React child <p>{user}</p> // fine <p>{user.name}</p> <p>{user.createdAt.toLocaleDateString()}</p> <p>{String(error)}</p> ``` Notice that React deliberately does *not* fall back to `String(value)` for objects. Doing so would render `[object Object]` on the page — a silent, useless output — where an error at least points at the line. It is a design decision to fail loudly on the case where the developer almost certainly made a mistake, and to render quietly on the cases (null, booleans) that are almost certainly intentional. ## Consequences you can reason out from the table Once you know the table, several React behaviours stop needing separate memorisation: - `return null` from a component renders nothing, because `null` renders nothing as a child. - `{isReady && <Panel />}` is safe when `isReady` is a real boolean, because the falsy branch produces `false`, which is invisible. - `{count && <Badge />}` is unsafe, because the falsy branch produces the number `0`, and numbers are visible. - Mapping an array of elements works with no wrapper, because arrays are flattened into the child list. - A stray `[object Object]` on the page is *not* a React default; if you see one, someone called `String()` or template-interpolated the object themselves. ## Whitespace, the near neighbour JSX collapses whitespace at the start and end of lines and drops lines that are pure whitespace, so two elements on separate source lines render with no space between them. Because a string child renders literally, the standard fix is to embed one: `{' '}`. It is the same rule — a string expression becomes a text node — applied to a formatting problem. ## What an interviewer is checking They want to see whether you hold a *rule* or a bag of anecdotes. Candidates who have only memorised idioms say "falsy things don't render", which is wrong in exactly the case that matters. Candidates who know the type table can derive the `0` leak, the object error, and the safety of `return null` on the spot, without having met each one before.

  • Why does React throw on an object child instead of just rendering `[object Object]`?
    Because rendering it would be silently useless. An object child is nearly always a mistake — the developer meant a field of it — so React fails loudly and names the object's keys, which points straight at the offending line. Stringifying would put meaningless text on the page and leave the bug to be found by a user.
  • Two JSX elements sit on separate source lines and render with no space between them. Why, and how do you add one?
    JSX trims leading and trailing whitespace on each line and removes lines that are entirely whitespace, so the newline between them contributes nothing. Embed an explicit string child — `{' '}` — between them, or put the space inside one of the elements' text. It works because a string expression always renders as a literal text node.
  • What renders for `{[null, 'a', 0, false]}`?
    The text `a0`. The array is flattened into the child list and each element follows the normal rules: `null` and `false` render nothing, the string `a` and the number `0` each render as text. Nothing about being inside an array changes how a value is treated.

saying these in an interview costs you the question

  • Says falsy values are skipped, so 0 is skipped
  • Expects an object child to render [object Object]
  • Thinks React prints the word false for boolean children
  • Believes an array child needs a wrapper element
  • Assumes a Date renders as a formatted date

context

open as a page

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%

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.

open as a page

A React component must show a loading spinner, an error message, or a data table depending on its state. How do you structure that branching in JSX, and why are deeply nested ternaries discouraged?

level: middleimportance: should knowfreq 52%

basics

~20 s

Use early returns above the JSX, one per state, so each branch reads as a flat guard clause. JSX braces hold expressions, not statements, so an inline if is impossible; nested ternaries are the workaround people reach for, and they scale badly in review and in diffs.

open as a page

In React, what actually changes if you write `{isOpen && <Panel />}` instead of always rendering `<Panel hidden={!isOpen} />`, and how do you choose between them?

level: seniorimportance: should knowfreq 44%

basics

~20 s

The conditional version removes Panel from the tree, so it unmounts: its state and refs are discarded and its effect cleanups run, and reopening remounts it fresh. The hidden version keeps it mounted, preserving state and DOM but paying render and memory cost while invisible.

open as a page