đ¨ JSX and TSX
Welcome to one of React's most distinctive features - JSX! At first glance, mixing HTML with JavaScript might seem strange (remember when we were told to keep them separate?), but JSX is actually one of the things that makes React so powerful and intuitive. It lets you describe your UI structure right alongside the logic that controls it. Once you get comfortable with JSX, you'll wonder how you ever lived without it! Let's explore this game-changing syntax. đ
đ¯ Learning Objectives
By the end of this lesson, you will be able to:
- Understand what JSX and TSX are and how they differ
- Write valid JSX/TSX syntax and understand the rules
- Embed JavaScript expressions in JSX using curly braces
- Use conditional rendering techniques (if/else, ternary, logical AND)
- Render lists of data and understand the importance of keys
- Handle common JSX gotchas and edge cases
- Apply JSX best practices for clean, maintainable code
Estimated Time: 60-75 minutes
Project: Build a dynamic product catalog with conditional rendering and list mapping
đ In This Lesson
đ¤ What is JSX/TSX?
JSX stands for JavaScript XML. It's a syntax extension to JavaScript that lets you write HTML-like code directly in your JavaScript files. When you use TypeScript with React, it's called TSX (TypeScript XML).
đ Definition
JSX/TSX: A syntax extension that allows you to write HTML-like markup inside JavaScript/TypeScript. It's not a string, not HTML, but a syntax that gets transformed into regular JavaScript function calls.
JSX vs TSX
The difference is simple:
- JSX - Used in
.jsxfiles (JavaScript + JSX) - TSX - Used in
.tsxfiles (TypeScript + JSX)
Since we're using TypeScript throughout this course, we'll write TSX. The syntax is identical - the only difference is you get TypeScript's type checking! đĒ
Why JSX Exists
Before JSX, creating React elements looked like this:
// Without JSX - using React.createElement
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, ',
React.createElement('span', null, 'World'),
'!'
);
With JSX, you can write the same thing much more intuitively:
// With JSX - much more readable!
const element = (
<h1 className="greeting">
Hello, <span>World</span>!
</h1>
);
JSX makes your code look like the UI it creates. This makes it easier to visualize, understand, and maintain your components!
đĄ JSX is Optional
You don't have to use JSX with React - you can use React.createElement directly. But 99.9% of React developers use JSX because it's so much better! Think of JSX as syntactic sugar that makes your life easier.
How JSX Works
JSX is not valid JavaScript on its own. Your code goes through a transformation process:
đ¨ Interactive: JSX Transformation Visualizer
Watch JSX transform into JavaScript! Click different JSX examples to see how they compile:
Let's see this transformation in action:
// You write this JSX:
const button = (
<button onClick={handleClick} className="btn">
Click me
</button>
);
// It gets transformed to this JavaScript:
const button = React.createElement(
'button',
{
onClick: handleClick,
className: 'btn'
},
'Click me'
);
// Which creates this React element object:
const button = {
type: 'button',
props: {
onClick: handleClick,
className: 'btn',
children: 'Click me'
}
};
â The Build Process Handles It
When you use tools like Vite, Create React App, or Next.js, the JSX transformation happens automatically during the build process. You just write JSX and everything works! The tool chain includes Babel or the TypeScript compiler to handle the transformation.
TSX Type Safety
Using TSX with TypeScript gives you amazing type safety:
interface ButtonProps {
text: string;
onClick: () => void;
disabled?: boolean;
}
function MyButton({ text, onClick, disabled }: ButtonProps) {
return (
<button onClick={onClick} disabled={disabled}>
{text}
</button>
);
}
// TypeScript catches errors:
<MyButton text="Click" onClick={() => {}} /> // â
Correct
<MyButton text={123} onClick={() => {}} /> // â Error: text should be string
<MyButton onClick={() => {}} /> // â Error: text is required
đ JSX Syntax Rules
JSX looks like HTML, but it has some important differences. Let's learn the rules so you can write valid JSX!
Rule 1: Return a Single Root Element
Every JSX expression must have exactly one root element. You can't return multiple elements at the same level.
// â WRONG - Multiple root elements
function BadComponent() {
return (
<h1>Title</h1>
<p>Paragraph</p>
);
}
// â
CORRECT - Single root element wrapping everything
function GoodComponent() {
return (
<div>
<h1>Title</h1>
<p>Paragraph</p>
</div>
);
}
// â
ALSO CORRECT - Using a Fragment (we'll cover this later)
function AlsoGoodComponent() {
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
}
Rule 2: Close All Tags
In HTML, some tags can be left unclosed. In JSX, every tag must be closed!
// â WRONG in JSX (even though valid in HTML)
<img src="photo.jpg">
<br>
<input type="text">
// â
CORRECT - Self-closing tags
<img src="photo.jpg" />
<br />
<input type="text" />
Rule 3: Use camelCase for Attributes
JSX uses camelCase for most attributes instead of HTML's kebab-case.
// HTML attributes
<div class="container" tabindex="0" onclick="handleClick()">
// JSX attributes (camelCase)
<div className="container" tabIndex={0} onClick={handleClick}>
| HTML | JSX/TSX | Reason |
|---|---|---|
class |
className |
class is a reserved word in JavaScript |
for |
htmlFor |
for is a reserved word in JavaScript |
tabindex |
tabIndex |
Follows JavaScript naming convention |
onclick |
onClick |
Follows JavaScript naming convention |
maxlength |
maxLength |
Follows JavaScript naming convention |
Rule 4: Use Curly Braces for JavaScript
To embed JavaScript expressions in JSX, wrap them in curly braces {}:
const name = "Alice";
const age = 30;
// Use curly braces to embed JavaScript
const element = (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old.</p>
<p>Next year you'll be {age + 1}!</p>
</div>
);
â ī¸ JSX Expressions Must Have a Value
You can only use expressions in curly braces, not statements. Expressions evaluate to a value, statements don't.
// â
Expressions (return a value)
{name}
{2 + 2}
{user.firstName}
{isLoggedIn ? 'Logout' : 'Login'}
{numbers.map(n => n * 2)}
// â Statements (don't return a value)
{if (x > 5) { ... }} // Use ternary instead
{for (let i = 0; ...) {}} // Use map instead
{const x = 5} // Define variables outside JSX
Rule 5: Comments in JSX
Comments in JSX need to be wrapped in curly braces:
function MyComponent() {
return (
<div>
{/* This is a comment in JSX */}
<h1>Title</h1>
{/*
Multi-line comment
in JSX
*/}
<p>Content</p>
</div>
);
}
Rule 6: Boolean Attributes
For boolean attributes, you can omit the value if it's true:
// These are equivalent
<input type="text" disabled={true} />
<input type="text" disabled />
// Explicitly set to false
<input type="text" disabled={false} />
// Conditional
<button disabled={isLoading}>Submit</button>
đ§ Embedding Expressions
One of JSX's superpowers is the ability to embed JavaScript expressions directly in your markup. Let's explore all the ways you can do this!
Basic Expression Embedding
Wrap any JavaScript expression in curly braces:
function UserGreeting() {
const user = {
firstName: "Alice",
lastName: "Johnson",
age: 30
};
const currentYear = new Date().getFullYear();
return (
<div>
{/* Simple variables */}
<h1>{user.firstName} {user.lastName}</h1>
{/* Object properties */}
<p>Age: {user.age}</p>
{/* Expressions and calculations */}
<p>Birth year: {currentYear - user.age}</p>
{/* Function calls */}
<p>Uppercase name: {user.firstName.toUpperCase()}</p>
{/* Template literals */}
<p>{`Hello, ${user.firstName}!`}</p>
</div>
);
}
Expressions in Attributes
You can use expressions in attributes too:
function ImageGallery() {
const imageUrl = "https://example.com/photo.jpg";
const altText = "Beautiful landscape";
const imageWidth = 500;
const isLarge = true;
return (
<img
src={imageUrl}
alt={altText}
width={imageWidth}
className={isLarge ? "large-image" : "small-image"}
style={{
border: '2px solid #ccc',
borderRadius: '8px'
}}
/>
);
}
đĄ Style Objects
Notice the double curly braces in style={{}}? The outer braces mean "JavaScript expression", and the inner braces are a JavaScript object. Styles use camelCase: backgroundColor not background-color.
Complex Expressions
You can use more complex JavaScript expressions:
function ProductCard() {
const product = {
name: "Laptop",
price: 999,
inStock: true,
rating: 4.5,
reviews: 128
};
return (
<div className="product-card">
<h3>{product.name}</h3>
{/* Mathematical operations */}
<p>Price: ${product.price.toFixed(2)}</p>
<p>With tax: ${(product.price * 1.08).toFixed(2)}</p>
{/* String methods */}
<p>{product.name.toLowerCase().replace(/\s+/g, '-')}.jpg</p>
{/* Array methods */}
<p>Rating: {'â'.repeat(Math.floor(product.rating))}</p>
{/* Logical expressions */}
<p>{product.inStock && `${product.reviews} reviews`}</p>
</div>
);
}
Function Calls
You can call functions in JSX expressions:
function formatDate(date: Date): string {
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
function formatPrice(price: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price);
}
function OrderSummary() {
const order = {
date: new Date(),
total: 1234.56,
items: 5
};
return (
<div>
<h2>Order Summary</h2>
<p>Date: {formatDate(order.date)}</p>
<p>Total: {formatPrice(order.total)}</p>
<p>Items: {order.items}</p>
</div>
);
}
â Extract Complex Logic
When expressions get complex, extract them into functions or variables. This makes your JSX cleaner and easier to test. Keep your JSX focused on structure, not complex logic!
đ¨ Interactive: What Can Go Inside { }?
Click each expression type to see examples and understand what works in JSX curly braces:
What You Can't Do
Some things don't work in JSX expressions:
function WrongExamples() {
return (
<div>
{/* â Can't use if statements */}
{if (user.isAdmin) { return <p>Admin</p> }}
{/* â Can't use for loops */}
{for (let i = 0; i < 5; i++) { ... }}
{/* â Can't declare variables */}
{const message = "Hello"}
{/* â Can't use multiple statements */}
{
const x = 5;
return x * 2;
}
</div>
);
}
function CorrectExamples() {
// â
Declare variables outside JSX
const message = "Hello";
const items = [1, 2, 3, 4, 5];
return (
<div>
{/* â
Use ternary for conditions */}
{user.isAdmin ? <p>Admin</p> : <p>User</p>}
{/* â
Use map for loops */}
{items.map(item => <p key={item}>{item}</p>)}
{/* â
Use variables */}
<p>{message}</p>
</div>
);
}
đˇī¸ JSX Attributes
Attributes in JSX work similarly to HTML, but with some important differences. Let's explore how to work with them effectively!
String Attributes
String values can be wrapped in quotes or curly braces:
// Both work the same
<img src="photo.jpg" alt="A photo" />
<img src={"photo.jpg"} alt={"A photo"} />
// Use quotes for static strings
<input type="text" placeholder="Enter your name" />
// Use curly braces for dynamic strings
const placeholder = "Enter your name";
<input type="text" placeholder={placeholder} />
Number Attributes
Numbers must be in curly braces (no quotes!):
// â
Correct
<input type="number" max={100} min={0} step={5} />
<img width={300} height={200} />
<div style={{ fontSize: 16, padding: 20 }} />
// â Wrong - these become strings!
<input type="number" max="100" min="0" /> // Strings, not numbers!
Boolean Attributes
Boolean attributes have special shorthand:
// All three are equivalent for true
<input type="checkbox" checked={true} />
<input type="checkbox" checked />
<button disabled />
// For false, must be explicit
<input type="checkbox" checked={false} />
<button disabled={false} />
// Dynamic based on state
const [isChecked, setIsChecked] = useState(false);
<input type="checkbox" checked={isChecked} />
The className Attribute
Use className instead of class:
// â Wrong
<div class="container"></div>
// â
Correct
<div className="container"></div>
// Dynamic className
const isActive = true;
<button className={isActive ? "btn-active" : "btn-inactive"}>
Click
</button>
// Multiple classes
const classes = ["btn", "btn-primary", "btn-lg"].join(" ");
<button className={classes}>Click</button>
// Template literal for multiple classes
<div className={`container ${isActive ? 'active' : ''}`}>
Content
</div>
The style Attribute
The style attribute takes a JavaScript object, not a string:
// â Wrong - style is not a string in React
<div style="color: red; font-size: 20px;">Text</div>
// â
Correct - style is an object with camelCase properties
<div style={{ color: 'red', fontSize: '20px' }}>Text</div>
// Style object extracted for clarity
const cardStyle = {
backgroundColor: '#f0f0f0',
padding: '1rem',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
};
<div style={cardStyle}>Card content</div>
// Dynamic styles
const isError = true;
<p style={{
color: isError ? 'red' : 'green',
fontWeight: isError ? 'bold' : 'normal'
}}>
Message
</p>
â ī¸ Style Performance Note
Inline styles are fine for dynamic values, but for static styles, use CSS classes. CSS classes are more performant and easier to maintain. Use inline styles when you need to compute styles based on props or state.
Event Handler Attributes
Event handlers are camelCase and take functions:
function EventExample() {
const handleClick = () => {
console.log('Clicked!');
};
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log('Input value:', event.target.value);
};
return (
<div>
{/* Function reference - no parentheses! */}
<button onClick={handleClick}>Click me</button>
{/* Inline arrow function */}
<button onClick={() => console.log('Inline click')}>
Click me too
</button>
{/* With parameters */}
<button onClick={() => handleClick()}>
Click with params
</button>
{/* Input events */}
<input
type="text"
onChange={handleInputChange}
onFocus={() => console.log('Focused')}
onBlur={() => console.log('Blurred')}
/>
</div>
);
}
Data Attributes
Custom data attributes work just like HTML:
// Data attributes for storing custom data
<div data-user-id="123" data-role="admin">
User info
</div>
// Accessing in event handlers
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
const userId = event.currentTarget.dataset.userId;
const role = event.currentTarget.dataset.role;
console.log(userId, role);
};
<div
data-user-id="123"
data-role="admin"
onClick={handleClick}
>
Click me
</div>
Spreading Attributes
You can spread an object of attributes:
function Input() {
const inputProps = {
type: 'text',
placeholder: 'Enter text',
maxLength: 50,
required: true
};
// Spread all properties onto the input
return <input {...inputProps} />;
}
// Useful for forwarding props
interface ButtonProps {
text: string;
[key: string]: any; // Allow any other props
}
function CustomButton({ text, ...rest }: ButtonProps) {
return (
<button {...rest}>
{text}
</button>
);
}
// Use it
<CustomButton
text="Click me"
onClick={() => {}}
disabled={false}
className="btn-primary"
/>
đ Conditional Rendering
One of the most common patterns in React is rendering different content based on conditions. Let's explore all the techniques for conditional rendering!
Method 1: Ternary Operator (Most Common)
The ternary operator is perfect for choosing between two options:
function WelcomeMessage({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
<div>
{isLoggedIn ? (
<h1>Welcome back!</h1>
) : (
<h1>Please log in.</h1>
)}
</div>
);
}
// Inline content
function UserGreeting({ user }: { user: any }) {
return (
<p>
{user ? `Hello, ${user.name}!` : 'Hello, Guest!'}
</p>
);
}
Method 2: Logical AND (&&)
Use && when you want to render something or nothing:
function Notifications({ count }: { count: number }) {
return (
<div>
<h2>Notifications</h2>
{/* Only show if count > 0 */}
{count > 0 && (
<p>You have {count} new notifications!</p>
)}
</div>
);
}
// Multiple conditions
function UserProfile({ user, isAdmin }: any) {
return (
<div>
<h2>{user.name}</h2>
{user.email && <p>Email: {user.email}</p>}
{user.phone && <p>Phone: {user.phone}</p>}
{isAdmin && <span className="badge">Admin</span>}
</div>
);
}
â ī¸ Watch Out: Falsy Values
Be careful with &&! React renders 0 and "" (empty string). If the left side is 0, it will show 0 in your UI!
// â Will display "0" if count is 0
{count && <p>{count} items</p>}
// â
Correctly shows nothing if count is 0
{count > 0 && <p>{count} items</p>}
// â
Alternative: use boolean conversion
{!!count && <p>{count} items</p>}
Method 3: If-Else with Variables
For complex conditions, use if-else outside JSX:
function StatusMessage({ status }: { status: string }) {
let message;
let icon;
let className;
if (status === 'success') {
message = 'Operation completed successfully!';
icon = 'â
';
className = 'success';
} else if (status === 'error') {
message = 'An error occurred.';
icon = 'â';
className = 'error';
} else if (status === 'loading') {
message = 'Loading...';
icon = 'âŗ';
className = 'loading';
} else {
message = 'Ready';
icon = 'âĒ';
className = 'default';
}
return (
<div className={className}>
<span>{icon}</span>
<p>{message}</p>
</div>
);
}
Method 4: Switch Statement with Function
Extract complex conditionals into a function:
type AlertType = 'info' | 'success' | 'warning' | 'error';
function Alert({ type, message }: { type: AlertType; message: string }) {
const renderIcon = () => {
switch (type) {
case 'info':
return 'âšī¸';
case 'success':
return 'â
';
case 'warning':
return 'â ī¸';
case 'error':
return 'â';
default:
return 'đĸ';
}
};
const getClassName = () => {
return `alert alert-${type}`;
};
return (
<div className={getClassName()}>
<span className="icon">{renderIcon()}</span>
<p>{message}</p>
</div>
);
}
Method 5: Early Return
Return early for simple cases:
function UserDashboard({ user }: { user: any }) {
// Early return for loading state
if (!user) {
return <p>Loading...</p>;
}
// Early return for error state
if (user.error) {
return <p>Error: {user.error}</p>;
}
// Main content
return (
<div>
<h1>Welcome, {user.name}!</h1>
<p>Email: {user.email}</p>
</div>
);
}
Nested Conditionals
You can nest conditions, but try to keep them simple:
function ProductCard({ product, user }: any) {
return (
<div className="product">
<h3>{product.name}</h3>
<p>${product.price}</p>
{product.inStock ? (
<div>
{user ? (
user.isPremium ? (
<button>Buy Now - 10% Off!</button>
) : (
<button>Buy Now</button>
)
) : (
<button>Login to Purchase</button>
)}
</div>
) : (
<p>Out of Stock</p>
)}
</div>
);
}
// Better: Extract to a function
function ProductCard({ product, user }: any) {
const renderButton = () => {
if (!product.inStock) return <p>Out of Stock</p>;
if (!user) return <button>Login to Purchase</button>;
if (user.isPremium) return <button>Buy Now - 10% Off!</button>;
return <button>Buy Now</button>;
};
return (
<div className="product">
<h3>{product.name}</h3>
<p>${product.price}</p>
{renderButton()}
</div>
);
}
â Best Practices for Conditionals
- Simple conditions: Use ternary or
&&directly in JSX - Complex conditions: Extract to variables or functions
- Multiple states: Use if-else or switch statements
- Deep nesting: Break into separate components or functions
- Early returns: Handle edge cases first, then main content
đ¨ Interactive: Conditional Rendering Simulator
Toggle the conditions to see how different rendering methods work:
đ Rendering Lists
Displaying lists of data is one of the most common tasks in React. Let's learn how to render arrays efficiently and correctly!
Basic List Rendering with map()
Use the map() method to transform an array into JSX elements:
function ShoppingList() {
const items = ['Apples', 'Bananas', 'Oranges', 'Grapes'];
return (
<ul>
{items.map(item => (
<li>{item}</li>
))}
</ul>
);
}
The Importance of Keys
React needs a key prop to identify which items have changed, been added, or removed. Keys must be unique among siblings!
// â No key - React will warn you
function BadList() {
const items = ['Apples', 'Bananas', 'Oranges'];
return (
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
);
}
// â
With key
function GoodList() {
const items = ['Apples', 'Bananas', 'Oranges'];
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
}
// â
Even better - use stable IDs
interface Product {
id: number;
name: string;
price: number;
}
function ProductList({ products }: { products: Product[] }) {
return (
<ul>
{products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
);
}
â ī¸ Why Not Use Index as Key?
Using array indexes as keys can cause bugs when the list changes (items added, removed, or reordered). React uses keys to match elements between renders, and indexes can point to different items after changes!
// â Don't use index if list can change
{items.map((item, index) => <li key={index}>{item}</li>)}
// â
Use stable unique IDs
{items.map(item => <li key={item.id}>{item.name}</li>)}
// â
If no ID, create one when data loads
const itemsWithIds = rawItems.map((item, i) => ({
...item,
id: `${item.name}-${i}` // Create stable ID
}));
Rendering Complex List Items
List items can contain complex JSX:
interface User {
id: number;
name: string;
email: string;
avatar: string;
isActive: boolean;
}
function UserList({ users }: { users: User[] }) {
return (
<div className="user-list">
{users.map(user => (
<div key={user.id} className="user-card">
<img src={user.avatar} alt={user.name} />
<div className="user-info">
<h3>{user.name}</h3>
<p>{user.email}</p>
{user.isActive && (
<span className="badge">Active</span>
)}
</div>
</div>
))}
</div>
);
}
Extracting List Item Components
For better organization, extract list items into separate components:
// Child component for list item
interface TodoItemProps {
todo: {
id: number;
text: string;
completed: boolean;
};
onToggle: (id: number) => void;
onDelete: (id: number) => void;
}
function TodoItem({ todo, onToggle, onDelete }: TodoItemProps) {
return (
<li className={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
}
// Parent component rendering list
function TodoList({ todos, onToggle, onDelete }: any) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={onToggle}
onDelete={onDelete}
/>
))}
</ul>
);
}
Filtering and Sorting Lists
You can filter and sort before rendering:
function ProductCatalog({ products }: { products: Product[] }) {
const [searchTerm, setSearchTerm] = useState('');
const [sortBy, setSortBy] = useState<'name' | 'price'>('name');
// Filter products
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
);
// Sort products
const sortedProducts = [...filteredProducts].sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
} else {
return a.price - b.price;
}
});
return (
<div>
<input
type="text"
placeholder="Search products..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<select value={sortBy} onChange={(e) => setSortBy(e.target.value as any)}>
<option value="name">Sort by Name</option>
<option value="price">Sort by Price</option>
</select>
<div className="products">
{sortedProducts.map(product => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
))}
</div>
</div>
);
}
Handling Empty Lists
Always handle the case when lists are empty:
function CommentList({ comments }: { comments: any[] }) {
if (comments.length === 0) {
return <p>No comments yet. Be the first to comment!</p>;
}
return (
<div>
{comments.map(comment => (
<div key={comment.id}>
<p>{comment.text}</p>
<small>By {comment.author}</small>
</div>
))}
</div>
);
}
// Alternative: inline conditional
function MessageList({ messages }: { messages: any[] }) {
return (
<div>
{messages.length > 0 ? (
messages.map(msg => (
<div key={msg.id}>{msg.text}</div>
))
) : (
<p>No messages to display</p>
)}
</div>
);
}
Nested Lists
You can render lists within lists:
interface Category {
id: number;
name: string;
items: { id: number; name: string }[];
}
function CategoryList({ categories }: { categories: Category[] }) {
return (
<div>
{categories.map(category => (
<div key={category.id} className="category">
<h2>{category.name}</h2>
<ul>
{category.items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
))}
</div>
);
}
â List Rendering Best Practices
- Always use keys - React needs them for efficient updates
- Use stable IDs - Avoid using array indexes when possible
- Extract components - Keep list items in separate components
- Handle empty states - Show meaningful messages for empty lists
- Filter/sort wisely - Do data transformations before rendering
- Keep keys unique - Only among siblings, not globally
đ¨ Interactive: Why Keys Matter
See how React uses keys to track list items. Watch what happens when items are added or removed:
đ§Š Fragments
Remember the rule about returning a single root element? Fragments let you group elements without adding extra DOM nodes!
The Problem
Sometimes wrapping in a div causes issues:
// This adds an extra div to the DOM
function Columns() {
return (
<div>
<td>Column 1</td>
<td>Column 2</td>
</div>
);
}
// Usage - creates invalid HTML!
<table>
<tr>
<Columns /> {/* Results in: tr > div > td (invalid!) */}
</tr>
</table>
The Solution: Fragments
Fragments let you group elements without adding a DOM node:
// Method 1: React.Fragment (verbose)
function Columns() {
return (
<React.Fragment>
<td>Column 1</td>
<td>Column 2</td>
</React.Fragment>
);
}
// Method 2: Short syntax (preferred)
function Columns() {
return (
<>
<td>Column 1</td>
<td>Column 2</td>
</>
);
}
// Now the table is valid HTML!
<table>
<tr>
<Columns /> {/* Results in: tr > td (valid!) */}
</tr>
</table>
When to Use Fragments
Fragments are useful in several scenarios:
// 1. Returning multiple elements
function Header() {
return (
<>
<h1>My Site</h1>
<nav>Navigation</nav>
</>
);
}
// 2. Avoiding wrapper divs in layouts
function Layout() {
return (
<>
<Header />
<Main />
<Footer />
</>
);
}
// 3. In conditional rendering
function ConditionalContent({ show }: { show: boolean }) {
return (
<div>
{show && (
<>
<h2>Title</h2>
<p>Content</p>
<button>Action</button>
</>
)}
</div>
);
}
// 4. In lists (but you need the key prop!)
function List({ items }: { items: any[] }) {
return (
<ul>
{items.map(item => (
<React.Fragment key={item.id}>
<li>{item.name}</li>
<li>{item.description}</li>
</React.Fragment>
))}
</ul>
);
}
đĄ Fragment vs Div
Use Fragment when:
- You don't need the extra DOM node
- The extra div would break CSS (flexbox, grid)
- The extra div would create invalid HTML (tables, lists)
Use Div when:
- You need to apply styles or CSS classes
- You need to attach event handlers to the container
- You need a ref to the container element
Fragments with Keys
When you need a key prop, use the full syntax:
// â Short syntax doesn't accept props
{items.map(item => (
<> {/* Can't add key here! */}
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</>
))}
// â
Use React.Fragment for keys
{items.map(item => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.definition}</dd>
</React.Fragment>
))}
â ī¸ JSX Gotchas and Common Mistakes
JSX has some quirks that can trip up beginners (and even experienced developers!). Let's learn about the common pitfalls and how to avoid them.
1. Rendering null, undefined, boolean
React handles these values specially:
function RenderingExamples() {
return (
<div>
{null} {/* Renders nothing */}
{undefined} {/* Renders nothing */}
{true} {/* Renders nothing */}
{false} {/* Renders nothing */}
{0} {/* Renders "0" - watch out! */}
{""} {/* Renders nothing */}
{" "} {/* Renders a space */}
</div>
);
}
// The zero gotcha!
function ItemCount({ count }: { count: number }) {
return (
<div>
{/* â Will show "0" if count is 0 */}
{count && <p>{count} items</p>}
{/* â
Won't show anything if count is 0 */}
{count > 0 && <p>{count} items</p>}
</div>
);
}
2. Objects Can't Be Rendered
You can't render plain objects directly:
function ObjectExample() {
const user = { name: "Alice", age: 30 };
return (
<div>
{/* â ERROR: Objects are not valid as a React child */}
<p>{user}</p>
{/* â
Render specific properties */}
<p>{user.name}</p>
{/* â
Or convert to string */}
<p>{JSON.stringify(user)}</p>
</div>
);
}
3. Reserved Words
Some HTML attributes are JavaScript reserved words:
// â class and for are reserved in JavaScript
<div class="container">
<label for="email">Email</label>
</div>
// â
Use className and htmlFor
<div className="container">
<label htmlFor="email">Email</label>
</div>
4. Event Handlers Need Functions
Don't call the function immediately:
function ButtonExample() {
const handleClick = () => {
console.log('Clicked!');
};
return (
<div>
{/* â Calls immediately, doesn't work as expected */}
<button onClick={handleClick()}>Bad</button>
{/* â
Passes function reference */}
<button onClick={handleClick}>Good</button>
{/* â
Inline arrow function */}
<button onClick={() => handleClick()}>Also Good</button>
</div>
);
}
5. Self-Closing Tags
All tags must be properly closed:
// â HTML style - won't work in JSX
<img src="photo.jpg">
<input type="text">
<br>
// â
JSX requires self-closing tags
<img src="photo.jpg" />
<input type="text" />
<br />
6. Adjacent JSX Elements
Must have a single parent:
// â Adjacent elements - error!
return (
<h1>Title</h1>
<p>Content</p>
);
// â
Wrap in container or fragment
return (
<>
<h1>Title</h1>
<p>Content</p>
</>
);
7. Array Methods That Don't Return
Some array methods don't work in JSX:
function ListExample({ items }: { items: string[] }) {
return (
<ul>
{/* â forEach doesn't return anything */}
{items.forEach(item => <li>{item}</li>)}
{/* â
map returns a new array */}
{items.map(item => <li key={item}>{item}</li>)}
</ul>
);
}
8. Whitespace Handling
JSX handles whitespace differently than HTML:
// These produce different output:
<p>Hello World</p> // "Hello World"
<p>
Hello World
</p> // "Hello World"
<p>Hello {' '} World</p> // "Hello World"
<p>Hello</p><p>World</p> // No space between
<p>Hello</p>{' '}<p>World</p> // Space between
9. Inline Styles Must Be Objects
And use camelCase properties:
// â String styles don't work
<div style="color: red; font-size: 20px"></div>
// â
Object with camelCase properties
<div style={{ color: 'red', fontSize: '20px' }}></div>
// â kebab-case properties don't work
<div style={{ 'background-color': 'blue' }}></div>
// â
camelCase
<div style={{ backgroundColor: 'blue' }}></div>
10. The Semicolon Trap
Be careful with automatic semicolon insertion:
// â Returns undefined!
function BadComponent() {
return
<div>Hello</div>;
}
// â
Wrap in parentheses
function GoodComponent() {
return (
<div>Hello</div>
);
}
â ī¸ Quick Reference: Common Gotchas
0renders as "0" - use{'{count > 0 && ...}'}- Objects can't render - access properties instead
- Use
classNamenotclass - Use
htmlFornotfor - Event handlers get functions, not calls
- Close all tags - even
<br /> - Single root element required - use Fragment
<></> - Use
map()notforEach() - Styles are objects with camelCase properties
- Wrap return value in parentheses
đī¸ Hands-on Practice
đī¸ Exercise 1: Build a Product Catalog
Objective: Create a product catalog with filtering, sorting, and conditional rendering.
Requirements:
- Display a list of products with name, price, and stock status
- Add a search filter to find products by name
- Show "In Stock" badge for available products
- Show "Out of Stock" message for unavailable products
- Handle empty search results
Starter Code:
import { useState } from 'react';
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
const products: Product[] = [
{ id: 1, name: "Laptop", price: 999, inStock: true },
{ id: 2, name: "Mouse", price: 29, inStock: true },
{ id: 3, name: "Keyboard", price: 79, inStock: false },
{ id: 4, name: "Monitor", price: 299, inStock: true },
{ id: 5, name: "Webcam", price: 89, inStock: false }
];
function ProductCatalog() {
const [searchTerm, setSearchTerm] = useState('');
// TODO: Filter products based on search term
// TODO: Render filtered products
// TODO: Handle empty results
return (
<div>
<h1>Product Catalog</h1>
{/* TODO: Add search input */}
{/* TODO: Render product list */}
</div>
);
}
đĄ Hint 1
Use the filter() method to filter products: products.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()))
đĄ Hint 2
Use conditional rendering to show "In Stock" or "Out of Stock": {product.inStock ? "In Stock" : "Out of Stock"}
â Solution
import { useState } from 'react';
import './ProductCatalog.css';
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
const products: Product[] = [
{ id: 1, name: "Laptop", price: 999, inStock: true },
{ id: 2, name: "Mouse", price: 29, inStock: true },
{ id: 3, name: "Keyboard", price: 79, inStock: false },
{ id: 4, name: "Monitor", price: 299, inStock: true },
{ id: 5, name: "Webcam", price: 89, inStock: false }
];
function ProductCatalog() {
const [searchTerm, setSearchTerm] = useState('');
// Filter products
const filteredProducts = products.filter(product =>
product.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<div className="catalog">
<h1>Product Catalog</h1>
<input
type="text"
placeholder="Search products..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="search-input"
/>
{filteredProducts.length > 0 ? (
<div className="product-grid">
{filteredProducts.map(product => (
<div key={product.id} className="product-card">
<h3>{product.name}</h3>
<p className="price">${product.price}</p>
{product.inStock ? (
<span className="badge in-stock">In Stock</span>
) : (
<span className="badge out-of-stock">Out of Stock</span>
)}
<button disabled={!product.inStock}>
{product.inStock ? 'Add to Cart' : 'Unavailable'}
</button>
</div>
))}
</div>
) : (
<p className="no-results">
No products found matching "{searchTerm}"
</p>
)}
</div>
);
}
export default ProductCatalog;
đī¸ Exercise 2: Build a Comment Section
Objective: Create a comment section that renders a list of comments with conditional content.
Requirements:
- Display a list of comments with author, text, and timestamp
- Show "Edited" badge if comment was edited
- Show "No comments yet" if the list is empty
- Format the timestamp nicely
Starter Code:
interface Comment {
id: number;
author: string;
text: string;
timestamp: Date;
edited: boolean;
}
const comments: Comment[] = [
{
id: 1,
author: "Alice",
text: "Great article!",
timestamp: new Date('2024-01-15T10:30:00'),
edited: false
},
{
id: 2,
author: "Bob",
text: "Thanks for sharing this.",
timestamp: new Date('2024-01-15T11:45:00'),
edited: true
}
];
function CommentSection() {
// TODO: Implement comment rendering
return <div></div>;
}
â Solution
interface Comment {
id: number;
author: string;
text: string;
timestamp: Date;
edited: boolean;
}
function CommentSection({ comments }: { comments: Comment[] }) {
const formatTime = (date: Date) => {
return date.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
if (comments.length === 0) {
return (
<div className="comments">
<h3>Comments</h3>
<p className="no-comments">
No comments yet. Be the first to comment!
</p>
</div>
);
}
return (
<div className="comments">
<h3>Comments ({comments.length})</h3>
{comments.map(comment => (
<div key={comment.id} className="comment">
<div className="comment-header">
<strong>{comment.author}</strong>
<span className="timestamp">
{formatTime(comment.timestamp)}
</span>
{comment.edited && (
<span className="badge">Edited</span>
)}
</div>
<p className="comment-text">{comment.text}</p>
</div>
))}
</div>
);
}
export default CommentSection;
đ¯ Quick Quiz
Question 1: Which is the correct way to render a list in JSX?
Question 2: What's wrong with this code: <div class="container">?
Question 3: Why do list items need a key prop?
đ Best Practices
â Do's
- Use className, not class - Always use
classNamefor CSS classes in JSX - Always provide keys for lists - Use stable, unique IDs when rendering lists
- Close all tags - Self-closing tags need the
/at the end - Use camelCase for attributes -
onClick, notonclick - Keep JSX expressions simple - Extract complex logic into functions or variables
- Use fragments to avoid extra divs -
<></>when you don't need a wrapper - Format your JSX - Use Prettier or similar tools for consistent formatting
- Use descriptive keys - Prefer IDs over array indexes when possible
â Don'ts
- Don't use array index as key - Can cause bugs when list changes
- Don't call functions in event handlers - Use
onClick={handleClick}notonClick={handleClick()} - Don't use if statements in JSX - Use ternary operators or logical AND instead
- Don't render objects directly - Access specific properties or convert to string
- Don't forget parentheses around multiline JSX - Avoid semicolon insertion issues
- Don't use
0in&&conditionals - Usecount > 0instead - Don't nest too deeply - Extract nested components into separate functions
đĄ Pro Tips
- Use VS Code extensions - ES7+ React/Redux/React-Native snippets speeds up JSX writing
- Learn the Emmet shortcuts - Works in JSX files for rapid HTML-like typing
- Use TypeScript - Catches JSX errors at compile time
- Destructure props early -
const { name, age } = propsmakes JSX cleaner - Extract render functions - For complex conditionals, create separate functions
- Use optional chaining -
{user?.name}safely accesses nested properties - Group related code - Keep data transformations near where they're used
Code Organization
Keep your JSX readable by following these patterns:
function WellOrganizedComponent({ data }: { data: any }) {
// 1. Early returns for edge cases
if (!data) {
return <p>Loading...</p>;
}
// 2. Data transformations
const filteredData = data.filter(item => item.active);
const sortedData = [...filteredData].sort((a, b) => a.name.localeCompare(b.name));
// 3. Helper render functions
const renderHeader = () => (
<header>
<h1>{data.title}</h1>
</header>
);
const renderItem = (item: any) => (
<div key={item.id}>
<h3>{item.name}</h3>
<p>{item.description}</p>
</div>
);
// 4. Main render - clean and readable
return (
<div>
{renderHeader()}
<main>
{sortedData.length > 0 ? (
sortedData.map(renderItem)
) : (
<p>No items to display</p>
)}
</main>
</div>
);
}
Performance Tips
- Avoid inline functions when possible - Extract event handlers to avoid recreating them
- Use CSS classes over inline styles - Better performance and maintainability
- Memoize expensive computations - Use useMemo for heavy data transformations (we'll learn this later)
- Keep keys stable - Don't generate random keys on every render
đ Summary
đ Congratulations!
You've mastered JSX and TSX! Here's what you now know:
- JSX/TSX fundamentals - HTML-like syntax in JavaScript/TypeScript
- Syntax rules - Single root, closed tags, camelCase attributes
- Embedding expressions - Using
{}to include JavaScript - Attributes - className, style objects, event handlers
- Conditional rendering - Ternary, &&, if/else patterns
- List rendering - Using map() with proper keys
- Fragments - Grouping without extra DOM nodes
- Common gotchas - And how to avoid them!
đ¯ Key Takeaways
- JSX makes React components intuitive by mixing markup with logic
- TypeScript + JSX = TSX with full type safety
- Use
className, notclass - Expressions in
{}, not statements - Map arrays to JSX, always with keys
- Ternary and && for conditional rendering
- Fragments avoid unnecessary DOM nodes
- Keep JSX simple, extract complex logic
Quick Reference: JSX Cheat Sheet
| Concept | Syntax | Example |
|---|---|---|
| Expression | {'{expression}'} |
{'{name}'} |
| Attribute | attr={'{value}'} |
className="btn" |
| Conditional | {'{cond ? a : b}'} |
{'{isActive ? "Yes" : "No"}'} |
| Show/Hide | {'{cond && element}'} |
{'{count > 0 && |
| List | {'{arr.map(...)}'} |
{'{items.map(i => |
| Fragment | <></> |
<><h1>Title</h1><p>Text</p></> |
| Comment | {'{/* ... */}'} |
{'{/* This is a comment */}'} |
đ Additional Resources
- React Docs: Writing Markup with JSX
- React Docs: JavaScript in JSX with Curly Braces
- React Docs: Conditional Rendering
- React Docs: Rendering Lists
- React TypeScript Cheatsheet
đ What's Next?
In the next lesson, we'll dive into Components and Props. You'll learn:
- How to create reusable component hierarchies
- Passing data with props and prop validation
- Component composition patterns
- Children props and component flexibility
- Best practices for component design
You've got the syntax down - now let's build some real components! đĒ
đ JSX Master Unlocked!
You can now write JSX with confidence! From simple expressions to complex conditionals and lists, you've got all the tools you need to describe your UI declaratively.
Ready to build amazing components! đ