What is JSX and how does React render elements from it?
JSX is syntactic sugar over React.createElement(type, props, ...children). The compiler (Babel/SWC/esbuild) transforms <div className='x'>hi</div> into createElement('div', { className: 'x' }, 'hi'), which returns a plain object describing the element. React's reconciler walks this tree, diffs against the previous tree, and applies minimal DOM mutations. JSX is not HTML and not a template engine — it's JavaScript expressions.
JSX feels like HTML but is fully JavaScript. Understanding the transform is the key.
JSX → createElement
const el = <button className="primary" onClick={save}>Save</button>;
// compiles to (classic transform):
const el = React.createElement(
'button',
{ className: 'primary', onClick: save },
'Save',
);
// React 17+ automatic transform:
import { jsx as _jsx } from 'react/jsx-runtime';
const el = _jsx('button', { className: 'primary', onClick: save, children: 'Save' });The result is a plain object — a React Element.
What an element looks like
{
type: 'button', // string for HTML, function/class for components
props: { className: 'primary', onClick: save, children: 'Save' },
key: null,
ref: null,
}For components:
<MyButton label="Save" />
// →
{ type: MyButton, props: { label: 'Save' }, key: null, ref: null }How React renders
- Element tree — JSX expressions evaluate to an element tree.
- Component call — for component-typed elements, React calls the function with props.
- Sub-tree — the function returns more elements.
- Reconcile — React diffs new tree vs previous tree.
- Commit — apply DOM changes.
Why JSX over template strings
- Expressions everywhere:
{user.name},{items.map(...)}, conditional rendering. - Type checking: TypeScript knows the prop shapes.
- Refactor-safe: no string-based template grammar to mis-parse.
- Composition: components are just function names — no special template imports.
JSX rules / gotchas
classNamenotclass(classis a JS keyword).htmlFornotfor.- camelCase events:
onClick,onChange. - Self-closing required:
<br />,<img />. - Adjacent elements need a Fragment or wrapper.
- Boolean/null/undefined render nothing; numbers and strings render as text.
{0}renders0—array.length && ...is a footgun (renders0if empty).
The element is immutable
Elements are data, not instances. You can't mutate props after creation. You re-render to update.
Conditional rendering patterns
{isOpen && <Modal />} // shorthand — careful with 0
{isOpen ? <Modal /> : null} // explicit
{isOpen ? <Modal /> : <Placeholder />} // either-orLists
{items.map(item => <Row key={item.id} item={item} />)}key is a React directive, not a DOM attribute.
JSX without React
JSX compiles to whatever jsx-runtime you configure. Preact uses its own, Vue's reactivity has experimental JSX support. The syntax is library-agnostic; the runtime decides what to do with the resulting objects.
Senior framing
JSX is JavaScript that returns plain objects describing UI. React's value is the reconciler that turns those objects into DOM efficiently. The 'just JS' nature is what lets you compose components like values — pass them as props, return them from functions, store them in arrays.
Follow-up questions
- •What's the difference between createElement and cloneElement?
- •Why does React require a single root element from a return?
- •How does the JSX automatic runtime differ from the classic transform?
Common mistakes
- •Writing class instead of className.
- •Using && with a number (0 renders as '0').
- •Mutating props on an element — they're immutable.
Performance considerations
- •createElement is cheap (object allocation). The cost is in the component function call + reconciliation. JSX itself has no runtime overhead beyond what createElement does.
Edge cases
- •Whitespace in JSX is tricky — multiple spaces collapse, newlines drop.
- •Comments inside JSX must be {/* like this */}.
- •Returning arrays of elements requires keys but is otherwise valid.
Real-world examples
- •Every React component uses JSX. Some teams prefer hyperscript (h(...)) for non-JSX environments. React Native uses JSX with different element types (View, Text). Solid, Preact, and Inferno all support JSX with their own runtimes.