How does JSX get transformed, and why doesn’t React require it?

LowIntermediateReact
Preparing for interviews?

Use guided tracks for structured prep, then practice company-specific question sets when you want targeted interview coverage.

Quick Answer

Explain what JSX actually is (syntax sugar), how a compiler (Babel/TypeScript) transforms it into plain JavaScript calls, and why React can work without JSX (you can call the element factory directly). Mention the two JSX runtimes (classic createElement vs automatic react/jsx-runtime) and the practical implications (React import, tooling, and why JSX isn’t HTML). Modern JSX transform removes the explicit React import, but tooling must be configured. Test build output and lint rules.

Answer

Core idea

JSX is not something React executes. JSX is syntax that your build tool (Babel or TypeScript) compiles into plain JavaScript function calls that create React elements. React doesn’t require JSX because React only needs the result: a React element tree (plain JS objects) — and you can create those objects without JSX.

Step

What happens

Who does it

1) Parse JSX

JSX is parsed into an AST (like any JS syntax).

Babel / TypeScript compiler

2) Transform

JSX nodes become function calls (classic or automatic runtime).

Babel plugin / TS JSX transform

3) Run

Those function calls return React elements (JS objects).

React runtime

4) Render

React reconciles elements and updates the DOM.

React + ReactDOM

JSX is a compile-time feature, not a React runtime feature

Two JSX runtimes you’ll see

There are two common outputs for JSX transforms: classic (calls React.createElement) and automatic (calls jsx/jsxs from react/jsx-runtime). Both produce the same kind of React elements.

JSX
// Input (JSX)
function App({ user }) {
  return (
    <main className="page">
      <h1>Hello, {user.name}</h1>
      <button onClick={() => alert('hi')}>Click</button>
    </main>
  );
}
                  
JS
// Output (classic runtime - conceptual)
function App({ user }) {
  return React.createElement(
    'main',
    { className: 'page' },
    React.createElement('h1', null, 'Hello, ', user.name),
    React.createElement('button', { onClick: () => alert('hi') }, 'Click')
  );
}
                  
JS
// Output (automatic runtime - conceptual)
import { jsx, jsxs } from 'react/jsx-runtime';

function App({ user }) {
  return jsxs('main', {
    className: 'page',
    children: [
      jsxs('h1', { children: ['Hello, ', user.name] }),
      jsx('button', { onClick: () => alert('hi'), children: 'Click' })
    ]
  });
}
                  

Detail

What it means in practice

Automatic runtime often removes the need to import React just for JSX

You might not see import React from 'react' anymore, but the runtime still imports helpers (from react/jsx-runtime).

JSX becomes props + children

Attributes become a props object; nested content becomes children.

JSX is JavaScript expressions

{...} is an expression slot, not “template interpolation” like HTML.

Lowercase vs Uppercase matters

<div /> becomes a string tag; <MyComp /> becomes a variable reference (a component function/class).

Important mental model differences

Why React doesn’t require JSX

React only needs you to produce React elements. JSX is just a nicer way to write those element factory calls. You can skip JSX entirely and write createElement (or jsx) calls yourself — it’s just more verbose.

JS
// React without JSX
import React from 'react';

export function App() {
  return React.createElement('h1', null, 'No JSX here');
}
                  

Interview framing

Say: “JSX is syntax sugar compiled by Babel/TS into element-creation calls (classic createElement or automatic jsx/jsxs). React doesn’t require JSX because it only consumes React elements — JSX never reaches React as JSX.”

Practical scenario
You upgrade to React 17+ and remove import React from files using JSX.

Common pitfalls

      • Tooling not configured for the new JSX transform.
      • Babel/TS settings inconsistent across packages.
      • Lint rules still expecting React in scope.
Trade-off or test tip
New transform reduces boilerplate but needs config. Test build output and update linters.

Similar questions
Guides
30 / 41