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 ofReact.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
// 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.createElementdirectly - 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
// 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
// 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
// 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
// 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/orapp/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
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
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 readyA concise answer to help you respond confidently on this topic during an interview.