JSX (JavaScript XML) is the syntax extension that makes React components readable and writable. It lets you write HTML-like markup directly inside JavaScript functions — and Vite/Babel compiles it into plain React.createElement() calls before the browser ever sees it. JSX is not required to use React, but it is used in virtually every React codebase because it makes the structure of your UI immediately visible at a glance. Understanding JSX rules — what makes it different from HTML, how to embed JavaScript, and the common pitfalls — is a fundamental skill for every MERN developer.
JSX vs HTML — Key Differences
| HTML | JSX | Reason |
|---|---|---|
class="..." |
className="..." |
class is a reserved keyword in JavaScript |
for="..." |
htmlFor="..." |
for is a reserved keyword in JavaScript |
<br> |
<br /> |
All tags must be self-closed in JSX |
<img src="..."> |
<img src="..." /> |
All void elements must self-close |
| Multiple root elements | One root element (or Fragment) | JSX must return a single expression |
onclick="fn()" |
onClick={fn} |
camelCase event names, function reference not string |
style="color: red" |
style={{ color: 'red' }} |
Inline styles are JavaScript objects, not strings |
| HTML comments | {/* JSX comment */} |
HTML comments are not valid JSX |
React.createElement(type, props, ...children) call. <h1 className="title">Hello</h1> becomes React.createElement('h1', { className: 'title' }, 'Hello'). This is why JSX attributes use camelCase (JavaScript object keys) rather than HTML attribute names.<>...</> or <React.Fragment>...</React.Fragment>) when a component needs to return multiple adjacent elements without adding an extra wrapper <div> to the DOM. Unnecessary wrapper divs can break CSS layouts (flexbox, grid children) and add clutter to the DOM tree.if/else statements or for loops directly inside JSX return values — JSX is an expression, not a statement block. Use the ternary operator for conditionals and Array.map() for loops inside JSX. For complex conditional logic, compute the result in a variable before the return statement and reference the variable in JSX.Basic JSX Syntax
// JSX must return one root element
function Welcome() {
return (
<div className="welcome"> {/* className, not class */}
<h1>Hello, MERN!</h1>
<p>Welcome to the blog.</p>
<hr /> {/* self-closing void element */}
<img src="/logo.png" alt="MERN Blog logo" /> {/* self-closing */}
</div>
);
}
// Multiple root elements — use a Fragment
function Actions() {
return (
<> {/* Fragment — no extra DOM element */}
<button>Save</button>
<button>Cancel</button>
</>
);
}
Embedding JavaScript Expressions
function PostCard({ title, author, viewCount, createdAt }) {
const formattedDate = new Date(createdAt).toLocaleDateString('en-US', {
year: 'numeric', month: 'long', day: 'numeric',
});
return (
<article className="post-card">
{/* Curly braces embed any JS expression */}
<h2>{title}</h2>
{/* Method calls */}
<p>By {author.name.toUpperCase()}</p>
{/* Variables */}
<time dateTime={createdAt}>{formattedDate}</time>
{/* Ternary — conditional text */}
<span className={viewCount > 100 ? 'popular' : 'normal'}>
{viewCount} {viewCount === 1 ? 'view' : 'views'}
</span>
{/* Template literals */}
<p className={`card card--${viewCount > 1000 ? 'viral' : 'standard'}`}>
{author.bio || 'No bio provided'}
</p>
</article>
);
}
Inline Styles
// Inline styles in JSX — JavaScript object with camelCase property names
function ProgressBar({ percentage }) {
return (
<div
className="progress-bar"
style={{ // outer {} = JSX expression, inner {} = JS object
width: `${percentage}%`,
backgroundColor: percentage > 80 ? '#22c55e' : '#3b82f6',
height: '8px',
borderRadius: '4px',
transition: 'width 0.3s ease',
}}
/>
);
}
// CSS property names: camelCase in JSX vs kebab-case in CSS
// CSS: background-color, font-size, border-radius
// JSX style prop: backgroundColor, fontSize, borderRadius
What JSX Compiles To
// JSX (what you write)
const element = (
<h1 className="title" id="main-heading">
Hello, {name}!
</h1>
);
// After Babel/Vite compilation (what the browser runs)
const element = React.createElement(
'h1',
{ className: 'title', id: 'main-heading' },
'Hello, ',
name,
'!'
);
// Both produce the same result — JSX is just friendlier syntax
Common Mistakes
Mistake 1 — Using class instead of className
❌ Wrong — using the HTML attribute name:
<div class="post-card"> {/* class is reserved in JS — will still work but triggers a warning */}
✅ Correct — use the JSX attribute name:
<div className="post-card"> {/* ✓ */}
Mistake 2 — Returning multiple root elements without a Fragment
❌ Wrong — JSX cannot return adjacent elements without a wrapper:
return (
<h1>Title</h1>
<p>Content</p> // SyntaxError: Adjacent JSX elements must be wrapped
);
✅ Correct — wrap in a Fragment or a div:
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
Mistake 3 — Using if/else inside JSX return value
❌ Wrong — if statements are not valid expressions in JSX:
return (
<div>
{if (isLoading) { return <Spinner /> }} {/* SyntaxError */}
</div>
);
✅ Correct — use ternary or logical AND inside JSX, or compute before return:
return (
<div>
{isLoading ? <Spinner /> : <PostList posts={posts} />}
{error && <ErrorMessage message={error} />}
</div>
);
Quick Reference
| HTML | JSX equivalent |
|---|---|
class="..." |
className="..." |
for="..." |
htmlFor="..." |
<br> |
<br /> |
style="color:red" |
style={{ color: 'red' }} |
onclick="fn()" |
onClick={fn} |
<!-- comment --> |
{/* comment */} |
| Multiple roots | <>...</> (Fragment) |