Skip to main content

🎨 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 .jsx files (JavaScript + JSX)
  • TSX - Used in .tsx files (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:

graph LR A[Write JSX/TSX] --> B[Babel/Compiler] B --> C[Transform to React.createElement calls] C --> D[Create React Elements] D --> E[Virtual DOM] E --> F[Real DOM] style A fill:#667eea,stroke:#333,stroke-width:2px,color:#fff style F fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff

🎨 Interactive: JSX Transformation Visualizer

Watch JSX transform into JavaScript! Click different JSX examples to see how they compile:

JSX → JavaScript Transformation JSX (What you write) <button onClick={handleClick}> Click me! </button> Babel JavaScript (What runs) React.createElement( 'button', { onClick: handleClick }, 'Click me!' ) React Element Object (Result) { type: 'button', props: { onClick: fn, children: 'Click me!' } } Click an example below to see how different JSX transforms!

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>
graph TD A[Write JSX] --> B{Valid Syntax?} B -->|Single root| C{All tags closed?} B -->|Multiple roots| X[❌ Error] C -->|Yes| D{camelCase attributes?} C -->|No| X D -->|Yes| E[✅ Valid JSX] D -->|No| X style A fill:#667eea,stroke:#333,stroke-width:2px,color:#fff style E fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff style X fill:#f44336,stroke:#333,stroke-width:2px,color:#fff

🔧 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 Can Go Inside { } in JSX? ✅ VALID Expressions Variables Math Ternary Functions Array.map() Template Lit ❌ INVALID Statements if/else for loops const/let Objects forEach() Click an expression type above

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
graph TD A[Conditional Rendering] --> B{How many options?} B -->|2 options| C[Use Ternary ?:] B -->|Show or hide| D[Use && operator] B -->|3+ options| E[Use if/else or switch] B -->|Complex logic| F[Extract to function] style A fill:#667eea,stroke:#333,stroke-width:2px,color:#fff style C fill:#4CAF50,stroke:#333,stroke-width:2px style D fill:#4CAF50,stroke:#333,stroke-width:2px style E fill:#ffc107,stroke:#333,stroke-width:2px style F fill:#2196F3,stroke:#333,stroke-width:2px

🎨 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:

React List Keys Demo ❌ Without Stable Keys (index) ✅ With Stable Keys (id) Click buttons to add/remove items and see how React tracks them

🧩 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

  • 0 renders as "0" - use {'{count > 0 && ...}'}
  • Objects can't render - access properties instead
  • Use className not class
  • Use htmlFor not for
  • Event handlers get functions, not calls
  • Close all tags - even <br />
  • Single root element required - use Fragment <></>
  • Use map() not forEach()
  • 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:

  1. Display a list of products with name, price, and stock status
  2. Add a search filter to find products by name
  3. Show "In Stock" badge for available products
  4. Show "Out of Stock" message for unavailable products
  5. 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:

  1. Display a list of comments with author, text, and timestamp
  2. Show "Edited" badge if comment was edited
  3. Show "No comments yet" if the list is empty
  4. 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 className for 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, not onclick
  • 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} not onClick={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 0 in && conditionals - Use count > 0 instead
  • 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 } = props makes 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, not class
  • 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 &&

{count}

}'}
List {'{arr.map(...)}'} {'{items.map(i =>
  • {i.name}
  • )}'}
    Fragment <></> <><h1>Title</h1><p>Text</p></>
    Comment {'{/* ... */}'} {'{/* This is a comment */}'}

    📚 Additional Resources

    🚀 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! đŸ’Ē

    graph LR A[Lesson 2.1: Intro] --> B[Lesson 2.2: JSX/TSX] B --> C[Lesson 2.3: Components & Props] C --> D[Lesson 2.4: Styling] D --> E[Lesson 2.5: Events] E --> F[Module Project] style B fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff style C fill:#667eea,stroke:#333,stroke-width:2px,color:#fff

    🎉 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! 🚀