What makes React render, why a child renders when its parent does, and how React compares two renders to keep or replace parts of the page.
So far we’ve used the word “render” a lot. In Part 2 you saw that rendering means React calls your components. In Part 4 a state change made a component render again. In Part 11 you met the commit, when React changes the page.
This part puts those pieces together. It is the first part of Stage 3, on how React renders. We’ll answer two questions. When does a component render? And after it renders, how does React decide what to keep and what to replace?
Parts 3, 4, 7 and 8 each promised this part. They showed that state is sometimes kept and sometimes thrown away. Here are the full rules.
Try this first
Read this code. Don’t press Run yet.
import { useState } from 'react'
function Title() {
console.log('Title renders')
return <h1>My shop</h1>
}
function Label({ count }: { count: number }) {
console.log('Label renders')
return <p>In the basket: {count}</p>
}
function Footer() {
console.log('Footer renders')
return <p>Thank you!</p>
}
function Basket() {
const [count, setCount] = useState(0)
console.log('Basket renders')
return (
<div>
<button onClick={() => setCount(count + 1)}>Add one</button>
<Label count={count} />
<Footer />
</div>
)
}
export default function App() {
console.log('App renders')
return (
<main>
<Title />
<Basket />
</main>
)
}
App renders
App renders
Title renders
Title renders
Basket renders
Basket renders
Label renders
Label renders
Footer renders
Footer renders
Basket renders
Basket renders
Label renders
Label renders
Footer renders
Footer renders
The state lives in Basket. Label gets the count as a prop. Footer gets no props at all.
Make a guess. You click “Add one” once. Which components will log again?
Press Run, click once, and read the Console.
The first ten lines are the first render. Every line is there twice because of Strict Mode, as in Part 2. The last six lines are the click. Basket, Label and Footer rendered again. App and Title did not.
Footer is the surprise. Nothing in its props changed. It has none. It rendered anyway, because its parent rendered.
Three steps: trigger, render, commit
React’s docs split every screen update into three steps:
- Trigger. Something tells React that the page may need to change.
- Render. React calls your components to find out what the page should show. In the docs’ words, rendering “is React calling your components”.
- Commit. React changes the page to match. Part 11 used this word.
After the commit, the browser draws the screen. React’s docs call that paint.
Keep render and commit apart in your head. Most of this part is about the gap between them.
What triggers a render
Here are the common triggers.
- The first render. When the app starts, React renders everything once. This is the mount, from Part 7.
- A state change. A set function, or
dispatchfrom Part 15, asks React to render that component again. The components inside it render too, as we’ll see. - A context change. Context lets a component read a value from far above it, with no props in between. When that value changes, the components that read it render again. Part 23 covers context.
- An outside store changes. Parts 12, 16 and 17 named
useSyncExternalStore. It reads a value kept outside React. When the store reports a change and the value is different, React renders the components that read it.
React’s “Render and Commit” page names the first two. It says a component renders after a state update in that component, or in any component above it. The useReducer, useContext and useSyncExternalStore pages describe the others.
We checked the last three. With Strict Mode off, one dispatch gave one render. A button changed only an outside store, with no React state at all. The component that read the store rendered. Its parent didn’t render.
We checked context more closely. A child was passed in as children, so Part 2’s rule let React skip it. But it read a context value. When that value changed, the child rendered anyway. Another child that didn’t read the context did not render.
A props change is not on the list
Many people believe a component renders because its props changed. Look at the list again. Props are not on it.
A child gets new props only when its parent renders. So the parent’s render is the real cause. In “Try this first”, Label got a new count, and Footer got nothing new. Both rendered, for the same reason: Basket rendered.
Part 3 noticed one more detail. Each time a parent renders, it makes a new props object for every child it writes in its JSX. That is true even when the values inside are the same. We checked: a child that rendered 3 times got 3 different props objects, all with the same value. So the props object can’t tell React to skip the child. Part 2’s children trick works because there the whole element is the same object.
You can see this from the other side, too. Change a props object without a render, and nothing happens:
const settings = { size: 1 }
function Size({ options }: { options: { size: number } }) {
console.log('Size renders with', options.size)
return <p>Size: {options.size}</p>
}
export default function App() {
console.log('App renders')
return (
<div>
<button onClick={() => { settings.size = settings.size + 1 }}>Bigger</button>
<Size options={settings} />
</div>
)
}
App renders
App renders
Size renders with 1
Size renders with 1
Run it and click “Bigger” three times. settings.size is 4 now. But the page still says “Size: 1”, and the Console shows only the first render. No state changed, so nothing told React to render.
Two cases where React skips a child
The rule “a child renders when its parent renders” has exits. You met two of them already.
- The same element object. In Part 2, a child passed as
childrenwas the same object on every render. React skipped it: 0 renders while the parent rendered 3 times. - The same state value. In Part 4,
setCount(count)with the value it already had skipped the render. Sometimes React called the component once more, but never its children.
A third exit, React.memo, comes in Part 20. Without one of these, a parent’s render means its children render too.
Rendering goes down the tree
React’s docs call this process “recursive”. That means it repeats on each level below. React calls Basket. Basket returns a Label and a Footer, so React calls those too. If they returned more components, React would call those next. It stops when only HTML tags are left.
It never goes up. App is above Basket, so it didn’t run. Title is beside Basket, so it didn’t run either.
Step through it:
A state change renders that component and everything below it. Then React commits only the difference. Press play, or step through it.
- The first render is done. The page shows “In the basket: 0”.
- You click “Add one”.
setCountasks React to renderBasketagain. - React calls
Basket. It does not callApporTitle. BasketreturnsLabelandFooter, so React calls them too.- React compares the new result with the last one. Only one piece of text is different.
- React commits. It changes that one piece of text on the page, from 0 to 1.
Step 6 is the next idea.
Rendering is not changing the page
In “Try this first”, three components rendered on the click. How much of the page changed? We watched it: one change. The text in Label‘s <p> went from “0” to “1”.
Here is a new app, so you can watch the page yourself. Browsers have a tool called MutationObserver. It watches part of the page and reports every change to it. This example uses it inside an Effect (Part 11). The Effect starts the watcher when the app mounts, and its cleanup stops it.
import { useState, useEffect, useRef } from 'react'
function Item({ name }: { name: string }) {
console.log('Item renders:', name)
return <li>{name}</li>
}
export default function App() {
const [clicks, setClicks] = useState(0)
const boxRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const box = boxRef.current
if (!box) return
const watcher = new MutationObserver(changes => {
console.log('The page changed', changes.length, 'time(s)')
})
watcher.observe(box, { subtree: true, childList: true, characterData: true, attributes: true })
return () => watcher.disconnect()
}, [])
console.log('App renders')
return (
<div ref={boxRef}>
<button onClick={() => setClicks(clicks + 1)}>Clicks: {clicks}</button>
<ul>
<Item name="Milk" />
<Item name="Bread" />
<Item name="Eggs" />
</ul>
</div>
)
}
App renders
App renders
Item renders: Milk
Item renders: Milk
Item renders: Bread
Item renders: Bread
Item renders: Eggs
Item renders: Eggs
App renders
App renders
Item renders: Milk
Item renders: Milk
Item renders: Bread
Item renders: Bread
Item renders: Eggs
Item renders: Eggs
The page changed 1 time(s)
The options passed to observe say what to watch. Here it is tags added or removed, text, and attributes, in the whole box. boxRef points at the box, as in Part 14.
Run it and click once. Four components rendered, so that’s four render lines, and eight with Strict Mode. Then the watcher reports one change. We looked at that change. It was the text inside the button: “0” became “1”. The <ul> and the three <li> tags were not touched.
With Strict Mode off, we counted four lines for the click, and still one change. Three clicks gave three changes, one for each.
React’s docs say it plainly: “React only changes the DOM nodes if there’s a difference between renders”. The DOM is the browser’s own model of the page: every tag, its attributes and its text. A DOM node is one thing in it, like one tag or one piece of text.
So a render is not expensive work on the page. It is React calling functions and comparing their answers. That comparing has a name.
Reconciliation: comparing two renders
Reconciliation is how React compares the new render with the last one. It decides what to keep and what to change. “To reconcile” means to make two things agree. Here, React makes the page agree with your latest JSX, with as few changes as it can.
React compares the two trees of elements, place by place. React’s docs use the word too: “Component types participate in reconciliation.” At each place, React asks one main question. Is this the same type as last time? The type is the tag name, like "div", or your component function, like Counter. Part 2 showed it on every element.
Same type at the same place: keep it
When the type is the same, React keeps the DOM node. It changes only what is different about it.
We tested <p className={big ? 'big' : 'small'} title="note">Hello</p>, and switched big. After the switch, it was the same <p> on the page. The watcher saw one change: the class attribute. The title and the text were left alone.
For a component, “keep it” means React keeps that component and its state. It calls the component again with the new props. That is why, in “Try this first”, the <p> from Label stayed on the page. Only its text changed.
A different type at the same place: replace it
When the type is different, React doesn’t try to change the old one into the new one. It removes the old one, with everything inside it, and builds the new one from nothing.
Here a Counter sits inside a <div> or a <section>. The Counter is the same in both. Only the tag around it changes. The Effect logs when Counter is added and removed.
import { useState, useEffect } from 'react'
function Counter() {
const [count, setCount] = useState(0)
useEffect(() => {
console.log('Counter added')
return () => console.log('Counter removed')
}, [])
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>
}
export default function App() {
const [boxed, setBoxed] = useState(true)
return (
<div>
<button onClick={() => setBoxed(!boxed)}>Switch</button>
{boxed ? (
<div>
<Counter />
</div>
) : (
<section>
<Counter />
</section>
)}
</div>
)
}
Counter added
Counter removed
Counter added
Counter removed
Counter added
Counter removed
Counter added
Run it. Click “Count” three times, then press “Switch”. The count goes back to 0.
Read the Console in two groups. The first three lines are the mount. As Part 11 showed, Strict Mode runs an Effect’s setup, then its cleanup, then its setup again. The last four lines are the switch:
Counter removed: the oldCounteris gone, and its Effect was cleaned up.Counter added: a newCounterwas made.Counter removedandCounter addedagain: Strict Mode’s extra test on the newCounter.
With Strict Mode off, the switch logged only “Counter removed” and “Counter added”. The watcher saw the old <div> leave the page and a new <section> arrive. The “Count” button was a new button, not the old one.
The Counter itself didn’t change type. Its parent did. When a place gets a new type, everything below it is replaced too.
Choose a case and step through it:
Reconciliation at one place. Choose a case, then press play or step through it.
In words:
- Last render: a
<div>with aCounterinside. Its count is 3. - You press “Switch”.
Apprenders again and returns new elements. - React compares them at the same place.
- If the new one is also a
<div>, the type is the same. React keeps the<div>and theCounter, and the count stays 3. If it’s a<section>, the type is different. React removes the<div>and everything in it. - Same type: on the page, React changes only the class. Different type: React builds a new
<section>and a newCounter, and the count starts at 0.
We tested the first case with <div className="fancy"> as the second branch. The count stayed at 3, and it was the same button. The only change on the page was the class.
An everyday example
Think of a teacher with a seating chart. Each seat has a desk, and each desk has a student’s things on it. Every morning the teacher gets a new chart.
If seat 3 still says “a student desk”, the teacher keeps the desk and the things on it. Only the name card might change. If seat 3 now says “a plant”, the teacher takes the desk away, with everything on it. A plant goes there instead.
The exact version
The chart picture hides one detail. React does not compare everything with everything. That would be far too slow for a big page.
React’s old docs explain how React keeps this fast. React makes two guesses. First, “Two elements of different types will produce different trees.” So React doesn’t look inside for parts it could save. Second, keys tell React which children are the same. Without keys, React compares each child only with the child at the same place last time. With keys, it matches the children of one parent by key. It never looks in other parents for parts to use again.
The cost of the first guess: a change of type throws away state that you might have wanted. That is the <div> and <section> case above.
The position in the tree is what counts
React keeps state “as long as you render the same component at the same position in the tree”. Your if and ? : are not part of the tree. React doesn’t see them. It sees only what your components return.
Here are the cases. Each starts with an “Ana” counter clicked 3 times. Then a “Switch” button shows “Ben” in its place. Part 7 showed most of them. We ran them all again for this part, with Strict Mode on and off. Both gave the same results.
| The code | Ben shows | Why |
|---|---|---|
isAna ? <Counter label="Ana" /> : <Counter label="Ben" /> |
3 | same type, same place: kept |
the same, with key="ana" and key="ben" |
0 | a different key: replaced |
{isAna && <Counter … />} then {!isAna && <Counter … />} |
0 | two different places |
Ben’s counter wrapped in a <div> |
0 | a <div> where a Counter was |
an if that returns the same tags with Counter in the same place |
3 | same type, same place: kept |
<AnaCounter /> or <BenCounter />, two functions with the same code |
0 | two different types |
Look at the last row. A type is a function, not what the function does. Two functions with the same code are two types.
A component defined inside another component hits the same rule. Part 2 showed that writing function Box() inside App makes a new Box function on every render of App. So the type at that place is new each time. We checked: after App rendered, the box was a new button. React’s docs say: “always declare component functions at the top level, and don’t nest their definitions.”
Keys: matching by name, not by position
Inside a list, the same rule would break things. If you add an item at the top, every item moves down one place. Matching by position then pairs each new item with the wrong old one. Part 8 showed that bug with key={index}.
A key gives each item its own name. React’s docs say a key tells React “to use the key itself as part of the position”. So among siblings, React matches a new item to the old item with the same key and the same type.
We watched the page while reversing a list of three names, Ana, Ben and Cara. With Strict Mode off, it gave:
- With
key={name}, React kept all three old<li>tags. It moved two of them, so the old Cara tag came first. The watcher saw tags moved, and no text changed. - With
key={index}, React moved nothing. The first<li>was still the old Ana tag. React changed its text to “Cara”, and the last tag’s text to “Ana”.
Both pages looked right. But with the index, anything the rows remembered stayed in the old places. That is the bug from Part 8.
And with key={Math.random()}, no key ever matches. We tried it on the Milk, Bread and Eggs list with the “Clicks” button. With Strict Mode off, after one click, 0 of the 3 old <li> tags were still on the page. All three were made again.
Reset state on purpose with a key
Usually you want React to keep state. Sometimes you want a fresh start: an empty form for each new person. Parts 4, 7 and 8 used the answer: change the key.
A key is part of the position. A new key is a new position. So React removes the old component, with its state and its tags, and makes a new one. React’s docs say you can “force a subtree to reset its state by giving it a different key”. A subtree is a component and everything inside it.
This is a real cost, so use it only when you want it. We put key={clicks} on the <ul> in the watcher example. One click then gave three changes, not one. The old <ul> was removed, the button’s text changed, and a new <ul> was added.
When does the screen update?
A set function doesn’t change the page at once. Part 4 showed batching: React waits until your event handler is done, then renders once for all the updates. React’s useState page says React “updates the screen after all the event handlers have run”.
So during the click, the page still shows the old value:
import { useState, useRef } from 'react'
export default function App() {
const [count, setCount] = useState(0)
const textRef = useRef<HTMLParagraphElement>(null)
function handleClick() {
setCount(count + 1)
console.log('Right after setCount, the page says:', textRef.current?.textContent)
}
console.log('App renders with', count)
return (
<div>
<p ref={textRef}>Count: {count}</p>
<button onClick={handleClick}>Add</button>
</div>
)
}
App renders with 0
App renders with 0
Right after setCount, the page says: Count: 0
App renders with 1
App renders with 1
Run it and click “Add”. The line from the handler says “Count: 0”. The trigger has happened, but the render and the commit come after the handler ends. Then the page says “Count: 1”.
Strict Mode: two renders, one commit
Strict Mode calls each component twice in development. So each update gives two renders. Does that mean two commits?
No. We compared the same app with Strict Mode on and off. With it on, one click called App twice. With it off, once. But the watcher saw exactly one change on the page in both cases. The second call is only a test, to find components that are not pure. A pure component gives the same JSX for the same inputs, and changes nothing outside itself. Part 11 explained this. React still commits once.
Effects are the same story. On a click, an Effect with no dependency array ran once in both cases. Strict Mode’s extra setup and cleanup happen only when a component mounts, as Part 11 said.
So to count the renders a production build would do, divide the log lines from the component body by two.
How to see renders
There are three common ways.
console.login the component. That’s what this part did. It is quick, and it shows the order. Remember the Strict Mode doubles.- React Developer Tools. This is a browser add-on from the React team. In its settings there is a box called “Highlight updates when components render”. Turn it on, and the add-on marks each component on the screen when it renders.
- The Profiler tab in React Developer Tools. It records each commit, and how long each component took to render. It has a setting, “Record why each component rendered while profiling”. Part 36 covers it.
Use them in a project on your own computer, like the one from Part 10.
Renders are usually cheap
After this part, you might want to stop every extra render. Usually, don’t.
A render is React calling functions that return small objects. Usually that is fast. And React already keeps the changes to the page small, as the watcher showed. React’s docs say not to make code faster before you need to. The React Compiler page adds that React is often fast enough with no extra work.
So measure first. If something feels slow, find out which components render and how long they take. React’s useMemo page shows a simple way: console.time and console.timeEnd around the slow code. React’s memo page also says to measure with React in production mode. Strict Mode’s extra calls, for one, happen only in development.
Parts 19 to 22 show how to skip renders when you need to. Part 19 starts with the first fix to try: Using Composition to Avoid Extra Renders.
Common mistakes
Believing a props change causes a render
function Footer() {
console.log('Footer renders')
return <p>Thank you!</p>
}
“Footer has no props, so it never renders again.” That’s wrong. In “Try this first” it rendered on every click, because its parent did. The trigger is a state change above it, not its props. If an extra render is a real problem, Parts 19 and 20 show the fixes. Don’t guess. Count first.
Changing data in place and waiting for a render
You saw this with settings.size above. Changing an object, an array, or a normal variable doesn’t trigger anything. React renders only for the first render, a state change or a context change. Keep the data in state, and give the set function a new object:
import { useState } from 'react'
function Size({ options }: { options: { size: number } }) {
console.log('Size renders with', options.size)
return <p>Size: {options.size}</p>
}
export default function App() {
const [settings, setSettings] = useState({ size: 1 })
console.log('App renders')
return (
<div>
<button onClick={() => setSettings({ ...settings, size: settings.size + 1 })}>Bigger</button>
<Size options={settings} />
</div>
)
}
Now three clicks show “Size: 4”. Part 5 explains why the new object matters.
Changing a component’s type in a condition
import type { ReactNode } from 'react'
function Card({ wide, children }: { wide: boolean; children: ReactNode }) {
return wide ? <section>{children}</section> : <div>{children}</div>
}
Every time wide changes, the type at that place changes. So every component inside Card loses its state, and any text typed into its boxes. Keep the same tag and change an attribute instead: <div className={wide ? 'wide' : 'narrow'}>{children}</div>. The same goes for a component defined inside another component. Move it to the top level of the file.
Random keys
key={Math.random()} gives every item a new key on every render. No key matches, so React throws every item away and builds it again. In our test, all 3 <li> tags were new after one click. Use an id that belongs to the item, as Part 8 showed.
Practice
Press Edit on any example above and try these. Strict Mode is on, so count the doubles.
- In “Try this first”, move
<Footer />out ofBasket. Put it inApp, right after<Basket />. Click “Add one” once. Which lines does the click add to the Console? - Start again from “Try this first”. Move the state up into
App. GiveBaskettwo props,countandonAdd, and passonAdd={() => setCount(count + 1)}. Click once. Which components render now? - In the watcher example, change
<Item name="Milk" />to<Item name={'Milk ' + clicks} />. Click once. How many changes does the watcher report? - In the
<div>and<section>example, change<section>to<div className="fancy">(and</section>to</div>). Click “Count” three times and press “Switch”. What does the counter show? Does the Console get any new lines?
Answers
- Four lines: “Basket renders” twice and “Label renders” twice.
Footeris now a child ofApp, andAppdidn’t render. - All five:
App,Title,Basket,LabelandFooter, each twice. That’s 10 lines. The state is now inApp, so the render starts at the top. - “The page changed 2 time(s)”. The button’s text changed, and so did the text of the first item. The other two items rendered but didn’t change on the page.
- “Count: 3”. Both branches are a
<div>now, so the type at that place is the same. React keeps theCounterand changes only theclass. The Console gets no new lines, because theCounterwas never removed.
Interview questions
Try to answer each one out loud before you open the answer.
What makes a React component render?
The common triggers: the first render, when the app starts. A state change in that component, or in a component above it, from a set function or dispatch. A change in a context value it reads. And a change in an outside store it reads with useSyncExternalStore. Props are not on the list. A child gets new props only because its parent rendered.
A strong answer names the exits too. React skips a child whose element is the same object as last time, like one passed in as children. It skips a render when a set function gets the value the state already has, by Object.is. And React.memo skips a child whose props are equal. It also notes that every render makes a new props object for each child. So the props object alone can’t skip a child.
What is the difference between render and commit?
Render is React calling your components to find out what the page should show. Nothing on the page changes during it. Commit is React changing the page to match, and it changes only what is different. Effects run after the commit.
A strong answer gives a number. In our test, one click rendered four components, but the page had one change: the text in one button. It also says why rendering must be pure: the same JSX for the same inputs, and no changes outside. React may render a component more than once before it commits, as Strict Mode does in development.
Does a component re-render when its props change?
Not because of the props. It renders because its parent rendered, and the props come with that render. A child with no props at all renders too. And if you change a props object in place, nothing renders, because nothing triggered React.
A strong answer adds that React.memo changes this. It lets React skip a child when its props are equal to last time. Then “the props changed” does decide whether the child renders.
What is reconciliation?
It is how React compares the new tree of elements with the last one. It decides what to keep and what to change. At each place in the tree, React checks the type. Same type: keep the DOM node or the component and its state, and update what changed. Different type: remove the old one and everything inside it, and run its Effects’ cleanup. Then build the new one. In lists, keys decide which old item each new item matches.
A strong answer explains why this is fast. Without keys, React compares each child only with the child at the same place last time. With keys, it matches the children of one parent by key. It never looks in other parents for parts to use again. That rests on two guesses from React’s docs. Different types make different trees. Keys mark the children that stay the same.
What is the “virtual DOM”? Why don’t React’s current docs use the term?
React’s old docs called it “a programming concept”. In it, a copy of the page is kept in memory, and kept in step with the real page. The old docs called that process reconciliation. In React, people usually mean the element objects your components return. React also keeps other objects of its own about the tree.
The same old page says the term is “more of a pattern than a specific technology”. So “people sometimes say it to mean different things”. The current react.dev pages we read for this series don’t use it. They say what happens instead: React renders your components, then commits the differences to the DOM.
A strong answer doesn’t stop at “React is fast because of the virtual DOM”. Comparing has a cost too. Say what happens: React compares two renders, then changes the page only where they differ.
Why does a component lose its state when you wrap it in a new tag?
React keeps state for the same component at the same place in the tree. Wrapping it changes the type at that place, say from a Counter to a <div>. A different type means React removes the old subtree, state included, and builds a new one. The same happens to a component defined inside another one. Each render makes a new function, so it is a new type.
A strong answer says how to keep state when the look must change. Keep the same tags, and change an attribute or a class instead.
How do you reset a component’s state on purpose?
Give it a key that changes when you want a fresh start: <NoteForm key={friendId} />. A new key is a new position, so React removes the old component and makes a new one. Every piece of state inside starts again, with no extra code.
A strong answer names the cost. The tags on the page are made again, and Effects run again. It also compares this with clearing state in an Effect. That way, the component first renders with the old state, then renders again. React’s docs mark that way “Avoid”, as Part 8 said.
Sources
- Render and Commit, react.dev: trigger, render and commit; rendering “is React calling your components”; the two reasons for a render; rendering is “recursive”; “React only changes the DOM nodes if there’s a difference between renders”; paint; and not making code faster too early (“Don’t optimize prematurely!”).
- Preserving and Resetting State, react.dev: “the same component at the same position in the tree”, different types and wrappers resetting state, not nesting component definitions, keys as “part of the position”, and “force a subtree to reset its state by giving it a different key”.
- React calls Components and Hooks, react.dev: “Component types participate in reconciliation.”
- useState, react.dev: skipping a render for the same value, and that React “updates the screen after all the event handlers have run”.
- Queueing a Series of State Updates, react.dev: batching.
- useContext, react.dev: components that read a context render again when it changes.
- useReducer and useSyncExternalStore, react.dev:
dispatchtriggers a render, and a changed store value re-renders the components that read it. - StrictMode, react.dev: the extra render and the extra Effect run in development.
- memo, useMemo and React Compiler, react.dev: measure in production mode,
console.time, and that React is often fast enough with no extra work. - React Developer Tools, react.dev, and the React DevTools source (GeneralSettings.js, ProfilerSettings.js): the exact names of the two settings.
- Reconciliation and Virtual DOM and Internals, React’s old docs: the two guesses, and what “virtual DOM” meant.
- MutationObserver.observe(), MDN: watching the page for changes, and the
subtree,childList,characterDataandattributesoptions. - Introducing the React Profiler, React blog, 2018: what the Profiler records.
- You Might Not Need an Effect, react.dev: resetting state with a key instead of an Effect.
- Every log, count, page change and table row above comes from running React 19.3.0 for this post, in jsdom, with Strict Mode on and off.
- This part follows the Render Timing and Reconciliation kata in react-katas.