📝 JSX Syntax and Expression Integration
JSX is the syntax that makes React feel natural: HTML-like markup living right inside your JavaScript. This lesson demystifies it — what it compiles to, where it differs from HTML, and how to weave live data, conditions, and lists into your UI with confidence.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain that JSX is syntactic sugar that compiles to plain JavaScript function calls
- List the key differences between JSX and HTML and correctly convert between them
- Embed JavaScript expressions in markup using curly braces
- Render UI conditionally with ternaries and the logical
&&operator — and avoid its classic pitfall - Render lists with stable keys and group elements with fragments
Estimated Time: 35–45 minutes • Difficulty: Beginner
Hands-on: Convert an HTML snippet to JSX and build a data-driven component.
In This Lesson
What Is JSX?
JSX (JavaScript XML) is a syntax extension that lets you write markup that looks like HTML directly inside JavaScript. It's React's signature feature and the reason describing UIs in React feels so intuitive.
// This is JSX
const element = <h1>Hello, world!</h1>;
JSX is optional — you could write React without it — but almost nobody does, because the alternative (raw createElement calls) is far harder to read. If you know HTML, you already know most of JSX; the superpower it adds is embedding real JavaScript inside the markup.
💡 Why blend markup and logic? The React team argues that rendering logic and markup are inherently coupled — a button's label, its click handler, and whether it's disabled all belong together. JSX keeps them in one component instead of splitting them across an HTML template and a separate script file.
📖 Key Terms
JSX: HTML-like syntax that compiles to JavaScript function calls.
Expression: any snippet of JavaScript that produces a value (e.g. user.name, 2 + 2, isOpen ? 'yes' : 'no').
Transpile: to convert source from one syntax to another — here, JSX to plain JS.
JSX Under the Hood
Browsers don't understand JSX. Before your code runs, a compiler (Babel or SWC, set up by Vite) transforms every JSX element into a function call. Understanding this transformation explains almost every JSX rule you'll meet.
plain JS objects] C --> D[React updates the DOM]
createElement(type, props, ...children) call that returns a lightweight JavaScript object describing the UI.A nested example makes the pattern clear:
// JSX
<div className="container">
<h1>{title}</h1>
</div>
// Compiled (conceptually)
React.createElement(
'div',
{ className: 'container' },
React.createElement('h1', null, title)
);
💡 The modern JSX transform
Since React 17, you no longer need to import React just to use JSX. The compiler auto-imports helpers from react/jsx-runtime. You'll still import specific things you use (like hooks), but bare JSX works without a React import.
JSX vs. HTML
JSX looks like HTML but is really JavaScript, so it follows a few different rules. These differences trip up every newcomer exactly once:
| HTML | JSX | Why |
|---|---|---|
class="box" | className="box" | class is a reserved word in JavaScript |
for="email" | htmlFor="email" | for is a reserved word |
onclick="fn()" | onClick={fn} | camelCase events; pass a function reference |
style="color:red" | style={{ color: 'red' }} | styles are JavaScript objects |
<br> | <br /> | every tag must be closed |
⚠️ Three rules to memorize
1. One root: a component must return a single parent element (or a fragment).
2. Close every tag: even void elements — <img />, <input />, <br />.
3. camelCase attributes: tabIndex, onChange, maxLength, not their lowercase HTML forms.
🏛️ An analogy: plain HTML is a static floor plan drawn on paper. JSX is an interactive 3D model — the same structure, but able to respond to conditions and data. That extra power is exactly why the rules are a little stricter.
Embedding Expressions
The heart of JSX is the pair of curly braces { }. Anything inside them is evaluated as a JavaScript expression and its result is inserted into the markup.
const name = 'John';
const greeting = <h1>Hello, {name}!</h1>;
const product = { name: 'Laptop', price: 999.99 };
const info = (
<div>
<h2>{product.name}</h2>
<p>Price: ${product.price.toFixed(2)}</p>
<p>With tax: ${(product.price * 1.08).toFixed(2)}</p>
</div>
);
What can go inside the braces
Any expression that produces a value:
- Variables and property access:
{user.firstName} - Function calls:
{formatDate(post.date)} - Arithmetic and string work:
{price * quantity},{`Hi, ${name}`} - Ternary expressions:
{isLoggedIn ? 'Logout' : 'Login'} - Array methods that return elements:
{items.map(...)}
⚠️ What cannot go inside the braces
Statements, not expressions: no if, for, or while, and no const/let declarations. Those don't produce a value. For that logic, use a ternary, an array method, or compute it before the return and reference the result in braces.
Conditional Rendering
Because JSX is just JavaScript, you render different UI by using ordinary JavaScript operators.
Ternary — for either/or
function Greeting({ isLoggedIn }) {
return (
<div>
{isLoggedIn ? <h1>Welcome back!</h1> : <h1>Please sign in.</h1>}
</div>
);
}
Logical && — for show-or-nothing
function Notifications({ messages }) {
return (
<div>
{messages.length > 0 && (
<h2>You have {messages.length} unread messages.</h2>
)}
</div>
);
}
⚠️ The famous && footgun
If the left side of && is the number 0, React renders "0" on screen instead of nothing, because 0 is falsy but still a renderable value. Always test with a real boolean:
// Bug: renders "0" when count is 0
{count && <p>{count} items</p>}
// Fix: force a boolean
{count > 0 && <p>{count} items</p>}
Compute before the return — for complex cases
When conditions get involved, the cleanest approach is to build the element in a variable first, then drop it into the JSX:
function WeatherInfo({ temperature }) {
let message;
if (temperature < 32) {
message = <p>It's freezing!</p>;
} else if (temperature < 70) {
message = <p>It's cool.</p>;
} else {
message = <p>It's warm!</p>;
}
return <div>{message}</div>;
}
✅ Rule of thumb
Use && to show something or nothing; use a ternary to choose between two options; pull logic out of the JSX (into a variable or helper) the moment it needs more than one else.
Lists and Keys
To render a collection, map an array to JSX elements. React needs a key — a stable, unique identifier — on each item so it can track which is which across renders.
function UserList({ users }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} — {user.email}
</li>
))}
</ul>
);
}
🏷️ Keys are name tags: at a conference, name tags let you recognize each person even after everyone shuffles seats. Keys do the same for React — when a list reorders, keys tell React "this is still the same item, just moved," so it can reuse the existing DOM instead of rebuilding it.
⚠️ Don't use the array index as a key
Index keys look convenient but break when items are inserted, removed, or reordered — React mismatches items and you get subtle bugs (wrong input values, lost focus). Use a stable ID from your data. Only fall back to the index for a static list that never changes.
📖 Keys are for React, not for you
The key prop is consumed by React and is not passed to your component. If a child needs that same value, pass it under a different prop name (e.g. id={user.id}).
Fragments
A component must return a single parent, but you don't always want an extra wrapper <div> polluting the DOM (and possibly breaking your layout). Fragments group children without adding a node.
// Shorthand fragment — renders no wrapper element
function ListItems() {
return (
<>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</>
);
}
When you map a list of fragments and need a key, use the full <Fragment> form (the shorthand <> can't take a key):
import { Fragment } from 'react';
function Glossary({ terms }) {
return (
<dl>
{terms.map((term) => (
<Fragment key={term.id}>
<dt>{term.name}</dt>
<dd>{term.definition}</dd>
</Fragment>
))}
</dl>
);
}
💡 Where fragments earn their keep
Tables are the classic case: you can't put a <div> between <tr> elements without invalid HTML, so a fragment lets a component return several table rows cleanly.
Best Practices
✅ Do
- Wrap multi-line JSX in parentheses after
returnfor readability. - Destructure props in the parameter list:
function Card({ title, body }). - Extract complex logic (filtering, sorting) into variables above the
return. - Use stable IDs as keys for dynamic lists.
⚠️ Avoid
- Cramming pipelines into JSX — a long
.filter().sort().map()chain inside the markup is hard to read. - Bare
{count && ...}with numbers — usecount > 0. - Index keys on lists that can reorder.
Here's the "extract logic" guideline in practice:
// Better — logic lifted out of the JSX
function ProductGrid({ products, filter }) {
const visible = products
.filter((p) => p.category === filter.category)
.filter((p) => p.price <= filter.maxPrice)
.sort((a, b) => a.price - b.price);
return (
<div className="grid">
{visible.map((p) => (
<ProductCard key={p.id} product={p} />
))}
</div>
);
}
Hands-on Exercise
🏋️ Convert HTML to JSX, then make it dynamic
Objective: Practice JSX rules, expressions, and conditional rendering in one component.
Part A — Convert this HTML to valid JSX:
<div class="user-profile">
<img src="profile.jpg" alt="User Profile" class="avatar">
<label for="status">Status:</label>
<button onclick="editProfile()">Edit</button>
</div>
Part B — Build a ProductCard component that:
- Takes a
productprop withname,price,discountPercent, andinStock. - Shows the name as a heading.
- If discounted, shows the original price struck through and the discounted price; otherwise just the price.
- Shows "In Stock" or "Out of Stock".
- Renders an "Add to Cart" button that is disabled when out of stock.
💡 Hint
For Part A: class → className, for → htmlFor, close the <img>, and pass the handler as onClick={editProfile}. For Part B: compute the discounted price above the return, then use a ternary for the price display and disabled={!product.inStock} on the button.
✅ Example solution
// Part A — valid JSX
<div className="user-profile">
<img src="profile.jpg" alt="User Profile" className="avatar" />
<label htmlFor="status">Status:</label>
<button onClick={editProfile}>Edit</button>
</div>
// Part B — ProductCard
function ProductCard({ product }) {
const { name, price, discountPercent, inStock } = product;
const hasDiscount = discountPercent > 0;
const salePrice = price * (1 - discountPercent / 100);
return (
<div className="product-card">
<h3>{name}</h3>
{hasDiscount ? (
<p>
<s>${price.toFixed(2)}</s>{' '}
<strong>${salePrice.toFixed(2)}</strong>
</p>
) : (
<p>${price.toFixed(2)}</p>
)}
<p style={{ color: inStock ? 'green' : 'red' }}>
{inStock ? 'In Stock' : 'Out of Stock'}
</p>
<button disabled={!inStock}>Add to Cart</button>
</div>
);
}
🎯 Quick Quiz
Question 1: What does JSX ultimately compile into?
Question 2: Why can {count && <p>...</p>} be buggy?
Question 3: What makes a good key when rendering a list?
Summary & Quiz
🎉 Key Takeaways
- JSX compiles to JavaScript function calls that return element objects — it's sugar, not magic.
- JSX differs from HTML: className, htmlFor, camelCase events, object style, and every tag closed.
- Curly braces embed expressions (values), never statements.
- Render conditionally with ternaries and
&&— and guard&&against the falsy-0bug. - Lists need stable keys; fragments group elements without extra DOM nodes.
📚 Further Reading
- react.dev — Writing Markup with JSX
- JavaScript in JSX with Curly Braces
- Rendering Lists (keys explained)
🚀 What's Next?
You can now describe any UI with JSX. Next we'll formalize the building block you've been writing all along — the component — and compare modern function components with the older class-based style.
🎉 JSX unlocked!
Markup plus live JavaScript is the language you'll write React in every day. Onward to components.