Skip to main content

🏷️ JSX Syntax

In the last lesson you saw HTML-looking markup living right inside JavaScript functions. That is JSX, and it's the syntax you'll write in every React component from now on. It feels like HTML on purpose — but it follows JavaScript's rules, not the browser's. This lesson makes those differences explicit so JSX stops feeling like magic and starts feeling like a tool you control.

Week 4 · Monday: React Fundamentals · Lecture 2

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain what JSX is and what a build step compiles it into
  • List the key differences between JSX and HTML (className, camelCase, self-closing)
  • Embed JavaScript expressions in markup with curly braces
  • Render lists with .map() and unique keys
  • Choose the right conditional-rendering pattern (ternary, &&, fragments)
  • Avoid the classic JSX beginner traps

Estimated Time: 60 minutes

Practice: Convert HTML to JSX and build a conditional notification component.

In This Lesson

What is JSX?

JSX (JavaScript XML) is a syntax extension that lets you write HTML-like markup directly inside JavaScript. Browsers don't understand JSX — a build tool called Babel (already wired up by Vite) compiles it into ordinary function calls before your code ever runs.

The analogy: JSX is like a chef's shorthand on a recipe card. It looks readable and familiar, but before anyone cooks, it's expanded into precise, literal steps.

graph LR A["JSX you write
<h1>Hello</h1>"] -->|Babel compiles| B["React.createElement
('h1', null, 'Hello')"] B -->|React renders| C[Real DOM node] C -->|Browser paints| D[Visible UI]

So this JSX:

const element = <h1 className="greeting">Hello, world!</h1>;

compiles to this plain JavaScript, which produces a plain object describing the UI:

const element = React.createElement(
    'h1',
    { className: 'greeting' },
    'Hello, world!'
);

// Which is really just an object:
// { type: 'h1', props: { className: 'greeting', children: 'Hello, world!' } }

💡 JSX is optional — but you'll always use it

You could call React.createElement by hand, but nobody does. JSX is far more readable, and because it compiles to a plain expression, you can treat a piece of JSX like any other value: store it in a variable, return it, or put it in an array.

JSX vs. HTML

JSX looks like HTML but follows JavaScript's rules. Here are the differences that matter day to day.

ConceptHTMLJSX
CSS classclass="box"className="box"
Event handleronclick="fn()"onClick={fn}
Label attributefor="id"htmlFor="id"
Void elements<br>, <img><br />, <img /> (must close)
Inline stylestyle="color:red"style={{ color: 'red' }} (object)

1. className, not class

Because class is a reserved word in JavaScript, JSX uses className:

// HTML:  <div class="container">Content</div>
<div className="container">Content</div>

2. camelCase attributes

Most DOM attributes become camelCase, and event handlers take a function, not a string:

// HTML:  <div onclick="handleClick()" tabindex="0">
<div onClick={handleClick} tabIndex={0}>Click me</div>

3. Every tag must close

JSX is strict: void elements self-close, and every element you open must be closed.

<img src="photo.jpg" alt="A photo" />   {/* must self-close */}
<br />
<input type="text" />

4. Inline styles are objects

// Styles are a JS object with camelCase keys:
const boxStyle = { color: 'blue', backgroundColor: 'lightgray', padding: '10px' };

function Box() {
    return <div style={boxStyle}>Styled with a JS object</div>;
}

// Or inline — note the DOUBLE braces: outer {} = "JS here", inner {} = the object
<div style={{ color: 'red', fontSize: '20px' }}>Inline styled</div>

Embedding JavaScript

The real power of JSX is dropping JavaScript expressions right into your markup with curly braces { }. Think of them as windows through which live JavaScript shines into your HTML.

function Greeting() {
    const name = 'Sarah';
    const hour = new Date().getHours();

    return (
        <div>
            <h1>Hello, {name}!</h1>               {/* a variable */}
            <p>It's {hour} o'clock.</p>
            <p>2 + 2 = {2 + 2}</p>                {/* any expression */}
            <p>Today is {new Date().toLocaleDateString()}</p>
        </div>
    );
}

⚠️ Expressions only — no statements

Curly braces accept anything that produces a value: variables, math, function calls, ternaries, .map(). They do not accept statements like if, for, or let. If you need branching, use a ternary (below) or compute the value above the return.

Because JSX itself is an expression, you can assign it to variables and return it from functions:

function getGreeting(user) {
    if (user) {
        return <h1>Hello, {user.name}!</h1>;   // compute before return
    }
    return <h1>Hello, stranger!</h1>;
}

const button = <button>Click me</button>;      // JSX in a variable

Conditional Rendering

Since you can't use an if statement inside JSX, React leans on JavaScript expressions to show or hide things.

Ternary — either/or

function Status({ isLoggedIn }) {
    return (
        <div>
            {isLoggedIn ? <LogoutButton /> : <LoginButton />}
        </div>
    );
}

Logical && — show only when true

function Inbox({ notifications }) {
    return (
        <div>
            {notifications.length > 0 && (
                <span>You have {notifications.length} notifications</span>
            )}
        </div>
    );
}

⚠️ The 0 trap with &&

If the left side of && is the number 0, React renders "0" on the screen instead of nothing. Guard against it: use notifications.length > 0 && … (a real boolean) rather than notifications.length && ….

Compute above the return — for anything complex

function Dashboard({ user }) {
    let content;
    if (!user)        content = <LoginForm />;
    else if (user.admin) content = <AdminPanel />;
    else              content = <UserHome />;

    return <main>{content}</main>;
}

Lists & Keys

To render a list, map an array to an array of JSX elements. Each element needs a unique key so React can track which items changed, were added, or removed.

function TodoList({ todos }) {
    return (
        <ul>
            {todos.map(todo => (
                <li key={todo.id}>{todo.text}</li>
            ))}
        </ul>
    );
}

// Given:
const todos = [
    { id: 1, text: 'Learn JSX' },
    { id: 2, text: 'Render a list' },
    { id: 3, text: 'Add keys' },
];
A data array maps to a list of JSX list items, each tagged with a stable key Data array { id: 1, ... } { id: 2, ... } { id: 3, ... } .map() JSX list <li key={1}>…</li> <li key={2}>…</li> <li key={3}>…</li>
Each rendered item carries a stable key (an id), so React can update the list efficiently.

⚠️ Don't use the array index as a key

Using key={index} seems easy, but it breaks when the list is reordered, filtered, or has items inserted — React mixes up which item is which. Use a stable, unique id from your data whenever possible.

Fragments & Safety

Return one root element

A component must return a single root. When you have siblings and don't want an extra wrapper <div> in the DOM, wrap them in a Fragment — the empty <>…</> tags:

function Header() {
    return (
        <>
            <h1>Title</h1>
            <p>Subtitle</p>
        </>
    );
}

// When a fragment needs a key (in a list), use the long form:
<React.Fragment key={item.id}>
    <dt>{item.term}</dt>
    <dd>{item.description}</dd>
</React.Fragment>

JSX escapes values automatically

Anything you put inside { } is rendered as text, never as live HTML. This protects you from cross-site scripting (XSS) by default:

const userInput = '<script>alert("XSS!")</script>';

// Safe — React shows the characters, it does NOT run the script:
<div>{userInput}</div>
// Renders the literal text &lt;script&gt;… on the page

✅ Comments in JSX

JSX comments go inside braces: {/* like this */}. A plain <!-- HTML comment --> won't work inside JSX.

Common Gotchas

1. Multiple elements need a wrapper

// ❌ Two roots — syntax error
function Wrong() {
    return (
        <h1>Title</h1>
        <p>Text</p>
    );
}

// ✅ Wrap in a fragment (or a real element)
function Right() {
    return (
        <>
            <h1>Title</h1>
            <p>Text</p>
        </>
    );
}

2. Expressions, not statements

// ❌ if is a statement — not allowed inside braces
<div>{if (ok) { return 'Yes' }}</div>

// ✅ Use a ternary expression
<div>{ok ? 'Yes' : 'No'}</div>

3. You can't render a raw object

const user = { name: 'John', age: 30 };

// ❌ "Objects are not valid as a React child"
<div>{user}</div>

// ✅ Render its properties (strings/numbers are fine)
<div>{user.name}, {user.age}</div>

What React tells you

Error: Objects are not valid as a React child
(found: object with keys {name, age}).
Fix: render user.name and user.age, or JSON.stringify(user).

Practice & Quiz

🏋️ Exercise 1: HTML → JSX

Goal: Convert this HTML into valid JSX inside a component.

<div class="card">
    <img src="profile.jpg" alt="Profile">
    <h3 class="name">John Doe</h3>
    <p>Web Developer</p>
    <button onclick="handleContact()">Contact</button>
</div>
💡 Hint

Four changes: classclassName, self-close the <img>, onclick="…"onClick={handleContact}, and wrap it all in a function that returns the JSX.

✅ Solution
function ProfileCard({ handleContact }) {
    return (
        <div className="card">
            <img src="profile.jpg" alt="Profile" />
            <h3 className="name">John Doe</h3>
            <p>Web Developer</p>
            <button onClick={handleContact}>Contact</button>
        </div>
    );
}

🏋️ Exercise 2: Conditional notification

Goal: Build a Notification component that shows an icon and message based on a type prop ('success' | 'error' | 'info').

function Notification({ type, message }) {
    // TODO: pick an icon based on type, then render icon + message
}
💡 Hint

Compute the icon above the return using an object lookup: { success: '✅', error: '❌', info: 'ℹ️' }[type]. Fall back to a default with ?? 'ℹ️'.

✅ Solution
function Notification({ type, message }) {
    const icon = { success: '✅', error: '❌', info: 'ℹ️' }[type] ?? 'ℹ️';
    return (
        <div className={`notification notification-${type}`}>
            <span aria-hidden="true">{icon}</span> {message}
        </div>
    );
}

// <Notification type="success" message="Saved!" />

🎯 Quick Quiz

Question 1: Why does JSX use className instead of class?

Question 2: What can go inside JSX curly braces { }?

Question 3: When rendering a list, the key prop should be…

Best Practices & Pitfalls

✅ Do

  • Use className, camelCase attributes, and self-closing void tags
  • Keep logic above the return; keep JSX readable
  • Give every list item a stable, unique key
  • Use fragments <>…</> to avoid needless wrapper <div>s

❌ Don't

  • Put if/for statements inside curly braces — use expressions
  • Use the array index as a key in dynamic lists
  • Render raw objects — pull out their string/number properties
  • Guard with a number (list.length && …) — it can print 0

⚠️ Semantic HTML still matters

JSX gives you all of HTML. Reach for real semantic tags — <header>, <main>, <article>, <button> — not a wall of <div>s. Accessibility and SEO depend on it.

Summary

🎉 Key Takeaways

  • JSX is HTML-like syntax in JavaScript; Babel compiles it to React.createElement calls
  • Key differences from HTML: className, camelCase, self-closing tags, style objects
  • Embed any JavaScript expression with curly braces — not statements
  • Render conditionally with ternaries and &&; compute complex branches above the return
  • Map arrays to lists and give each item a stable, unique key
  • Return one root (use a fragment); JSX escapes values to prevent XSS

📚 Additional Resources

🚀 What's Next?

You can now write JSX confidently. Next we turn that markup into reusable building blocks and learn how to feed them data: Components and Props — the heart of every React application.

🎉 JSX unlocked!

Every component you build from here is JSX plus a little JavaScript. You've got the syntax — now let's structure real components.