Contents
3 Chapter

The essence of JSX

What JSX is and how it turns into JavaScript function calls, and the rules around expressions, attributes, and children — all in one place.

In Chapter 2, while building your first project with Vite, you naturally saw code like this:

src/App.jsx
function App() {
  return (
    <div>
      <h1>Hello, React!</h1>
    </div>
  );
}

Inside a function, we just return something that looks like HTML. HTML inside JavaScript — feels a bit odd, doesn’t it? In this chapter we look at exactly what this syntax — JSX — is, how it works, and the basic rules to internalize.

What is JSX? #

JSX (JavaScript XML) is an extension that adds markup similar to HTML into JavaScript. It is not part of official JavaScript syntax, but Facebook proposed it alongside the release of React, and today almost every React project uses JSX.

You can write React code without JSX. The following two snippets produce exactly the same result.

with JSX
function App() {
  return <h1>Hello</h1>;
}
without JSX
function App() {
  return React.createElement('h1', null, 'Hello');
}

The first one is much easier to read. That is why every React project uses JSX.

The browser does not understand JSX #

The JavaScript engine in the browser does not understand JSX directly. Code we write in JSX gets transformed into plain JavaScript at build time before reaching the browser. Vite, which we set up in Chapter 2, handles this transformation automatically, so we can write JSX freely without worrying about it.

The big picture of the transformation:

JSX → JS transformation
src/App.jsx (JSX)
   ↓ Vite (esbuild / SWC)
JS bundle (React.createElement calls)
   ↓ browser
DOM element

Both key from Chapter 8 and reconciliation from Chapter 14 operate on the object tree built out of these React.createElement calls. In this chapter we focus on the syntax rules at the layer above.

The basic rules of JSX #

JSX looks similar to HTML but has a few different rules. These are where beginners stumble most often, so let us walk through them carefully.

Rule 1. Wrap everything in a single parent #

JSX is ultimately something a function returns, so it can only return one value at a time. To return multiple elements at once, you must wrap them in a single parent.

bad
function App() {
  return (
    <h1>Hello</h1>
    <p>Have a great day</p>
  );
}
good
function App() {
  return (
    <div>
      <h1>Hello</h1>
      <p>Have a great day</p>
    </div>
  );
}

Wrapping in a <div> works, but there are times when an empty <div> for no reason feels wrong. For those cases, use an empty tag — a Fragment.

using a Fragment
function App() {
  return (
    <>
      <h1>Hello</h1>
      <p>Have a great day</p>
    </>
  );
}

<>...</> is the short form of React.Fragment. It groups multiple children without producing any element in the real DOM.

Rule 2. Every tag must be closed #

In HTML you could leave tags like <img>, <br>, and <input> unclosed. In JSX, every tag must be closed. Tags with no children get a / at the end and become self-closing.

JSX
<img src="/cat.png" alt="cat" />
<br />
<input type="text" />

Rule 3. className instead of class #

The class attribute used in HTML to assign CSS classes clashes with a JavaScript reserved word, so in JSX we use className.

JSX
<div className="card">This is a card</div>

For the same reason, the for attribute on <label> becomes htmlFor.

JSX
<label htmlFor="email">Email</label>
<input id="email" type="email" />

Rule 4. Attribute names are camelCase #

In HTML, attribute names were all lowercase (onclick, tabindex, readonly), but in JSX we use camelCase.

JSX
<button onClick={handleClick} tabIndex={0}>Click</button>
<input readOnly value="fixed value" />

This rule shows up in earnest again in Chapter 7 (Event handling). onClick, onChange, onSubmit — all camelCase.

Embedding JavaScript expressions inside JSX #

The real power of JSX is that you can freely embed JavaScript expressions inside the markup. Every JavaScript expression inside curly braces { } is evaluated, and the result is rendered on screen.

src/App.jsx
function App() {
  const name = 'Cheolsu';
  const age = 30;

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <p>Age: {age}</p>
      <p>In 10 years: {age + 10}</p>
    </div>
  );
}

Function calls, ternary expressions, and array methods all work too as long as they are expressions.

src/App.jsx
function App() {
  const isLoggedIn = true;
  const items = ['apple', 'banana', 'cherry'];

  return (
    <div>
      <h1>{isLoggedIn ? 'Welcome!' : 'Please log in'}</h1>
      <ul>
        {items.map(item => <li key={item}>{item}</li>)}
      </ul>
    </div>
  );
}
Note
Only expressions are allowed inside curly braces. Statements like if or for loops cannot go in directly. For conditional output, use the ternary operator or the && operator; for repeated output, use map. We cover these in Chapter 7 (Conditional rendering) and Chapter 8 (Lists and key) in detail.

Inline styles #

To assign a style directly in JSX, pass an object. CSS property names are camelCase as well.

src/App.jsx
function App() {
  return (
    <h1 style={{ color: 'tomato', fontSize: '32px' }}>
      A styled heading
    </h1>
  );
}

The double curly braces look strange at first, but the outer { } means “a JavaScript expression inside JSX,” and the inner { } means “a JavaScript object literal.”

In real-world work, instead of inline styles, tools like CSS Modules / Tailwind / styled-components show up more often. This book uses inline styles in examples for readability, but design-system-level styling belongs to a separate book.

Comments inside JSX #

To leave a comment inside JSX, you need to wrap it in curly braces, since it is a JavaScript expression.

src/App.jsx
function App() {
  return (
    <div>
      {/* This is a JSX comment */}
      <h1>Title</h1>
    </div>
  );
}

Try it yourself #

Change src/App.jsx from the project you made in Chapter 2 to the following.

src/App.jsx
function App() {
  const name = 'Cheolsu';
  const fruits = ['apple', 'banana', 'cherry'];

  return (
    <>
      <h1>Hello, {name}!</h1>
      <p>Today we have {fruits.length} fruits in total:</p>
      <ul>
        {fruits.map(fruit => <li key={fruit}>{fruit}</li>)}
      </ul>
    </>
  );
}

export default App;

On save, HMR updates the screen immediately. Try changing the variables or adding new fruits to the array, and see how the screen reacts.

Tip
If you hit a build error while writing JSX, nine times out of ten you forgot one of the four rules above. Run through “Did I wrap multiple elements in a parent?”, “Did I close every tag?”, “Did I write className instead of class?”, and “Did I use camelCase for attributes?” in that order — that resolves most cases.

Exercises #

  1. Add three more fruits you like to the fruits array in the “Try it yourself” code, and confirm that all six show up the moment you save. Then check that the total N items part in the <p> also updates to 6 automatically.
  2. Change the <h1> style to use an inline style — color tomato, font size 48px. Pass it as an object, like style={{ color: 'tomato', fontSize: '48px' }}.
  3. Convert the fruits array into an array of objects. Each item should be an object of shape { id, name, emoji }, and inside <li> display them like {fruit.emoji} {fruit.name}. Use fruit.id as key. Handling object arrays is the foundation of Chapter 8 (Lists and key).

In one line: JSX is a syntax that compiles down to JavaScript function calls (React.createElement). Wrap in a parent / close every tag / className · htmlFor / camelCase attributes — internalize those four rules. With curly braces { } you can freely embed JavaScript expressions inside JSX.

Next chapter #

In the next chapter, Chapter 4: Components and props, we point out that functions like App from the example above are in fact React’s most important unit — components — and then look at props, the channel components use to pass data between each other. This becomes the foundation for solidifying these interfaces with TypeScript in Chapter 17 (Typing props and children).

X