🧬 JSX Syntax and Props
JSX lets you write markup right inside JavaScript, and props let you feed data into it. Together they are how you describe a React UI. This lesson covers JSX's rules and quirks, then goes deep on passing, defaulting, and forwarding props.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain what JSX is, how Babel compiles it, and its core syntax rules
- Embed JavaScript expressions in JSX and apply conditional rendering (&&, ternary)
- Render lists with
.map()and correct keys, and group elements with Fragments - Pass, destructure, and default props, and forward extra props with the spread operator
- Use the children prop and avoid common JSX pitfalls
Estimated Time: 35–45 minutes • Difficulty: Intermediate
Hands-on: Build a reusable, prop-driven product card that renders a list of products.
In This Lesson
What Is JSX?
JSX (JavaScript XML) is a syntax extension that lets you write HTML-like markup directly in your JavaScript. It is not required to use React, but nearly everyone does — it makes component structure far easier to read than the alternative.
Browsers can't run JSX directly. A compiler — usually Babel, built into tools like Vite and Next.js — transpiles it into plain React.createElement calls (or the modern jsx() runtime) before it reaches the browser:
Here is the same component written both ways. The JSX version on top compiles to the JavaScript below it:
// What you write (JSX)
function Welcome() {
return (
<div className="welcome">
<h1>Hello, world!</h1>
<p>Welcome to React</p>
</div>
);
}
// What Babel produces (simplified)
function Welcome() {
return React.createElement(
'div',
{ className: 'welcome' },
React.createElement('h1', null, 'Hello, world!'),
React.createElement('p', null, 'Welcome to React')
);
}
💡 The blueprint analogy. JSX is a blueprint for a building. The tags are rooms, the attributes are each room's specifications, and Babel is the architect who converts the blueprint into precise construction instructions. The DOM is the finished building.
JSX Syntax Rules
JSX looks like HTML but follows a few strict rules. Learn these five and most "why won't this compile" errors disappear.
1. Return a single root element
A component can only return one top-level element. Wrap siblings in a parent — or, better, in a Fragment (<>…</>) so you don't add an extra DOM node:
// ❌ Two root elements — won't compile
return (
<h1>Title</h1>
<p>Paragraph</p>
);
// ✅ Wrapped in a Fragment
return (
<>
<h1>Title</h1>
<p>Paragraph</p>
</>
);
2. Close every tag
Unlike forgiving HTML, JSX requires all tags to close. Void elements like <img>, <br>, and <input> must be self-closed with a trailing slash: <img src="x.jpg" alt="x" />.
3. camelCase most attributes
Because JSX becomes JavaScript, attribute names follow JS conventions. The two you'll hit most: class becomes className, and for becomes htmlFor. Event handlers are camelCase too: onclick → onClick.
| HTML | JSX |
|---|---|
class="card" | className="card" |
for="email" | htmlFor="email" |
onclick="..." | onClick={handleClick} |
tabindex="0" | tabIndex="0" |
style="color:red" | style={{ color: 'red' }} |
data-* and aria-* attributes are the exception — they keep their hyphenated HTML form.
4. Embed JavaScript with curly braces
Anywhere inside JSX, { } drops you back into JavaScript for a single expression — a value, not a statement. More on this next.
5. Comment with {/* … */}
HTML comments don't work inside JSX. Use a JavaScript comment inside braces: {/* like this */}.
📖 style takes an object
Inline styles are a JavaScript object, not a string, and properties are camelCased: style={{ backgroundColor: 'navy', fontSize: '16px' }}. The outer braces enter JavaScript; the inner braces are the object literal. For real projects, prefer CSS classes or CSS Modules over inline styles.
Expressions & Conditionals
The power of JSX is that { } accepts any JavaScript expression: variables, math, function calls, ternaries. It does not accept statements like if, for, or switch — those go outside the JSX.
function UserGreeting({ user }) {
const formatName = (u) => `${u.name} (${u.age})`;
return (
<div>
{/* variables */}
<h1>Hello, {user.name}!</h1>
{/* function calls */}
<p>Welcome back, {formatName(user)}</p>
{/* math & string methods */}
<p>Next year you'll be {user.age + 1}</p>
<p>Name in caps: {user.name.toUpperCase()}</p>
</div>
);
}
Conditional rendering
Because JSX only takes expressions, you render conditionally with expressions too. The two idioms you'll use daily:
function Dashboard({ isLoggedIn, unread }) {
return (
<div>
{/* Ternary: choose between two outputs */}
{isLoggedIn
? <button>Log Out</button>
: <button>Log In</button>}
{/* Logical AND: render only when the condition is true */}
{isLoggedIn && unread > 0 && (
<p>You have {unread} unread messages</p>
)}
</div>
);
}
⚠️ The 0 trap with &&
With {count && <Badge />}, if count is 0, React renders the number 0 on screen instead of nothing — because 0 is falsy but still a renderable value. Guard with a real boolean: {count > 0 && <Badge />}, or use a ternary that returns null.
Lists, Keys & Fragments
To render a collection, map an array to an array of JSX elements. Each element needs a key — a stable, unique identifier that helps React's diffing algorithm track items across re-renders.
function TodoList({ todos }) {
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
);
}
⚠️ Don't use the array index as a key (usually)
Using key={index} looks convenient but breaks when the list can reorder, filter, or have items inserted — React mismatches items and you get subtle bugs (wrong input values, lost focus). Prefer a stable id from your data. An index is acceptable only for a static list that never changes order.
Fragments
A Fragment groups children without adding a wrapper DOM node. Use the short syntax <>…</> most of the time; use the explicit <Fragment key={…}> form when you need a key on the group (for example, rendering pairs in a list):
import { Fragment } from 'react';
function Glossary({ terms }) {
return (
<dl>
{terms.map((t) => (
<Fragment key={t.id}>
<dt>{t.word}</dt>
<dd>{t.definition}</dd>
</Fragment>
))}
</dl>
);
}
Fragments also matter for valid HTML: you can't wrap <td> cells or <option> elements in a <div> without breaking the table or select. A Fragment groups them cleanly.
Props in Depth
Props are how a parent passes data to a child. They arrive as a single object; destructuring in the parameter list is the standard, readable way to use them.
Passing and receiving
// Parent passes props
function App() {
const user = { name: 'Alice', role: 'Admin' };
return <UserBadge name={user.name} role={user.role} />;
}
// Child destructures them
function UserBadge({ name, role }) {
return <span>{name} — {role}</span>;
}
Default values
Give props defaults right in the destructuring. This replaces the old defaultProps approach for function components:
function Button({ variant = 'primary', size = 'medium', children }) {
return <button className={`btn btn-${variant} btn-${size}`}>{children}</button>;
}
// <Button>Save</Button> → primary, medium
// <Button variant="danger">Delete</Button> → danger, medium
The spread operator: forwarding props
When you want to pass most props straight through to an inner element, collect the rest with ...rest and spread them. This keeps wrapper components thin:
function TextInput({ label, id, ...rest }) {
return (
<div className="field">
<label htmlFor={id}>{label}</label>
{/* type, value, onChange, placeholder… all forwarded */}
<input id={id} {...rest} />
</div>
);
}
// <TextInput label="Email" id="email" type="email" placeholder="you@site.com" />
The children prop
Whatever you nest between a component's tags arrives as the special children prop — the key to reusable containers:
function Panel({ title, children }) {
return (
<section className="panel">
<h2>{title}</h2>
<div>{children}</div>
</section>
);
}
// <Panel title="Notes"><p>Anything can go here.</p></Panel>
✅ Type-checking props
Larger projects catch prop mistakes early with TypeScript (define an interface for the props) — the modern default. Older JavaScript codebases use the prop-types package for runtime checks. Either way, documenting a component's expected props pays for itself the first time someone misuses it.
Common Pitfalls
These trip up nearly everyone at first. Recognizing them saves hours:
- Rendering an object directly:
{user}throws "Objects are not valid as a React child." Render specific fields ({user.name}) or map the object to elements. - Forgetting
className: writingclass="..."silently does the wrong thing (or warns). AlwaysclassName. - Mutating props: props are read-only. Never assign to a prop inside a child; compute a new value instead.
- Missing keys in lists: React warns, and updates can misbehave. Always give mapped elements a stable
key. - The
0/NaN&& trap: guard with an explicit boolean comparison as shown earlier.
Console warning you'll see for the last one:
Warning: Each child in a list should have a unique "key" prop.
Hands-on Exercise
🏋️ Build a Prop-Driven Product Card
Objective: Practice props, defaults, conditional rendering, and lists in one component.
Requirements:
- Create a
ProductCardcomponent that takesname,price, andinStockprops. - Format the price as currency with
Intl.NumberFormat. - Show a green "In stock" or red "Out of stock" label using conditional rendering.
- Give
inStocka default oftrue. - Create a
ProductListthat maps an array of products toProductCards with correct keys.
const products = [
{ id: 'p1', name: 'Laptop', price: 999.99, inStock: true },
{ id: 'p2', name: 'Phone', price: 699.99, inStock: false },
{ id: 'p3', name: 'Tablet', price: 399.99 } // inStock defaults to true
];
💡 Hint
Currency: new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(price). For the label, a ternary works well: {inStock ? '✅ In stock' : '❌ Out of stock'}.
✅ Sample solution
function ProductCard({ name, price, inStock = true }) {
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(price);
return (
<article className="product-card">
<h3>{name}</h3>
<p>{formatted}</p>
<p style={{ color: inStock ? 'green' : 'red' }}>
{inStock ? '✅ In stock' : '❌ Out of stock'}
</p>
</article>
);
}
function ProductList({ products }) {
return (
<div className="product-list">
{products.map((p) => (
<ProductCard
key={p.id}
name={p.name}
price={p.price}
inStock={p.inStock}
/>
))}
</div>
);
}
Best Practices
✅ Do
- Destructure props in the parameter list — it documents what a component expects.
- Extract complex logic into variables or helper functions above the
return, keeping JSX readable. - Use Fragments instead of throwaway wrapper
<div>s. - Give every mapped element a stable, unique key.
- Prefer CSS classes over inline
styleobjects for anything reused.
⚠️ Don't
- Don't put
if/forstatements inside{ }— only expressions belong there. - Don't render raw objects or arrays of objects as children.
- Don't use
&&with a numeric left side without a boolean guard. - Don't reach for
dangerouslySetInnerHTMLunless you fully trust and sanitize the HTML.
Summary & Quiz
🎉 Key Takeaways
- JSX is HTML-like syntax that Babel compiles to
createElementcalls. - Return one root element, close all tags, and use
className/htmlFor. { }embeds JavaScript expressions; conditionals use ternary or&&.- Render lists with
.map()and stable keys; group with Fragments. - Props pass data down — destructure them, default them, and forward the rest with spread; nested content is
children.
🎯 Quick Quiz
Question 1: Why must you write className instead of class in JSX?
Question 2: What is the purpose of the key prop when rendering a list?
Question 3: How does a component receive content nested between its opening and closing tags?
📚 Further Reading
- React docs — Writing Markup with JSX
- React docs — JavaScript in JSX with Curly Braces
- React docs — Rendering Lists
- React docs — Conditional Rendering
🚀 What's Next?
You can now describe any static UI and feed it data through props. Next we make it come alive: state management with Hooks — useState, useEffect, and friends — so components can remember, react, and update over time.
🎉 Nicely done!
JSX and props are the vocabulary of React. From here, everything is about behavior.