ReactJSX Rules & Gotchas

JSX Rules & Gotchas

JSX looks simple but has a specific set of rules that differ from HTML. Breaking any of them causes a compile error or a confusing runtime warning. This page catalogs every rule, shows the wrong version alongside the correct fix, and explains why each rule exists.

Rule 1: Return a Single Root Element

A JSX expression — including a component's return value — must have exactly one root element. JSX compiles to a single function call that returns a single value; two sibling root elements would require two return values, which is impossible.

JSX
// ❌ Two root elements — compile error
function Broken() {
  return (
    <h1>Title</h1>
    <p>Paragraph</p>
  )
}

// ✅ Wrapped in a single parent
function WithDiv() {
  return (
    <div>
      <h1>Title</h1>
      <p>Paragraph</p>
    </div>
  )
}

// ✅ Wrapped in a Fragment (no extra DOM node)
function WithFragment() {
  return (
    <>
      <h1>Title</h1>
      <p>Paragraph</p>
    </>
  )
}
Tip
Use a Fragment `<></>` whenever you need a wrapper but don't want an extra `<div>` in the DOM. Fragments are especially important inside `<table>` elements where an extra `<div>` would produce invalid HTML.
Rule 2: All Tags Must Be Closed

HTML allows certain void elements to be left unclosed (<br>, <img>, <input>, <hr>, <meta>). JSX requires every tag to be closed, either with a closing tag or as a self-closing tag:

JSX
// ❌ HTML-style unclosed tags — parse error in JSX
<img src="/cat.png">
<br>
<input type="text">

// ✅ Self-closing syntax required
<img src="/cat.png" />
<br />
<input type="text" />

// ✅ Custom components must also be closed
<MyComponent />
<MyComponent></MyComponent>
Rule 3: Attributes Are camelCase

HTML attribute names are case-insensitive and often lowercase. In JSX, attribute names map to JavaScript DOM property names, which are camelCase. The most common ones to memorise:

JSX
// ❌ HTML-style attribute names
<div class="container" onclick="handler()">
<input maxlength="50" tabindex="1" />

// ✅ camelCase JSX attributes
<div className="container" onClick={handler}>
<input maxLength={50} tabIndex={1} />
Rule 4: Use className, Not class

class is a reserved keyword in JavaScript. React maps CSS class names to the DOM property className. This is the most frequent mistake developers make when copying HTML into JSX:

JSX
// ❌ 'class' is a JS keyword
<p class="text-muted">Note</p>       // Works but shows a warning

// ✅ Always use className in JSX
<p className="text-muted">Note</p>
Rule 5: Avoid JS Keywords as Attribute Names

Beyond class and for, a handful of other HTML attributes clash with JavaScript reserved words or built-in properties:

JSX
// HTML → JSX mappings for reserved words
// class    → className
// for      → htmlFor  (on <label>)

// Usage examples:
<label htmlFor="email">Email</label>
<input id="email" type="email" />

// 'key' and 'ref' are also special — they are not passed as props
// to the component; React consumes them internally.
Rule 6: Embed JavaScript with Curly Braces

Any JavaScript expression can appear inside JSX — wrapped in curly braces. Statements (if, for, while) cannot. The most common mistake is trying to use an if statement inline:

JSX
// ❌ if statement is not an expression
function Broken({ isAdmin }) {
  return <div>{if (isAdmin) <AdminPanel />}</div>  // SyntaxError
}

// ✅ Ternary — an expression that evaluates to a value
function Fixed({ isAdmin }) {
  return <div>{isAdmin ? <AdminPanel /> : null}</div>
}

// ✅ Compute before return
function AlsoFixed({ isAdmin }) {
  const content = isAdmin ? <AdminPanel /> : <UserPanel />
  return <div>{content}</div>
}
Rule 7: Comments Use the {/* */} Syntax

HTML comments <!-- like this --> are not valid inside JSX. JavaScript comments inside JSX must be wrapped in curly braces:

JSX
function Page() {
  return (
    <div>
      {/* This is a valid JSX comment */}
      <h1>Hello</h1>

      {/*
        Multi-line comment:
        This section handles the hero banner
      */}
      <HeroBanner />

      // ❌ This comment IS NOT hidden — it renders as text!
      /* ❌ Neither is this */
    </div>
  )
}
Warning
Plain `//` and `/* */` comments placed directly in JSX markup will render as visible text in the browser. Always use `{/* comment */}` for comments inside JSX.
Rule 8: JSX Expressions Must Return a Value
Anything placed between ` and ` must evaluate to something React can render: a string, number, JSX element, array, `null`, `undefined`, or `false`. `undefined` and `false` render nothing, which is useful for conditional rendering:

JSX
// null, undefined, and false render nothing (intentionally)
{false}     // renders nothing
{null}      // renders nothing
{undefined} // renders nothing

// 0 and '' DO render (they are valid values)
{0}   // renders the number 0 — careful with boolean && patterns!
{''}  // renders an empty string (invisible but present in DOM)

// Typical use: conditional rendering
{isLoading && <Spinner />}      // shows Spinner or nothing
{error ? <ErrorMsg /> : null}   // shows ErrorMsg or nothing
Common JSX Errors and Fixes

JSX
// Error 1: Adjacent JSX elements must be wrapped
// Fix: use a Fragment
function Comp() {
  return (
    <>
      <h1>Title</h1>
      <p>Body</p>
    </>
  )
}

// Error 2: Unterminated JSX contents (forgot closing tag)
// Fix: close every tag
<div>
  <input />     {/* not <input> */}
  <img src="x.png" />  {/* not <img src="x.png"> */}
</div>

// Error 3: Cannot read properties of undefined (reading 'map')
// Fix: guard against undefined arrays
{items?.map((item) => <li key={item.id}>{item.name}</li>)}

// Error 4: Each child in a list should have a unique "key" prop
// Fix: add a stable key to each list item
{users.map((user) => (
  <UserCard key={user.id} user={user} />
))}

// Error 5: Objects are not valid as React children
// Fix: render specific properties, not the object itself
{user.name}  // ✅
{user}       // ❌ Can't render an object directly
Note
When JSX gives you a cryptic error, the culprit is usually one of these five: missing root element, unclosed tag, wrong attribute name, object rendered as child, or missing `key` prop. Check these first.
Quick Reference Checklist
  • Single root element — wrap siblings in <></> (Fragment) or a <div>

  • Close all tags<img />, <br />, <input />, <MyComp />

  • camelCase attributestabIndex, maxLength, autoComplete

  • className not classclassName="container"

  • htmlFor not forhtmlFor="email" on <label>

  • Expressions only in {} — ternaries, &&, function calls; no if/for statements inline

  • JSX comments use {/* */} — not HTML comments, not bare //

  • Don't render objects — render {obj.name} not {obj}