Jsx In React
JSX felt wrong to me until one reframe changed everything -- writing React.createElement is already mimicking HTML, so why not just write the HTML?
My first reaction to JSX was the same one most developers have: this looks wrong. You are putting HTML inside a JavaScript function. Years spent cleaning up JavaScript that had been tangled inside HTML tags made the inverse feel just as bad.
The reframe that finally changed my mind had nothing to do with productivity arguments. It was simpler than that.
The Reframe
Look at the React.createElement call we wrote in the previous posts:
React.createElement("h1", { id: "title" }, "Padre Gino's")What is that code trying to produce? An h1 element with an id of title and the text "Padre Gino's". It is mimicking HTML markup -- badly, because it is using a function call instead of the syntax designed for that job.
JSX says: instead of mimicking markup, just write the markup.
<h1 id="title">Padre Gino's</h1>That is the entire case for JSX. It is not magic. It is a build-time transformation that converts the markup syntax back into the React.createElement calls that React actually understands. The compiler does the translation. You get to write what you mean.
What JSX Actually Is
JSX is a syntax extension -- a thin layer on top of JavaScript that the build tool transforms before the browser ever sees it. Vite (via Babel under the hood) turns every JSX element into a React.createElement call at compile time.
ExpandJSX compiles to React.createElement at build time -- the browser only ever sees plain JavaScript
The output is identical to what you would write by hand. The only difference is readability. When a component renders deeply nested structure, React.createElement calls nest inside each other and become genuinely difficult to parse. JSX keeps the structure visible.
The Pizza Component in JSX
Here is the Pizza component from the previous post rewritten in JSX:
const Pizza = (props) => {
return (
<div className="pizza">
<h2>{props.name}</h2>
<p>{props.description}</p>
</div>
);
};
export default Pizza;A few things to notice.
className instead of class. The word class is a reserved keyword in JavaScript -- it is used for class definitions. To avoid a collision, JSX uses className, which also happens to match the DOM property name (element.className). This was not invented for React; it is the actual JavaScript API name.
Curly braces for expressions. Anything inside {} is a JavaScript expression -- anything that can appear on the right side of an assignment. {props.name} evaluates and renders the value. {props.name.toUpperCase()} also works. {"Pepperoni"} is a string literal. If you can write it on the right of =, you can put it inside {}.
The parentheses after return. JSX returned across multiple lines needs parentheses so JavaScript's automatic semicolon insertion does not break it. A single-line return does not need them, but they are harmless and worth making a habit.
Splitting Into Its Own File
As the component grows, it belongs in its own file. Create src/Pizza.jsx and move the component there. The .jsx extension tells Vite to apply the JSX transform. Without it, Vite will not process the file and the build will fail.
In App.jsx, import it:
import Pizza from "./Pizza";No curly braces because Pizza is the default export. Default exports are imported by name without braces. Named exports require the braces:
// named export in Pizza.jsx
export const Pizza = (props) => { ... };
// named import in App.jsx
import { Pizza } from "./Pizza";For components, the convention is one file per component with a default export. Named exports make sense when a file exports multiple related things -- utilities, constants, or a component plus a helper hook.
The React Import
Earlier versions of React required this at the top of every JSX file:
import React from "react";JSX compiles to React.createElement, so React had to be in scope even if you never referenced it directly. This confused everyone who was new to the framework.
Modern build tools are smarter. Vite auto-injects the React import when it sees JSX, so you no longer need to write it. If you are reading an older codebase or tutorial and see that import at the top of every file, this is why -- the tooling has since caught up.
In the next post the project connects to a real API server and starts serving the pizza data that will drive the menu.
The Essentials
- The reframe.
React.createElementis already mimicking HTML. JSX lets you write the HTML directly instead of mimicking it in JavaScript. The compiler handles the translation. - JSX is a compile-time transformation. Vite converts
<div>intoReact.createElement("div", ...)before the browser sees it. The runtime is identical -- the developer experience is not. classNamenotclass.classis a reserved JavaScript keyword.classNamematches both the JSX requirement and the underlying DOM API (element.className).- Curly braces accept any expression. Anything valid on the right side of
=is valid inside{}. Functions calls, ternaries, template literals -- all fine. .jsxextension for Vite. Vite only applies the JSX transform to files ending in.jsxor.tsx. Plain.jsfiles are not processed.- No manual React import needed. Modern build tools auto-inject the React import. The explicit import is only necessary in older setups.
Further Reading and Watching
- Writing Markup with JSX (React docs) -- the official JSX rules reference: single root element, className, self-closing tags, and curly brace expressions
- What is JSX? (Fireship) -- a concise explanation of JSX syntax, the compilation step, and why it exists
Keep reading