Skip to content

JSX

Building even a small piece of UI with plain JavaScript means juggling separate steps: create an element, set its class, set its text, then attach it to the page. The markup and the code driving it live apart, and keeping them in sync gets harder as the UI grows. JSX closes that gap: markup lives directly inside your JavaScript, close enough that you can mix them freely instead of keeping templates and scripts apart.

Markup inside JavaScript

Because JSX is JavaScript, you can drop any JavaScript expression into it by wrapping it in curly braces, {}. A variable, a function call, a bit of math, whatever value you need, goes straight into the markup where you'd otherwise type static text.

jsx
const name = 'Ada'
return <p className="greeting">Hello, {name}</p>

Here {name} is replaced with the value of the name variable when this renders, so the page shows "Hello, Ada". Outside the curly braces, everything is markup; inside them, you're back in plain JavaScript.

A few rules come with JSX. Most attributes use camelCase instead of the lowercase, hyphenated names HTML uses, so it's className, htmlFor, and tabIndex rather than class, for, and tabindex, while ARIA and data- attributes keep their hyphens, as in aria-hidden and data-testid. Every tag has to be closed, including ones that would be left open in HTML, so <img /> and <br /> need that closing slash. And a component can only return one root element. If you need to return two side-by-side elements without wrapping them in an extra <div>, wrap them in a fragment, written as empty angle brackets:

jsx
return (
  <>
    <h1>Title</h1>
    <p>Some text</p>
  </>
)

How JSX differs from HTML

JSX looks close enough to HTML that the differences are subtle enough to miss until they trip you up. The attribute names are the first one: class becomes className, because class is a reserved word in JavaScript. The style attribute is another: in HTML it's a string of CSS, but in JSX it takes a JavaScript object, with property names in camelCase and values as strings.

jsx
<div style={{ backgroundColor: 'lightblue', fontSize: '18px' }}>Styled</div>

The outer {} here is the same one that embeds any expression, and the inner {} is the object literal being embedded, which is why a style attribute in JSX ends up with two curly braces in a row.

The other difference is what's allowed inside {}. It only accepts expressions, things that produce a value, like a variable, a function call, or a ternary. It does not accept statements, so an if or a for loop cannot go directly inside curly braces in your markup. When you need that kind of logic, you run it above the return and embed the result instead.

Under the hood, JSX compiles down to plain JavaScript function calls before it ever reaches the browser. That's a tooling detail more than something you'll think about day to day, and the Beyond the basics chapter comes back to it once the rest of React is in place.

JSX is syntax sugar. The compiler turns each element into a call to React.createElement or, with the automatic JSX runtime this track's tooling uses (standard since React 17, and the default you'll get with any modern setup), an equivalent call generated by the build tool without you needing React in scope. That earlier example compiles to something close to React.createElement('p', { className: 'greeting' }, 'Hello, ', name).

That compilation step is exactly why {} only holds expressions. Each {} becomes an argument passed into that function call, and a function argument has to evaluate to a value. A statement like if or for doesn't produce a value, so it can't sit where an argument is expected. A ternary works inline because it's an expression; an if doesn't, because it's a statement. When the logic is more than a ternary can hold cleanly, compute the value in a variable above the return and embed that variable instead.

JunoJSX is markup with JavaScript mixed in The main thing to hold onto: {} is your doorway back into JavaScript from inside the markup. Anything between those braces is a value, a variable, a calculation, a function call, and it gets dropped right into the page. Everything else follows from there: className instead of class, closing every tag, and one root element per return.
JunoJSX is markup with JavaScript mixed in Treat {} as your expression slot: variables, function calls, ternaries, all fine, statements like if are not. Pair that with the naming differences, className, htmlFor, style as an object, and JSX stops feeling like a variant of HTML and starts feeling like what it is, JavaScript wearing markup syntax.
JunoJSX is markup with JavaScript mixed in Every {} compiles into a function argument, which is the entire reason expressions work there and statements don't. Keep that mental model and the rest of JSX's quirks, camelCase attributes, the style object, self-closing tags, stop looking like arbitrary rules and start looking like consequences of "this is React.createElement calls in disguise."

Next up: Styling components, where those elements get their look.