Skip to main content

What is JSX in React?

JSX is a syntax extension for JavaScript that lets you write HTML-like markup directly in JS files, which Babel then converts into React.createElement calls.

Theory

TL;DR

  • JSX is HTML-shaped syntax inside JavaScript: familiar tags, but expressions go in {}
  • Main win: <div>Hello</div> instead of React.createElement('div', null, 'Hello')
  • Babel converts JSX at build time, so there is no runtime overhead
  • Use JSX in every React component; skip it only when you have no bundler at all
  • JSX is not HTML: attributes use camelCase (className, onClick, htmlFor)

Quick example

jsx
// Without JSX (verbose) const elem = React.createElement('h1', null, 'Hello, ', name, '!'); // With JSX (readable) const elem = <h1>Hello, {name}!</h1>; // Babel turns the JSX line into the createElement call above. // Identical output, less code.

Curly braces {} mark the switch from markup to JavaScript. Everything inside is a JS expression: variables, ternaries, function calls. Plain text outside the braces stays as literal text.

Key difference from plain HTML

JSX looks like HTML but differs in three ways. Attributes are camelCase (className not class, htmlFor not for). Every tag must close, including void elements (<img />). And you can only return one root element. That third rule is why <>...</> (React.Fragment) exists - it wraps sibling elements without adding a real node to the DOM.

When to use

  • Any React component → always JSX, no exceptions
  • Rendering dynamic lists → {todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
  • Conditional output → {isLoading ? <Spinner /> : <Content />}
  • No bundler (CDN React via script tag) → fall back to React.createElement directly
  • Server-side rendering → JSX works fine in Node via ReactDOMServer.renderToString

How Babel handles it

Babel's @babel/preset-react plugin parses JSX at build time. Uppercase tag names like <TodoList /> become variable references - your component function. Lowercase names like <ul> become string literals. Babel also auto-escapes content, so JSX expressions never inject raw HTML. That prevents XSS by default. The only way around it is dangerouslySetInnerHTML, which is intentionally awkward to type.

Common mistakes

Using if statements inside JSX

jsx
// Wrong - if is a statement, not an expression return <div>{if (isLoggedIn) <p>Hi</p>}</div>; // parse error // Fix - ternary works because it is an expression return <div>{isLoggedIn ? <p>Hi</p> : null}</div>;

Rendering an array of objects without mapping

jsx
// Wrong - shows "[object Object],[object Object]" <ul>{[{name: 'Alice'}, {name: 'Bob'}]}</ul> // Fix <ul>{[{name: 'Alice'}, {name: 'Bob'}].map(item => <li>{item.name}</li>)}</ul>

Multiple root elements without a Fragment

jsx
// Wrong - two siblings at the top level return ( <h1>Title</h1> <p>Text</p> ); // syntax error // Fix return ( <> <h1>Title</h1> <p>Text</p> </> );

class instead of className

jsx
// Wrong - class is a reserved keyword in JS <div class="container">Content</div> // Fix <div className="container">Content</div>

Real-world usage

  • Next.js: every file in pages/ or app/ exports a JSX component
  • React Native: same JSX syntax, different tags (<View>, <Text> instead of <div>, <p>)
  • Remix, Gatsby: server-rendered JSX with the same mental model
  • Preact: JSX-compatible drop-in with a 3KB runtime

Follow-up questions

Q: What does Babel do to JSX?
A: It converts every JSX tag to a React.createElement call. <h1>Hi</h1> becomes React.createElement("h1", null, "Hi"). The result is a plain JavaScript object describing the element type and its props.

Q: Why use curly braces for expressions?
A: They signal "JavaScript mode here." {2 + 2} renders 4. Without them, 2 + 2 stays as the literal string "2 + 2".

Q: Can JSX return multiple root elements?
A: No. Wrap them in <>...</> (React.Fragment). Fragment elements never appear in the real DOM.

Q: What is the difference between JSX and HTML attributes?
A: JSX uses className instead of class, htmlFor instead of for, and camelCase event handlers (onClick not onclick). Attribute names in JSX are case-sensitive; in HTML they are not.

Q: (Senior) How does JSX handle spread props, and what happens when keys conflict?
A: <MyComp {...props} color="blue" /> compiles to React.createElement(MyComp, Object.assign({}, props, { color: "blue" })). Later props override earlier ones. If props.color is "red", the final value is "blue". The merge is shallow.

Examples

Basic: from JSX to the DOM

jsx
function Greeting({ name }) { return <h1 className="title">Hello, {name}!</h1>; } // Babel compiles the return to: // React.createElement('h1', { className: 'title' }, 'Hello, ', name, '!') // Usage: <Greeting name="Alice" /> // Renders: <h1 class="title">Hello, Alice!</h1>

You write JSX, Babel does the conversion, React builds the DOM node. Nothing else is happening.

Intermediate: dynamic list with conditional styling

jsx
function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <li key={todo.id} className={todo.completed ? 'done' : ''}> {todo.text} </li> ))} </ul> ); } // Usage: // <TodoList todos={[{ id: 1, text: 'Buy milk', completed: true }]} /> // Output: <ul><li class="done">Buy milk</li></ul>

The key prop is required when rendering lists. React uses it to track which items changed between renders. Drop it and you get a warning in the console on every update.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?