JSX Under the Hood
JSX compiles to createElement or jsx() calls: elements as objects, props and children, and why type plus key drive identity.
- react
- jsx-under
JSX is syntax sugar, not a browser feature. Build tools turn it into function calls that produce element objects — plain descriptions React later reconciles to the DOM (or native views).
const el = <button type="button" className="primary">Save</button>;
With the classic runtime this is roughly:
const el = React.createElement(
'button',
{ type: 'button', className: 'primary' },
'Save'
);
The modern automatic runtime (react/jsx-runtime) emits jsx / jsxs instead of requiring React in scope for every file — same idea: type, props, children.
Docs: Writing Markup with JSX, createElement.
Elements are blueprints
Conceptually an element looks like:
{
type: 'button',
props: { type: 'button', className: 'primary', children: 'Save' },
key: null,
ref: null,
}
type: string for host components (div), function/class for composites, or special types (fragments).props: inputs, includingchildren.key/ref: reserved by React; not ordinary props your component reads the same way.
Changing type on the next render tells React to tear down the old subtree and mount a new one — the core of reconciliation.
Expressions and children
Curly braces embed JS expressions:
function Price({ cents, currency }) {
const label = new Intl.NumberFormat(undefined, {
style: 'currency',
currency,
}).format(cents / 100);
return <span>{label}</span>;
}
Children can be text, elements, arrays, or null / false / undefined (which render nothing):
{isAdmin && <AdminPanel />}
{items.length ? <List items={items} /> : null}
Trap: 0 is a valid React child and will render. Prefer an explicit ternary when the left side of && can be 0.
Props quirks worth memorizing
| HTML / DOM | In JSX |
|---|---|
class |
className |
for |
htmlFor |
| inline styles | object with camelCase: style={{ marginTop: 8 }} |
| boolean attributes | disabled or disabled={true} |
| events | camelCase: onClick, onChange |
<input
className="field"
style={{ width: '100%' }}
onChange={(e) => setValue(e.target.value)}
/>
Self-closing tags are required when there are no children: <img src={url} alt="" />.
Components as types
function Hello({ to }) {
return <p>Hello {to}</p>;
}
const node = <Hello to="Ada" />;
// type is the Hello function
Dynamic tags need a capitalized variable:
const Tag = as === 'h1' ? 'h1' : 'h2';
return <Tag className="title">{children}</Tag>;
Interview out-loud
“JSX compiles to createElement or jsx calls that return element objects with type, props, and children. React reconciles those descriptions to the host tree. Keys and component type drive identity. className and htmlFor replace class and for because JSX is JavaScript.”
Related on this site
Further reading
Edge cases worth rehearsing
Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.
Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.
When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.
Related guides
- Accessibility Patterns in ReactPractical React a11y: labels, focus management, keyboard, live regions, and composition patterns that stay accessible.
- Avoid Prop Drilling with CompositionStop threading props through intermediates: children slots, inversion of control, and when context is the right escape hatch.
- Batching State UpdatesHow React 18+ batches setState in events, timeouts, and promises: when updates flush and why double setState still works.
- Children Prop PatternsUsing children and slot props for flexible APIs: wrappers, compound components, and when to prefer explicit props.
- Client Component BoundariesWhere to put use client: push interactivity to leaves, serializable props, and children as server slots.