In one minute
Before React, a page changes through the DOM API: create a node, set its properties, append it somewhere. A node you create lives in memory until you append it to something that is already on the page. React is a much more convenient way to make the same calls, so it helps to see them once by hand.
You’ll be able to: explain what “on the page” means for a DOM node, why textContent is safe and innerHTML isn’t, and what work React takes off your hands.
1const card = document.createElement('article')2const name = document.createElement('h2')3name.textContent = 'Ceramic Mug'4const price = document.createElement('p')5price.textContent = '$18.00'6card.append(name, price)78rootElement.append(card)const card = document.createElement('article')A real DOM node is created, but only in memory. Nothing on the page changes.const name = document.createElement('h2')A heading gets its text. Still in memory.const price = document.createElement('p')Same for the price.card.append(name, price)The pieces are put together into one card, still detached from the page.rootElement.append(card)Only now does the card appear: appending to a node that’s already on the page attaches it.
The example: a product card by hand
The product store’s first lesson builds one card with nothing but DOM calls. Even the #root element is created by the code, to show it’s an ordinary node:
const rootElement = document.createElement('div')
rootElement.id = 'root'
document.body.append(rootElement)
const card = document.createElement('article')
card.className = 'product-card'
const name = document.createElement('h2')
name.textContent = product.name
const price = document.createElement('p')
price.className = 'price'
price.textContent = formatUSD(product.priceCents)
card.append(name, price)
At this point the card exists, but only in memory. One more line puts it on the page:
rootElement.append(card)
What the lesson logged before and after that line:
card built. On the page? false
after append. On the page? true
html: <article class="product-card"><h2>Ceramic Mug</h2><p class="price">$18.00</p></article>
How it works
document.createElementdoesn’t show anything. It returns a detached node. SettingclassNameortextContentchanges that detached node.appendis what attaches it. Appending to a node that is already in the document (here#root, which is inside<body>) makes the whole subtree visible.- A “root” isn’t special.
#rootis adivlike any other. React later does exactly this kind of work, just consistently and for you.
textContent vs innerHTML
Product names come from users and APIs, so assume they can contain anything. The lesson renders a name with HTML in it both ways:
const safe = document.createElement('h2')
safe.textContent = evil // shown as text
const unsafe = document.createElement('h2')
unsafe.innerHTML = evil // parsed as HTML: the <img> is real
textContent → child elements: 0 · visible text: Mug <img src="x" onerror="window.__ran = true">
innerHTML → child elements: 1 · first child: IMG
onerror handler ran? true
textContent treats the string as text: no elements, the tags show up literally. innerHTML parses it, so the <img> became a real element and its onerror handler ran. That is how cross-site scripting (XSS) works.
React makes the safe choice the default: every string you render is treated as text. The only way to insert raw HTML is the deliberately alarming dangerouslySetInnerHTML prop.
Common mistakes
- Creating a node and wondering why it’s not visible. Nothing appears until it’s appended to something already on the page.
- Using
innerHTMLfor data. UsetextContent(or React, which does the same for you). Reach forinnerHTML/dangerouslySetInnerHTMLonly with HTML you trust or have sanitized.
Interview Q&A
Why doesn't document.createElement alone put anything on the page?
It only builds a node in memory. A node becomes visible when it’s appended (or inserted) into a node that is already in the document. In the product store, the card logged On the page? false after it was fully built and true only after rootElement.append(card). This create → change → append sequence is the manual work React’s declarative API hides.
Why use textContent instead of innerHTML for text?
textContent never parses its input as HTML, so a value like Mug <img src="x" onerror="…"> stays visible text. innerHTML parses it into real elements, and the recording shows the <img>’s onerror handler actually ran. That’s an XSS hole. React escapes strings by default for the same reason, and makes raw HTML opt-in through dangerouslySetInnerHTML.
Is there anything special about the "root" DOM node React renders into?
No. It’s an ordinary element, created and appended like any other; the lesson even creates it in code. A library like React has no privileged access to the page: it makes the same DOM calls you could write by hand, just many of them, consistently.
Related
- Raw React APIs: the same card with React, no JSX.
- Render and Commit (React Internals): when React makes these DOM calls.
Sources
- MDN:
Document.createElement,Node.textContent,Element.innerHTMLsecurity - Topic order inspired by Kent C. Dodds’ EpicReact React Fundamentals workshop; the example app and code here are this handbook’s own.
Primary source: developer.mozilla.org/en-US/docs/Web/API/Document/createElement