JSX is a syntax extension to JavaScript that lets you write HTML-like
markup inside a .js or .tsx file. It is not valid JavaScript — a
build tool (Babel, SWC, or the TypeScript compiler) transforms every JSX
expression into a React.createElement call before the browser sees it.
// What you write
const el = <h1 className="title">Hello</h1>
// What the compiler produces (React 17+ JSX transform uses _jsx instead)
const el = React.createElement('h1', { className: 'title' }, 'Hello')
The output is a plain JavaScript object — a React element — that describes what should appear on screen. React's renderer later turns that description into actual DOM nodes.
Rule of thumb: JSX is syntactic sugar for React.createElement. If you
can't picture the compiled form, you don't yet fully understand what JSX is.
Because JSX compiles to a single React.createElement call, which returns
one value. You can't return two values from one expression in JavaScript.
// ❌ Invalid — two adjacent elements at the top level
return (
<h1>Title</h1>
<p>Body</p>
)
// ✅ Wrap in a div
return (
<div>
<h1>Title</h1>
<p>Body</p>
</div>
)
// ✅ Or use a Fragment (no extra DOM node)
return (
<>
<h1>Title</h1>
<p>Body</p>
</>
)
Rule of thumb: when you need siblings without a wrapper DOM node, reach
for a Fragment (<>…</> or <React.Fragment>…</React.Fragment>).
A Fragment groups children without adding an extra node to the DOM. There are two syntaxes:
// Short syntax — most common
return (
<>
<dt>Term</dt>
<dd>Definition</dd>
</>
)
// Long syntax — required when you need the key prop (e.g. in a list)
return items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.def}</dd>
</React.Fragment>
))
Use Fragments when:
- A wrapper
<div>would break CSS layout (e.g. inside a<table>or flex/grid container). - You're mapping over pairs of sibling elements that each need a
key.
Rule of thumb: prefer <>…</> by default; switch to the named form
only when you need to attach key.
Wrap any JavaScript expression in {}. An expression is anything that
produces a single value — a variable, function call, ternary, template
literal, etc.
const name = 'Alice'
const isAdmin = true
return (
<div>
<p>Hello, {name.toUpperCase()}</p>
<p>Role: {isAdmin ? 'Admin' : 'User'}</p>
<p>Sum: {2 + 2}</p>
</div>
)
What you cannot put in {}:
- Statements (
if,for, variable declarations) — they don't produce a value. Extract them above the return, or use expressions like ternaries andArray.map. - Objects as children:
{myObject}will throw; useJSON.stringifyor extract individual properties.
Rule of thumb: if it can go on the right-hand side of =, it can go
inside {}.
Several attribute names and rules differ:
| HTML | JSX | Reason |
|---|---|---|
class |
className |
class is a reserved JS keyword |
for |
htmlFor |
for is a reserved JS keyword |
onclick |
onClick |
Events use camelCase in JSX |
<br> |
<br /> |
All tags must be closed in JSX |
style="color:red" |
style={{ color: 'red' }} |
Style takes a JS object |
tabindex |
tabIndex |
camelCase for multi-word attributes |
// HTML
// <label class="label" for="email" onclick="submit()">Email</label>
// JSX
<label className="label" htmlFor="email" onClick={submit}>Email</label>
Additionally, JSX is case-sensitive: <div> is a DOM element; <Div>
would be treated as a custom component.
Rule of thumb: when an HTML attribute doesn't work in JSX, check if it's a reserved keyword or needs camelCase.
In JSX, omitting a value for an attribute is the same as setting it to
true. You never write disabled="true" — that's an HTML string, not a
boolean.
// These are equivalent
<input disabled={true} />
<input disabled />
// Explicitly false — attribute is omitted from the DOM
<input disabled={false} />
// Dynamic
<button disabled={isLoading}>Submit</button>
A common mistake is disabled="false" — HTML treats any non-empty string
as truthy, so the button would still be disabled. Always use {false}.
Rule of thumb: use {true} / {false} for any attribute that has a
boolean semantic; never pass boolean values as strings.
JSX uses the case of the first letter to decide how to compile the tag:
- Lowercase (
<div>,<span>) →React.createElement('div', …)— passed as a string, rendered as a native DOM element. - Uppercase (
<MyComponent>) →React.createElement(MyComponent, …)— passed as a variable reference, rendered as a React component.
// React treats 'button' as a DOM element — works as expected
const el = <button onClick={handle}>Click</button>
// React looks up `button` as a variable — ReferenceError if not defined
const el = <button /> // ← lowercase: treated as DOM tag, fine
const el = <Button /> // ← uppercase: looks for a `Button` variable
If you store a component in a lowercase variable, you must reassign it to an uppercase one before using it in JSX:
const components = { circle: CircleIcon }
const Icon = components['circle'] // uppercase alias required
return <Icon />
Rule of thumb: always capitalize component names; use lowercase only for HTML intrinsic elements.
The style prop takes a JavaScript object, not a CSS string. Property
names are camelCase versions of their CSS counterparts.
// ❌ HTML-style string — not valid in JSX
<div style="color: red; font-size: 16px">…</div>
// ✅ JSX object — note the double braces: outer {} for expression,
// inner {} for the object literal
<div style={{ color: 'red', fontSize: '16px', marginTop: 8 }}>…</div>
Values that are numbers (like marginTop: 8) are automatically converted
to pixels for properties that accept pixel units. Values for others (like
opacity: 0.5) are used as-is.
Rule of thumb: for anything beyond a quick prototype, prefer CSS modules or a utility class over inline styles — they don't support media queries or pseudo-selectors.
Comments inside JSX must be wrapped in {} and use the /* … */ syntax —
not //, which would comment out the closing } as well.
return (
<div>
{/* This is a JSX comment — it won't appear in the rendered output */}
<p>Visible text</p>
{/* <p>Commented-out element</p> */}
</div>
)
Outside of JSX (above the return), normal JS comments work fine.
Rule of thumb: {/* comment */} inside JSX, // comment outside.
Use the spread syntax {...obj} to forward a set of props without listing
each one individually.
const buttonProps = { type: 'button', disabled: false, 'aria-label': 'Save' }
return <button {...buttonProps}>Save</button>
// Equivalent to:
return <button type="button" disabled={false} aria-label="Save">Save</button>
A common pattern in wrapper components:
function IconButton({ icon, children, ...rest }) {
// `rest` captures all props not explicitly destructured
return (
<button {...rest}>
<Icon name={icon} />
{children}
</button>
)
}
Rule of thumb: spread props enable flexible pass-through but make it easy to accidentally forward unknown props to DOM elements (which triggers React warnings). Destructure known props and spread only the remainder.
Returning null renders nothing — the component mounts (lifecycle runs,
refs attach) but contributes no DOM nodes. It's the idiomatic way to
conditionally hide output without unmounting.
function Banner({ show, message }) {
if (!show) return null
return <div className="banner">{message}</div>
}
Crucially, returning null is different from not rendering the component
at all:
null→ component mounts;useEffectand refs still fire.- Conditional rendering (
{show && <Banner />}) → component is not mounted whenshowis false; effects and state are destroyed.
Rule of thumb: return null when you want the component in the tree
(for effects or imperative refs) but invisible; use conditional rendering
when you want it fully gone.
React can render an array of React elements as a child. The standard
approach is Array.map:
const fruits = ['Apple', 'Banana', 'Cherry']
return (
<ul>
{fruits.map(fruit => (
<li key={fruit}>{fruit}</li> // key is required on the outermost element
))}
</ul>
)
React also accepts arrays returned directly:
function Tags({ tags }) {
return tags.map(tag => <span key={tag} className="tag">{tag}</span>)
}
Things that React will not render as children: plain objects, undefined
(silently skipped), and false (silently skipped — useful for short-circuits).
Rule of thumb: always add a stable key to each element in a mapped
list; without it, React warns and may produce incorrect diffs.
Single {} opens a JavaScript expression slot in JSX. Double {{}}
is simply that expression slot containing an object literal — there's
no special double-brace syntax.
// {} — expression slot
<p>{count}</p>
// {{}} — expression slot containing an object literal
<div style={{ color: 'red', fontSize: 16 }}>…</div>
// ^outer: JSX expression ^inner: JS object
The confusion usually arises with style because it's the most common
attribute that takes an object value.
Rule of thumb: think of it as two separate things: "open expression"
then "open object". The outer {} is JSX; the inner {} is JavaScript.
Anything placed between opening and closing JSX tags is automatically
passed as the children prop.
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="body">{children}</div>
</div>
)
}
// Usage — the <p> and <button> become children
<Card title="Welcome">
<p>Hello there!</p>
<button>OK</button>
</Card>
children can be a string, a single element, an array of elements, or
even a function (render prop pattern). Use React.Children utilities if
you need to iterate or count children programmatically.
Rule of thumb: children is just a prop; you can also pass it
explicitly as <Card children={<p>Hi</p>} />, though the nested form is
more readable.
No — if is a statement, not an expression, so it can't go inside {}.
JSX only accepts expressions.
// ❌ if statement inside JSX — syntax error
return (
<div>
{if (isLoggedIn) { return <Dashboard /> }}
</div>
)
// ✅ Ternary — an expression
return (
<div>
{isLoggedIn ? <Dashboard /> : <Login />}
</div>
)
// ✅ Logical && — renders right side only if left is truthy
return (
<div>
{isAdmin && <AdminPanel />}
</div>
)
// ✅ If/else above the return — perfectly fine
let content
if (isLoggedIn) { content = <Dashboard /> }
else { content = <Login /> }
return <div>{content}</div>
Rule of thumb: for simple two-path branches use a ternary; for one-side
rendering use &&; for complex logic extract it above the return.
More Components interview questions
More ways to practice
The self-quiz is live. Get notified when mock interviews and new question packs drop.