From document.createElement to JSX
The same product card built with DOM calls and with JSX, side by side, and the few rules JSX adds.
In plain JavaScript you build a page piece by piece: document.createElement('h3') makes a heading, textContent puts words in it, append hangs it inside another element. When the data changes, you find the elements again and fix them, or throw them away and build new ones.
JSX lets you write the same tree the way it looks in HTML, directly inside JavaScript. You describe what the card should be for the current data, and React works out what to change on the page.
The same card, built twice
Both cards below read the same three controls. The left one is drawn with DOM calls, the right one with JSX. Type in the name, then tick Sold out, and watch how many elements each approach creates for one change.
Leather armchair
228 SAR
The DOM version builds a whole new card for every keystroke. React changes only the text that differs, and creates one element when the badge appears. That matters more than speed: an element that is rebuilt loses its focus, its text selection and its scroll position.
function renderCard(container, product) {
const card = document.createElement('article');
const title = document.createElement('h3');
title.textContent = product.name;
const price = document.createElement('p');
price.textContent = product.price + ' SAR';
card.append(title, price);
if (product.soldOut) {
const badge = document.createElement('span');
badge.textContent = 'Sold out';
card.append(badge);
}
container.replaceChildren(card); // throw the old card away, put the new one in
}
// And you must remember to draw again after every change:
nameInput.addEventListener('input', () => {
product.name = nameInput.value;
renderCard(container, product);
});
export function ProductCard({ name, price, soldOut }) {
return (
<article>
<h3>{name}</h3>
<p>{price} SAR</p>
{soldOut && <span>Sold out</span>}
</article>
);
}
What JSX really is
JSX is not HTML, and browsers cannot run it. The build turns every tag into a function call that returns a plain object describing the element. This is roughly what the card above becomes:
import { jsx as _jsx, jsxs as _jsxs } from 'react/jsx-runtime';
export function ProductCard({ name, price, soldOut }) {
return _jsxs('article', {
children: [
_jsx('h3', { children: name }),
_jsxs('p', { children: [price, ' SAR'] }),
soldOut && _jsx('span', { children: 'Sold out' }),
],
});
}
So {name} is just a JavaScript value placed in the children of the h3, and {soldOut && …} is an ordinary && expression: when soldOut is false, React draws nothing there.
The rules you meet on day one
- Curly braces hold any expression:
{price * 2},{items.length},{isRTL ? 'rtl' : 'ltr'}. Statements such asifandfordo not fit inside; use&&,? :or.map(). - Attributes use JavaScript names:
classNameinstead ofclass,htmlForinstead offor,onClickinstead ofonclick. - `style` takes an object:
style={{ marginInlineStart: 8, fontWeight: 700 }}, with camelCase property names; a plain number on a size such asmarginInlineStartmeans pixels. - Every tag is closed:
<img />,<br />,<input />. - One outer element: return one element, or wrap siblings in a fragment,
<>…</>. - Lists need a `key`:
products.map((p) => <li key={p.id}>{p.name}</li>), so React can tell the items apart when the list changes.
In engine terms
- A theme's
tsconfig.jsonsets"jsx": "react-jsx", the automatic runtime shown above: noimport Reactis needed in.tsxfiles. - React 19 compares the new description with the previous one after each render and applies only the difference to the DOM. That comparison is what the right-hand counter observes, through a
MutationObserver. - Engine components are written the same way:
ProductCard(src/components/product/ProductCard.tsx) is a function returning JSX with conditional badges and prices.