React Internals: JSX, Reconciliation & Virtual DOM
A deep dive into how React works under the hood, with visual diagrams and practical examples.
Table of Contentsโ
-
- JSX Transformation Example
- React.createElement Signature
- Complex Example
- What React.createElement Returns
- JSX Compilation Flow
-
- Real DOM vs Virtual DOM Comparison
- Virtual DOM Structure
- Example: Virtual DOM Object
-
- Diffing Heuristics
- Type Change Example
- Same Type Diffing
-
- Without Keys (Bad)
- With Keys (Good)
- Key Rules
-
- High-Level Reconciliation Flow
- Step-by-Step Example
- Reconciliation Phases
-
- What is a Fiber?
- Fiber Tree Structure
- Work Loop
- Why Fiber?
-
- Complete React Update Cycle
- Real-World Example
-
- Best Practices
1. JSX โ React.createElementโ
JSX is syntactic sugar that gets transformed into React.createElement calls during the build process.
JSX Transformation Exampleโ
// What you write:
const element = <div className="container">Hello World</div>;
// What it becomes:
const element = React.createElement(
'div',
{ className: 'container' },
'Hello World'
);
React.createElement Signatureโ
React.createElement(type, props, ...children)
- type: String (HTML tag) or Component (function/class)
- props: Object with properties (null if none)
- children: Child elements or text
Complex Exampleโ
// JSX:
<div className="card">
<h1>Title</h1>
<p>Description</p>
<Button onClick={handleClick}>Click me</Button>
</div>
// Transformed to:
React.createElement(
'div',
{ className: 'card' },
React.createElement('h1', null, 'Title'),
React.createElement('p', null, 'Description'),
React.createElement(
Button,
{ onClick: handleClick },
'Click me'
)
);
What React.createElement Returnsโ
It returns a React Element - a plain JavaScript object:
{
type: 'div',
props: {
className: 'card',
children: [
{ type: 'h1', props: { children: 'Title' } },
{ type: 'p', props: { children: 'Description' } },
{ type: Button, props: { onClick: fn, children: 'Click me' } }
]
},
key: null,
ref: null,
$$typeof: Symbol.for('react.element')
}
JSX Compilation Flowโ
2. Virtual DOM vs Real DOMโ
The Virtual DOM is React's lightweight representation of the actual DOM.
Real DOM vs Virtual DOMโ
| Aspect | Real DOM | Virtual DOM |
|---|---|---|
| Nature | Browser's actual DOM tree | JavaScript object representation |
| Updates | Expensive (triggers reflow/repaint) | Cheap (just object manipulation) |
| Speed | Slow for frequent updates | Fast diffing and batching |
| Memory | Heavy | Lightweight |
Virtual DOM Structureโ
Example: Virtual DOM Objectโ
// Virtual DOM representation
{
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: { children: 'Hello' }
},
{
type: 'ul',
props: {
children: [
{ type: 'li', props: { children: 'Item 1' } },
{ type: 'li', props: { children: 'Item 2' } }
]
}
}
]
}
}
3. Diffing Algorithmโ
React uses a heuristic O(n) algorithm instead of the traditional O(nยณ) tree diffing.
Diffing Heuristicsโ
React makes two assumptions:
- Different types produce different trees - If element type changes, rebuild from scratch
- Keys identify which children have changed - Use keys to match elements across renders
Type Change Exampleโ
Result: Entire subtree is destroyed and rebuilt because div โ p
// Before:
<div><span>Hello</span></div>
// After:
<p><span>Hello</span></p>
// React destroys div and span, creates new p and span
Same Type Diffingโ
Result: Keep the DOM node, update only changed attributes
// Before:
<div className="old" style={{color: 'red'}}>Text</div>
// After:
<div className="new" style={{color: 'blue'}}>Text</div>
// React only updates className and style, keeps the DOM node
4. Keys and List Reconciliationโ
Keys help React identify which items have changed, been added, or removed.
Without Keys (Bad)โ
// Initial render:
<ul>
<li>Alice</li>
<li>Bob</li>
</ul>
// After adding Charlie at the beginning:
<ul>
<li>Charlie</li>
<li>Alice</li>
<li>Bob</li>
</ul>
What React does: Updates all three <li> elements (inefficient!)
With Keys (Good)โ
// Initial render:
<ul>
<li key="alice">Alice</li>
<li key="bob">Bob</li>
</ul>
// After adding Charlie:
<ul>
<li key="charlie">Charlie</li>
<li key="alice">Alice</li>
<li key="bob">Bob</li>
</ul>
What React does: Recognizes Alice and Bob unchanged, only inserts Charlie
Key Rulesโ
// โ
Good: Stable, unique keys
items.map(item => <div key={item.id}>{item.name}</div>)
// โ Bad: Index as key (unstable when reordering)
items.map((item, index) => <div key={index}>{item.name}</div>)
// โ Bad: Random keys (creates new elements every render)
items.map(item => <div key={Math.random()}>{item.name}</div>)
5. Reconciliation Processโ
Reconciliation is the algorithm React uses to diff one tree with another to determine what needs to change.
High-Level Reconciliation Flowโ
Step-by-Step Exampleโ
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
Step 1: Initial Render
Step 2: State Update (count = 1)
Reconciliation Phasesโ
React 16+ uses Fiber architecture with two phases:
6. Fiber Architectureโ
Fiber is React's reconciliation engine since version 16. It's a reimplementation of the core algorithm.
What is a Fiber?โ
A Fiber is a JavaScript object representing a unit of work. Each React element has a corresponding Fiber node.
// Simplified Fiber node structure
{
type: 'div', // Component type
key: null, // Key from props
props: { children: [...] }, // Props
stateNode: DOMNode, // Actual DOM node
return: parentFiber, // Parent fiber
child: firstChildFiber, // First child
sibling: nextSiblingFiber, // Next sibling
alternate: oldFiber, // Previous version
effectTag: 'UPDATE', // What needs to be done
nextEffect: nextFiber // Linked list of effects
}
Fiber Tree Structureโ
Work Loopโ
Fiber enables time-slicing: breaking work into chunks and spreading it across multiple frames.
// Simplified work loop concept
function workLoop(deadline) {
let shouldYield = false;
while (nextUnitOfWork && !shouldYield) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
if (nextUnitOfWork) {
// More work to do, schedule next frame
requestIdleCallback(workLoop);
} else {
// All work done, commit to DOM
commitRoot();
}
}
Why Fiber?โ
Before Fiber (Stack Reconciler):
- Synchronous, recursive
- Once started, couldn't pause
- Long updates blocked the browser
With Fiber:
- Asynchronous, can pause/resume
- Prioritize urgent updates (user input)
- Better perceived performance
7. Putting It All Togetherโ
Complete React Update Cycleโ
Real-World Exampleโ
function TodoApp() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React' },
{ id: 2, text: 'Build App' }
]);
const addTodo = () => {
setTodos([...todos, { id: 3, text: 'Deploy' }]);
};
return (
<div>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
<button onClick={addTodo}>Add</button>
</div>
);
}
When addTodo is clicked:
- setState triggers update
- JSX transformed to
React.createElementcalls - Virtual DOM new tree created with 3 todos
- Diffing compares old (2 todos) vs new (3 todos)
- Keys identify that todos 1 & 2 unchanged
- Fiber marks effect: INSERT new
<li>with id=3 - Commit inserts single DOM node
- Result efficient update, only one DOM insertion
8. Performance Implicationsโ
Best Practicesโ
1. Use Keys for Lists
// โ
Efficient reconciliation
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
2. Avoid Inline Functions/Objects in JSX
// โ Creates new function every render
<button onClick={() => handleClick(id)}>Click</button>
// โ
Memoized callback
const handleClickMemoized = useCallback(() => handleClick(id), [id]);
<button onClick={handleClickMemoized}>Click</button>
3. Prevent Unnecessary Re-renders
// โ
Memoize expensive components
const MemoizedChild = React.memo(Child);
// โ
Use React.memo with custom comparison
const MemoizedItem = React.memo(Item, (prev, next) => {
return prev.id === next.id && prev.text === next.text;
});
4. Keep Component Type Stable
// โ Creates new component type every render
function Parent() {
const Child = () => <div>Child</div>;
return <Child />;
}
// โ
Stable component reference
const Child = () => <div>Child</div>;
function Parent() {
return <Child />;
}
Summaryโ
- JSX is syntactic sugar for
React.createElementcalls - React Elements are plain JavaScript objects describing UI
- Virtual DOM is a lightweight representation enabling efficient updates
- Diffing uses heuristics (type comparison, keys) for O(n) performance
- Fiber enables interruptible rendering and better performance
- Keys are critical for efficient list reconciliation
- Reconciliation compares trees and produces minimal DOM updates
Understanding these internals helps you write more performant React applications and debug issues more effectively.