Understanding JSX Completely
What is JSX?
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like syntax inside JavaScript files. Browsers cannot understand JSX directly, so Babel or SWC transforms it into regular JavaScript.
// JSX written by developer
const element = <h1 className="title">Hello!</h1>;
// After Babel transform (before React 17)
const element = React.createElement(
'h1',
{ className: 'title' },
'Hello!'
);
// After Babel transform (React 17+ automatic JSX Transform)
import { jsx as _jsx } from 'react/jsx-runtime';
const element = _jsx('h1', { className: 'title', children: 'Hello!' });
In React 17+, you no longer need to write import React from 'react'.
Understanding React.createElement
JSX ultimately compiles down to React.createElement(type, props, ...children) calls.
// JSX
function Welcome() {
return (
<div className="container">
<h1>Welcome</h1>
<p>Learning React</p>
</div>
);
}
// Transformed result (conceptually)
function Welcome() {
return React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, 'Welcome'),
React.createElement('p', null, 'Learning React')
);
}
Understanding this structure makes JSX's constraints easier to understand.
Embedding Expressions
Insert JavaScript expressions inside curly braces {}. Statements are not allowed.
const name = 'John';
const today = new Date();
const isAdmin = true;
function Profile() {
return (
<div>
{/* Variable */}
<h1>Hello, {name}!</h1>
{/* Function call */}
<p>Today: {today.toLocaleDateString('en-US')}</p>
{/* Ternary operator */}
<span>{isAdmin ? 'Admin' : 'Regular User'}</span>
{/* Arithmetic */}
<p>1 + 1 = {1 + 1}</p>
{/* Template string */}
<p className={`badge ${isAdmin ? 'admin' : 'user'}`}>Badge</p>
</div>
);
}
Conditional Rendering
1. Ternary Operator
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? (
<h1>Welcome back!</h1>
) : (
<h1>Please sign in.</h1>
)}
</div>
);
}
2. Logical AND Operator (&&)
function Notification({ hasUnread, count }) {
return (
<div>
<h1>Inbox</h1>
{/* Only renders when hasUnread is true */}
{hasUnread && <span className="badge">{count}</span>}
</div>
);
}
Caution: If count is 0, 0 && ... renders 0.
// ❌ Renders "0" when count is 0
{count && <span>{count} items</span>}
// ✅ Explicit boolean conversion
{count > 0 && <span>{count} items</span>}
{!!count && <span>{count} items</span>}
3. Hiding with null Return
function Alert({ type, message }) {
if (!message) return null; // Exit without rendering
return (
<div className={`alert alert-${type}`}>
{message}
</div>
);
}
4. Early Return Pattern
function UserDashboard({ user, isLoading, error }) {
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <p>User not found.</p>;
// Main content
return (
<div>
<h1>{user.name}'s Dashboard</h1>
</div>
);
}
List Rendering
map and key
const products = [
{ id: 1, name: 'Laptop', price: 1200 },
{ id: 2, name: 'Mouse', price: 35 },
{ id: 3, name: 'Keyboard', price: 89 },
];
function ProductList() {
return (
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} — ${product.price.toLocaleString()}
</li>
))}
</ul>
);
}
Key Selection Rules
// ✅ Unique ID from the data
<li key={item.id}>
// ✅ Stable unique value
<li key={item.slug}>
// ❌ Array index (causes bugs when items are reordered)
<li key={index}>
// ❌ Math.random() (changes on every render)
<li key={Math.random()}>
Nested Lists
function CategoryList({ categories }) {
return (
<div>
{categories.map(category => (
<div key={category.id}>
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
))}
</div>
);
}
JSX Rules
1. Single Root Element
// ❌ Cannot return multiple root elements
function Bad() {
return (
<h1>Title</h1>
<p>Content</p>
);
}
// ✅ Option 1: Wrap with a div
function Good1() {
return (
<div>
<h1>Title</h1>
<p>Content</p>
</div>
);
}
// ✅ Option 2: Use Fragment (no extra DOM node)
function Good2() {
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
}
// ✅ Option 3: Fragment (when key is needed)
function Good3({ items }) {
return items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
));
}
2. className and htmlFor
HTML attributes that conflict with JavaScript reserved words use different names.
// HTML → JSX
// class → className
// for → htmlFor
<div class="container"> // ❌ HTML style
<div className="container"> // ✅ JSX style
<label for="email"> // ❌
<label htmlFor="email"> // ✅
3. camelCase Attributes
// HTML → JSX
// tabindex → tabIndex
// maxlength → maxLength
// onclick → onClick
// onchange → onChange
// style values are also camelCase
<input tabIndex={1} maxLength={50} />
<div style={{ backgroundColor: '#fff', fontSize: '16px' }} />
4. Closing Tags
Even tags that don't self-close in HTML must be closed in JSX.
// ❌ Open tags like in HTML
<input type="text">
<br>
<img src="photo.jpg">
// ✅ JSX style
<input type="text" />
<br />
<img src="photo.jpg" />
5. JavaScript Reserved Words
// ❌
<button class="btn" for="input">
// ✅
<button className="btn">
// Other notes
// onclick → onClick
// onkeydown → onKeyDown
Practical Example: Product Card Component
function ProductCard({ product, onAddToCart }) {
const { id, name, price, image, inStock, rating } = product;
return (
<div className={`product-card ${inStock ? '' : 'out-of-stock'}`}>
<img src={image} alt={name} />
<div className="product-info">
<h3>{name}</h3>
<div className="rating">
{'★'.repeat(Math.floor(rating))}
{'☆'.repeat(5 - Math.floor(rating))}
<span>({rating})</span>
</div>
<p className="price">${price.toLocaleString('en-US')}</p>
{inStock ? (
<button
className="btn-add"
onClick={() => onAddToCart(id)}
>
Add to Cart
</button>
) : (
<button disabled className="btn-disabled">
Out of Stock
</button>
)}
</div>
</div>
);
}
// Usage example
function App() {
const products = [
{ id: 1, name: 'Wireless Keyboard', price: 89, image: '/kb.jpg', inStock: true, rating: 4.5 },
{ id: 2, name: 'Mouse Pad', price: 15, image: '/pad.jpg', inStock: false, rating: 4.0 },
];
function handleAddToCart(id) {
console.log(`Added product ${id} to cart`);
}
return (
<div className="product-grid">
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</div>
);
}
Pro Tips
1. JSX is an expression
JSX can be stored in variables or returned from functions.
const loadingUI = <Spinner size="large" />;
const errorUI = error && <ErrorBanner message={error.message} />;
return (
<div>
{isLoading ? loadingUI : errorUI}
</div>
);
2. A taste of Render Props
// Extract conditional rendering into a function
function ConditionalRender({ condition, children, fallback }) {
return condition ? children : (fallback ?? null);
}
// Usage
<ConditionalRender condition={isAdmin} fallback={<AccessDenied />}>
<AdminPanel />
</ConditionalRender>
3. Define style objects outside the component
// ❌ Creates a new object on every render
<div style={{ color: 'red', fontWeight: 'bold' }}>
// ✅ Define outside the component
const errorStyle = { color: 'red', fontWeight: 'bold' };
function ErrorText({ text }) {
return <div style={errorStyle}>{text}</div>;
}