Skip to content
React rc jsx 4 min read

JSX Gotchas

JSX feels like HTML, and that resemblance is exactly what lures developers into a handful of recurring bugs. The syntax is forgiving enough to compile but strict enough to surprise you at runtime, so a stray 0, a missing key, or an accidental string of styles can quietly break your UI. This page catalogs the most common JSX mistakes in a problem-then-fix format so you can recognize them on sight and write JSX that behaves the way it reads.

Rendering 0 with &&

The single most reported JSX bug is the logical-AND short-circuit leaking a falsy value into the DOM. React renders nothing for null, undefined, and booleans—but it does render the number 0 and empty strings.

Problem:

function Cart({ items }) {
  // When items.length is 0, this prints a literal "0"
  return <div>{items.length && <Badge count={items.length} />}</div>;
}

Because 0 && <Badge /> evaluates to 0, React happily renders that zero as text. The fix is to coerce the left side into a real boolean.

Fix:

function Cart({ items }) {
  return <div>{items.length > 0 && <Badge count={items.length} />}</div>;
}

Any falsy non-boolean is dangerous on the left of &&. Use an explicit comparison (count > 0), Boolean(value), or a ternary (cond ? <X /> : null) so the operator can only ever yield a boolean or an element.

Using the array index as a key

When you render a list with .map(), React needs a stable key per item to track identity across renders. Reaching for the array index seems convenient, but it ties the key to position, not to the data—so reordering, inserting, or deleting items confuses React’s reconciliation and corrupts component state.

Problem:

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>
          <input type="checkbox" /> {todo.text}
        </li>
      ))}
    </ul>
  );
}

If you delete the first todo, every checkbox shifts up a slot but keeps its old checked state, because key 0 now points at different data.

Fix: key by a stable, unique identifier from the data itself.

function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          <input type="checkbox" /> {todo.text}
        </li>
      ))}
    </ul>
  );
}

Index keys are only acceptable for a static list that never reorders, filters, or changes length.

Returning multiple root elements

A component or expression must resolve to a single node, but JSX is just a tree—you can’t return two siblings without a parent. Forgetting this produces a confusing parse error.

Problem:

function Header() {
  // SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag
  return (
    <h1>Title</h1>
    <p>Subtitle</p>
  );
}

Fix: wrap the siblings in a Fragment so you add no extra DOM node.

function Header() {
  return (
    <>
      <h1>Title</h1>
      <p>Subtitle</p>
    </>
  );
}

Use the long form <React.Fragment key={...}> when the fragment itself is a list item that needs a key.

Inline styles as strings

In HTML, style is a string. In JSX, style expects a JavaScript object with camelCased property names. Passing a string silently fails or throws.

Problem:

// Does not work — React expects an object, not a CSS string
<div style="color: red; font-size: 14px;">Hi</div>

Fix: pass an object literal (note the double braces—outer for JSX, inner for the object).

<div style={{ color: "red", fontSize: 14 }}>Hi</div>
ConcernHTML attributeJSX equivalent
Inline stylesstyle="color: red"style={{ color: "red" }}
CSS classesclass="btn"className="btn"
Property namesfont-sizefontSize (camelCase)
Numeric lengthswidth: 12pxwidth: 12 (px assumed)

Writing comments in JSX

HTML comments (<!-- -->) are not valid inside JSX. Comments must live inside a JavaScript expression, which means wrapping them in curly braces.

Problem:

function Panel() {
  return (
    <div>
      <!-- this breaks the parser -->
      <p>Content</p>
    </div>
  );
}

Fix: use a JS block comment inside braces.

function Panel() {
  return (
    <div>
      {/* This is the correct way to comment in JSX */}
      <p>Content</p>
    </div>
  );
}

Outside the return (in normal component logic) regular // and /* */ comments work as usual.

Whitespace and adjacent text

JSX collapses whitespace the way HTML does, but newlines between expressions are stripped entirely—so concatenated values can run together unexpectedly.

Problem:

function Price({ amount, currency }) {
  // Renders "10USD" with no space
  return (
    <span>
      {amount}
      {currency}
    </span>
  );
}

The line break between the two expressions is removed, leaving no gap. To force a space, insert an explicit space expression or a literal space.

Fix:

function Price({ amount, currency }) {
  return (
    <span>
      {amount} {currency}
    </span>
  );
}

Output:

10 USD

You can also use {" "} to insert a deliberate space when elements span multiple lines.

Best Practices

  • Guard && with an explicit boolean (x > 0, Boolean(x)) so falsy values like 0 and "" never leak into the DOM.
  • Key lists by a stable id from the data, not by array index, unless the list is truly static.
  • Wrap sibling roots in a Fragment (<>...</>) instead of an unnecessary wrapper <div>.
  • Pass style an object of camelCased properties; reserve it for dynamic values and prefer className otherwise.
  • Comment with {/* ... */} inside JSX; HTML comments are invalid.
  • Add explicit spaces with {" "} when adjacent expressions span multiple lines.
Last updated June 14, 2026
Was this helpful?