JSX
JSX is the syntax that lets you write HTML-flavoured templates inside JavaScript. It compiles to plain function calls — nothing magic — but its rules for attributes, expressions, conditionals, and lists are where most React beginners trip.
Expressions, attrs, children, gotchas
EXAMPLE
// 1) JSX is JavaScript — compiles to React.createElement (or jsx runtime)
// JSX:
const el = <h1 className="title">Hello, {user.name}</h1>;
// Compiled:
const el = React.createElement('h1', { className: 'title' }, 'Hello, ', user.name);
// 2) Any single expression inside { }
const price = 4.99;
<p>Total: ${price.toFixed(2)}</p>
<p>{user.firstName + ' ' + user.lastName}</p>
<p>{Date.now()}</p>
<p>{1 + 1}</p>
// Statements (if/for) are NOT expressions. Use ternaries, &&, .map().
// 3) Attributes
// className not class
// htmlFor not for
// tabIndex not tabindex (camelCase for almost everything)
// style takes an object: { backgroundColor: 'red', fontSize: 14 }
// data-* and aria-* keep their dash form
<input type="text" id="name" tabIndex={1} aria-label="Name" />
<label htmlFor="name">Name</label>
<div className="card" style={{ padding: 16, borderRadius: 8 }}>…</div>
// 4) Boolean attributes
<button disabled>Save</button> // disabled={true}
<button disabled={isSaving}>Save</button>
<input checked={agreed} readOnly /> // readOnly, not readonly
// 5) Children — strings, numbers, elements, arrays, fragments
<section>
Plain text
<em>emphasised</em>
{[<p key="a">A</p>, <p key="b">B</p>]}
</section>
// 6) Conditional rendering
{loggedIn && <Avatar user={user} />}
{loggedIn ? <Welcome user={user} /> : <SignInPrompt />}
{count === 0 && <EmptyState />}
{count > 0 && <p>{count} items</p>}
// CAUTION: 0 is FALSY but RENDERS as '0'.
{count && <p>{count} items</p>} // 0 → renders the literal 0; use count > 0 or count != null
// 7) Lists — every child of an array needs a stable key
<ul>
{todos.map((t) => (
<li key={t.id} className={t.done ? 'done' : ''}>{t.text}</li>
))}
</ul>
// NEVER use the array index as key for lists that reorder, filter, or splice mid-list.
// 8) Fragments — return multiple elements without a wrapper
function Group() {
return (
<>
<h2>Group</h2>
<p>members…</p>
</>
);
}
// Keyed fragments (when in a list)
{rows.map((r) => (
<React.Fragment key={r.id}>
<td>{r.name}</td>
<td>{r.email}</td>
</React.Fragment>
))}
// 9) Components — uppercase tags
<UserCard user={user} /> // component
<usercard user={user} /> // DOM tag <usercard>, ignored unless registered
// 10) Spreading props
const inputProps = { type: 'email', autoComplete: 'email', required: true };
<input {...inputProps} value={email} onChange={onChange} />
// Later spreads override earlier ones.
// 11) Children prop
function Card({ title, children }) {
return (
<section className="card">
<h3>{title}</h3>
<div>{children}</div>
</section>
);
}
<Card title="Profile">
<p>email: mara@example.com</p>
</Card>
// 12) HTML entities + escaping
// JSX does NOT auto-decode HTML entities the way HTML does.
// Use string literals or curly with the entity:
<p>{'\u00A9'} 2024</p> // copyright
<p>© 2024</p> // works in JSX (limited entities)
<p>{'a & b'}</p> // safest — emits literal '&'
// 13) Setting raw HTML — last-resort escape hatch
<div dangerouslySetInnerHTML={{ __html: sanitisedHtml }} />
// dangerously — only when you've sanitised the input (DOMPurify, etc.)
// 14) Event handlers — camelCase, value is a function
<button onClick={handleClick}>Save</button>
<input onChange={(e) => setName(e.target.value)} />
<form onSubmit={(e) => { e.preventDefault(); submit(); }}>
// SyntheticEvent — React's cross-browser wrapper; e.target, e.currentTarget, e.preventDefault().
// 15) Inline functions vs memoized handlers
// Inline is fine for most cases. For memoized children (React.memo), use useCallback
// to keep handler identity stable:
const onSave = useCallback((id) => save(id), []);
<Row onSave={onSave} />
// 16) Whitespace rules
// Adjacent text is collapsed to a single space.
// Blank lines between tags do NOT render whitespace.
// Inline {' '} when you need an explicit space.
<span>{firstName}{' '}{lastName}</span>
// 17) Returning null is valid — renders nothing
function Maybe({ when, children }) {
return when ? children : null;
}
// 18) JSX vs createElement — TypeScript + tooling
// Modern setups use the jsx runtime (no React import needed for JSX). Older setups
// require 'import React from "react"' even when React isn't referenced.
// 19) Common bugs
// • Lowercase component name → React treats as DOM tag, fails silently
// • class instead of className → DOM warning, no class applied
// • {count && <X/>} when count can be 0 → renders '0'
// • Reusing a key after deleting/reordering → wrong state attaches to wrong row
// • Returning a non-element/non-array from a component → 'Objects are not valid as a React child'
// • Comments inside JSX must be in braces: {/* like this */} — // and /* */ outside braces only
// • Inline style with px: { width: 200 } sets 200px; { width: '200px' } also works; { width: 0.5 } sets 0.5px
Why it matters
JSX is a thin wrapper around React.createElement: expressions in {}, camelCase attributes, every list child needs a stable key, and 0 renders as “0” instead of nothing. Lean on <>…</> fragments to avoid wrapper divs, and use && for present/absent and ternaries for either/or rendering.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Exercise
JSX uses this attribute instead of "class".
<div
="card">…</div>
camelCase; nine chars.
Discussion
Loading…