JSX Is JavaScript in Disguise
JSX is the HTML-looking syntax you write inside React files. The name stands for JavaScript XML, and the single most useful thing to understand about it is that it is not HTML and not a template language. It is a shorthand that your build tool rewrites into ordinary JavaScript function calls before the browser ever sees it.
Those function calls produce plain JavaScript objects. Writing <h1 className="title">Hello</h1> gives you an object that says: element type h1, props { className: 'title' }, children 'Hello'. Nothing has touched the page at this point. React collects those objects, works out what changed, and only then updates the DOM.
This explains a lot of behaviour that otherwise looks arbitrary. A piece of JSX is a value, so you can store it in a variable, put it in an array, pass it as a prop or return it from a function — because underneath, you are just moving an object around. It also explains why a component must return exactly one thing: a function can only return one value.
// What you write
const heading = <h1 className="title">Hello</h1>;
// Roughly what the build tool produces
const heading = createElement('h1', { className: 'title' }, 'Hello');
// Which evaluates to a plain object, something like:
// { type: 'h1', props: { className: 'title', children: 'Hello' }, ... }
// Because it is just a value, all of this is legal:
const rows = [<li key="a">One</li>, <li key="b">Two</li>];
const maybe = isLoggedIn ? <Dashboard /> : <Login />;
function wrap(content) {
return <div className="box">{content}</div>;
} - Older React code had
import React from 'react'at the top of every file containing JSX, because the compiled output calledReact.createElementdirectly. Modern setups, including Vite's React template, use a newer JSX transform that pulls in what it needs automatically, so you will not see that line in new projects.
Curly Braces Hold Expressions, Not Statements
Inside JSX, a pair of curly braces means drop out of markup and evaluate this JavaScript. Whatever the JavaScript evaluates to gets inserted at that spot. You can use braces in two places: as the content between tags, and as the value of an attribute.
The rule that catches everyone is that braces accept an expression — something that produces a value — and not a statement. A ternary is an expression, so it works. A function call is an expression, so it works. An if block, a for loop and a switch are statements, so they are syntax errors inside JSX. This is not React being fussy; it is a direct consequence of JSX compiling down to function arguments, and you cannot pass an if statement as a function argument.
When you need branching logic, you have two clean options: compute the value in a normal variable above the return, where all of JavaScript is available to you, or use a ternary inside the braces. Both are used constantly in real code, and choosing the first one whenever the logic is more than a line long keeps your JSX readable.
function OrderStatus({ order, user }) {
// Statements live ABOVE the return, where they are legal.
let label;
if (order.cancelled) label = 'Cancelled';
else if (order.deliveredOn) label = 'Delivered';
else label = 'On the way';
const total = order.items.reduce((sum, i) => sum + i.price, 0);
return (
<div className="order">
{/* This is how you write a comment inside JSX */}
<h3>Order #{order.id}</h3>
{/* Expressions of every kind are fine */}
<p>Placed by {user.name.toUpperCase()}</p>
<p>Items: {order.items.length}</p>
<p>Total: ₹{total}</p>
<p>Status: {label}</p>
<p>{order.items.length > 3 ? 'Bulk order' : 'Standard order'}</p>
{/* Braces in an attribute value, not just in content */}
<img src={order.thumbnailUrl} alt={`Order ${order.id}`} width={120} />
</div>
);
// <p>{ if (order.cancelled) { ... } }</p> <-- syntax error, always
} {value}as content inserts the value between the tagsattr={value}passes a real JavaScript value, not a string- Quotes mean a literal string:
width="120"is the text 120,width={120}is the number - Allowed inside braces: variables, arithmetic, function calls, ternaries,
&&,map - Not allowed inside braces:
if,for,while,switch, variable declarations - A JSX comment is
{/* like this */}— plain HTML comments do not work
- Braces inside an attribute that also uses quotes is a common beginner mix-up. It is either
className="card"orclassName={styleVariable}— neverclassName="{styleVariable}", which passes the literal seven characters.
Where JSX Parts Company with HTML
JSX borrows HTML's look but has to obey JavaScript's rules, which produces a short list of differences. They are all mechanical, and your editor will flag most of them, but knowing why stops them from feeling random.
class and for are reserved words in JavaScript, so JSX uses className and htmlFor instead. Multi-word attributes become camelCase — onclick is onClick, tabindex is tabIndex, maxlength is maxLength — because they end up as keys on a JavaScript object. Every tag must be explicitly closed, including ones HTML lets you leave open: <br />, <img />, <input />.
The style attribute changes shape entirely. In HTML it is a string; in JSX it is a JavaScript object, which is why you see two sets of braces — the outer pair means expression and the inner pair is the object itself. Its keys are camelCase, and for numeric values React adds px for you on properties that need a unit, while leaving unitless properties such as opacity, zIndex, flex and lineHeight alone.
// HTML
// <div class="card" style="background-color: white; padding: 16px">
// <label for="email">Email</label>
// <input type="email" id="email" maxlength="80" onclick="go()">
// <br>
// </div>
// The same thing in JSX
<div className="card" style={{ backgroundColor: 'white', padding: 16 }}>
<label htmlFor="email">Email</label>
<input type="email" id="email" maxLength={80} onClick={go} />
<br />
</div>
// padding: 16 -> React writes padding: 16px
// opacity: 0.5 -> React writes opacity: 0.5, with no unit added classbecomesclassName;forbecomeshtmlFor- Attributes are camelCase:
onChange,tabIndex,readOnly,autoFocus - Every element must be closed —
<img />, not<img> styletakes an object with camelCase keys, not a CSS string- Custom component names must start with a capital letter, or React treats them as HTML tags
and other HTML entities still work in JSX text
One Root Element, and Fragments
A component returns one value, so its JSX must have exactly one outermost element. Returning two sibling elements side by side is a syntax error. The obvious fix is to wrap them in a <div>, and for a while that is fine — until you notice that every layer of your app has added a wrapper div, and the extra nesting is quietly breaking a CSS grid or flex layout that expected the children to be direct descendants.
A Fragment solves that. Written as an empty pair of tags, <> and </>, it groups elements for JavaScript's benefit but produces no DOM node at all. Use it whenever a wrapper element would exist only to satisfy the one-root rule.
There is one situation where the short syntax will not do. When you render a list, each item needs a key, and the shorthand fragment cannot take attributes. In that case use the long form, <Fragment key={id}>, imported from React.
import { Fragment } from 'react';
// Error: two roots
function Bad() {
return (
<h2>Title</h2>
<p>Text</p>
);
}
// Works, but adds a div to the DOM you may not want
function WithDiv() {
return (
<div>
<h2>Title</h2>
<p>Text</p>
</div>
);
}
// Works and adds nothing to the DOM
function WithFragment() {
return (
<>
<h2>Title</h2>
<p>Text</p>
</>
);
}
// A keyed fragment needs the long form
function DefinitionList({ entries }) {
return (
<dl>
{entries.map(entry => (
<Fragment key={entry.id}>
<dt>{entry.term}</dt>
<dd>{entry.meaning}</dd>
</Fragment>
))}
</dl>
);
} - Fragments matter most inside table rows and CSS grid or flex containers, where an extra
<div>is not merely untidy — it changes how the layout behaves.
What React Puts on Screen, and What It Skips
When you write {something}, React has to decide what to show. Strings and numbers are printed as text. Arrays are rendered element by element, which is exactly why map works in JSX without any extra ceremony. And four values render nothing at all: null, undefined, true and false. That last group is what makes conditional rendering possible, and it is also the source of one of React's most common surprises.
The surprise is that 0 is not in the skip list. It is a number, so React prints it. Write {cart.length && <Summary />} with an empty cart and JavaScript evaluates the expression to 0, which React dutifully renders as a stray zero on your page. The fix is to make the left side a real boolean — {cart.length > 0 && <Summary />}. A whole lesson later in this course is devoted to conditional rendering, but this particular trap is worth meeting early because it looks like a rendering bug rather than a JavaScript one.
One more rule: React refuses to render a plain object. Try to print {user} where user is { name: 'Riya' } and you get the error Objects are not valid as a React child. React has no idea how you want an object displayed. Print a specific field, or turn it into a string yourself.
- Strings and numbers — rendered as text
- Arrays — every item rendered in order
null,undefined,true,false— render nothing0— renders as the character 0, which is the classic&&bug- Objects and Dates — throw an error; use
user.nameordate.toLocaleDateString() - Another element or component — rendered normally
{items.length && <List />}shows a bare 0 when the list is empty. Write{items.length > 0 && <List />}instead.
The Mistakes That Cost Beginners an Hour
Most JSX errors are typos with clear messages. One is not, and it deserves its own warning because the code looks perfectly correct and simply renders nothing.
JavaScript automatically inserts a semicolon after a bare return at the end of a line. So if you write return and then start your JSX on the next line, the function returns undefined and your component renders empty. There is no error and no warning. The fix is to open a parenthesis on the same line as return, which is why almost every multi-line component you will ever read is written that way.
// BROKEN: returns undefined, renders nothing, warns about nothing
function Broken() {
return
<h1>Where did my heading go?</h1>;
}
// FIXED: the opening bracket stays on the return line
function Fixed() {
return (
<h1>There it is</h1>
);
}
// Single-line JSX needs no parentheses at all
function Tiny() {
return <h1>Also fine</h1>;
} returnalone on its own line — silently returnsundefined- Using
classinstead ofclassName— React warns in the console - Forgetting to close
<img>or<input>— a build error, not a runtime one - Writing a component in lower case —
<profile />is treated as an unknown HTML tag and renders nothing - Trying to use
ifinside braces — move the logic above thereturn - Rendering an object directly — pick a field out of it first
