Skip to main content

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>;
}