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.
// ❌ 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>
</>
)
}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:
// ❌ 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:
// ❌ 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:
// ❌ '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:
// 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:
// ❌ 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:
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>
)
}Rule 8: JSX Expressions Must Return a Value
// 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 nothingCommon JSX Errors and Fixes
// 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 directlyQuick Reference Checklist
Single root element — wrap siblings in
<></>(Fragment) or a<div>Close all tags —
<img />,<br />,<input />,<MyComp />camelCase attributes —
tabIndex,maxLength,autoCompleteclassName not class —
className="container"htmlFor not for —
htmlFor="email"on<label>Expressions only in {} — ternaries,
&&, function calls; noif/forstatements inlineJSX comments use
{/* */}— not HTML comments, not bare//Don't render objects — render
{obj.name}not{obj}