Why `&&` in JSX can backfire

Consider this common React pattern:

function ContactList({ contacts }) {
	return (
		<div>
			<ul>
				{contacts.length &&
					contacts.map((contact) => (
						<li key={contact.id}>
							{contact.firstName} {contact.lastName}
						</li>
					))}
			</ul>
		</div>
	)
}

What happens when contacts is an empty array? You'd render 0 on the page. This isn't a theoretical edge case — it was shipped to production at PayPal on this page, leaving users who had no contacts staring at a stray zero:

PayPal page with contacts

The root cause is how JavaScript evaluates logical AND. When the left operand is falsy, && short-circuits and returns that operand's value without ever evaluating the right side. So 0 && anything always evaluates to 0, which React happily renders as text.

The fix is to be explicit about the falsy case with a ternary:

function ContactList({ contacts }) {
	return (
		<div>
			<ul>
				{contacts.length
					? contacts.map((contact) => (
							<li key={contact.id}>
								{contact.firstName} {contact.lastName}
							</li>
						))
					: null}
			</ul>
		</div>
	)
}

The same trap with `undefined`

Here's a subtler variant:

function Error({ error }) {
	return error && <div className="fancy-error">{error.message}</div>
}

If error happens to be undefined, you won't get a sensible fallback — you'll get an uncaught error:

Uncaught Error: Error(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.

In production, the symptom might look like this:

react-dom.production.min.js:13 Uncaught Invariant Violation: Minified React error #152; visit https://reactjs.org/docs/error-decoder.html?invariant=152&args[]=f for the full message or use the non-minified dev environment for full errors and additional helpful warnings.

This is the same underlying issue: undefined && anything returns undefined, which is not a valid React element. Remember that JSX is simply a syntactic sugar for React.createElement calls:

function throwTheCandy(candyNames) {
	for (const candyName of candyNames) {
		throwCandy(candyName)
	}
}

throwTheCandy(candies.length && candies.map((c) => c.name))

Trying to pass undefined where React expects a child or a component type doesn't make sense type-wise, yet React permits it because rendering 0 is technically valid.

Branching syntax over boolean coercion

The problem isn't that && is wrong — it's that it's designed to return a value, not to render conditionally. In an if statement, the result of && is discarded and only truthiness matters. In JSX, you're using the operator's return value directly, and when the left side is falsy, that return value is the falsy operand itself:

0 && true // 0
true && 0 // 0
false && true // false
true && '' // ''

That's why TypeScript can catch the undefined case, but not the 0 case — and why relying on !!contacts.length or contacts.length > 0 works but only masks the semantic confusion.

Instead, reach for actual branching constructs. A ternary or an explicit if statement makes both outcomes clear. For the contacts example, the if version would be:

function ContactList({ contacts }) {
	let contactsElements = null
	if (contacts.length) {
		contactsElements = contacts.map((contact) => (
			<li key={contact.id}>
				{contact.firstName} {contact.lastName}
			</li>
		))
	}

	return (
		<div>
			<ul>{contactsElements}</ul>
		</div>
	)
}

Ternaries are often the most readable in JSX, though readability is subjective. An extra payoff: coverage tools can report branches you haven't tested, which isn't possible when you use &&.

For extra protection, the eslint-plugin-react's jsx-no-leaked-render rule will flag && uses that could leak a falsy value into the output.

Writing it clearly today

Both problematic patterns have a straightforward fix. Here's how you'd render them now:

function ContactList({ contacts }) {
	return (
		<div>
			<ul>
				{contacts.length
					? contacts.map((contact) => (
							<li key={contact.id}>
								{contact.firstName} {contact.lastName}
							</li>
						))
					: null}
			</ul>
		</div>
	)
}
function Error({ error }) {
	return error ? <div className="fancy-error">{error.message}</div> : null
}

The rule of thumb: when you want conditional rendering, choose the syntax that names both outcomes. Logical operators return operands, not branches.