ReactJs Under the Hood
Understanding how React works internally helps developers write more performant applications and debug complex issues. React’s architecture has evolved significantly over the years, with the Fiber reconciliation engine representing a major rewrite that enables features like concurrent rendering and time-slicing.
Virtual DOM and Reconciliation
React uses a Virtual DOM, an in-memory representation of the actual DOM. When component state changes, React:
- Creates a new Virtual DOM tree
- Compares it with the previous tree (reconciliation)
- Calculates minimal changes needed
- Updates only changed parts of the real DOM
This approach is faster than manipulating the DOM directly for complex UIs, as DOM operations are expensive while JavaScript object comparisons are cheap.
The Reconciliation Algorithm
React’s reconciliation algorithm makes assumptions to achieve O(n) complexity instead of O(n³):
Assumption 1: Elements of different types produce different trees
// React will destroy the old <div> and create a new <span>
<div><Counter /></div>
↓
<span><Counter /></span>
Assumption 2: Developers can hint at stable children with keys
// Without keys, React might recreate all elements
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
Assumption 3: Component instances maintain identity across renders when the type and key don’t change
The Fiber Architecture
React 16 introduced Fiber, a complete rewrite of React’s reconciliation algorithm. Fiber is a JavaScript object representing a unit of work, forming a linked-list tree structure.
What is a Fiber?
Each Fiber represents a component instance and contains:
- Component type and props
- State and hooks
- Pointers to child, sibling, and parent Fibers
- Effect lists (side effects to perform)
- Priority and expiration time
// Simplified Fiber structure
{
type: 'div', // Component type
key: null, // Unique key
props: { className: 'app' }, // Props
stateNode: <DOMElement>, // Actual DOM node
child: <Fiber>, // First child Fiber
sibling: <Fiber>, // Next sibling Fiber
return: <Fiber>, // Parent Fiber
alternate: <Fiber>, // Previous Fiber for comparison
effectTag: 'UPDATE', // Type of change
nextEffect: <Fiber> // Next Fiber with effects
}
The Fiber Tree
React maintains two Fiber trees:
- Current tree: Represents the current UI
- Work-in-progress tree: Being constructed during updates
After completing work, React swaps the work-in-progress tree to become the current tree (double buffering).
Render Phases
React’s rendering process has two distinct phases:
1. Render Phase (Reconciliation)
The render phase is pure and can be paused, aborted, or restarted:
- Begin work: React walks down the tree, creating or updating Fibers
- React calls component functions and lifecycle methods
- Calculates which Fibers need changes
- Builds effect lists
This phase can be interrupted and resumed, enabling concurrent features.
2. Commit Phase
The commit phase is synchronous and cannot be interrupted:
- Complete work: React walks up the tree, finalizing Fibers
- Commit: React applies changes to the DOM
- Runs layout effects (
useLayoutEffect) - Runs passive effects (
useEffect) after painting
Concurrent Rendering
React 18 introduced concurrent rendering, allowing React to work on multiple state updates simultaneously and interrupt rendering for higher-priority updates.
Time Slicing
React breaks rendering work into chunks and yields to the browser periodically:
function ExpensiveList({ items }) {
// With concurrent rendering, React can pause rendering this list
// to handle user input, then resume
return (
<ul>
{items.map(item => (
<ExpensiveItem key={item.id} item={item} />
))}
</ul>
);
}
Priority Levels
React assigns priority to updates:
- Immediate: User interactions (clicks, typing)
- Normal: Network responses, timers
- Low: Data fetching, analytics
Higher-priority updates can interrupt lower-priority ones.
Transitions
The useTransition Hook marks updates as transitions, allowing React to defer them:
import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// Update input immediately
setQuery(e.target.value);
// Mark results update as a transition
startTransition(() => {
setResults(expensiveSearch(e.target.value));
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending ? <Spinner /> : <List items={results} />}
</>
);
}
React Internals Workflow
Creating a New Feature (Live Coding)
Dan Abramov’s live coding session demonstrates implementing a new React feature. Key insights:
React’s package structure:
- Each package has its own responsibility
- Source code lives in
packages/*/src - Tests can be anywhere but are often co-located
- The codebase is complex but well-organized
Development workflow:
- Test-first development is encouraged
- Start with a failing test for the desired behavior
- Implement the feature to make the test pass
- Consider edge cases and refactor
Key internal modules:
-
ReactFiberBeginWork: Handles the “begin” phase of reconciliation -
ReactFiberCompleteWork: Handles the “complete” phase -
ReactFiberCommitWork: Applies changes to the DOM -
ReactWorkTags: Defines all possible Fiber types
Important concepts:
- Fibers are reused between renders for performance
- React 16+ creates a copy of the initial Fiber (current ↔ work-in-progress)
- The reconciliation process has three main steps: begin, complete, commit
Prefer Fragments Over Divs
For wrapping multiple elements without styles, use Fragments instead of divs:
// Less optimal - creates unnecessary DOM node
return (
<div>
<Child1 />
<Child2 />
</div>
);
// Better - no extra DOM node
return (
<>
<Child1 />
<Child2 />
</>
);
Server Components
React Server Components (RSC) allow components to render on the server, sending only the rendered output to the client:
// Server Component (default)
async function BlogPost({ id }) {
const post = await db.posts.find(id);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
<Comments postId={id} /> {/* Can be a Client Component */}
</article>
);
}
// Client Component (opt-in with 'use client')
'use client';
function Comments({ postId }) {
const [comments, setComments] = useState([]);
useEffect(() => {
fetchComments(postId).then(setComments);
}, [postId]);
return <CommentList comments={comments} />;
}
Benefits:
- Reduced bundle size (server components don’t ship to client)
- Direct database access without API layer
- Automatic code splitting
- Streaming server rendering
Discussed in: React Server Components with Dan Abramov, Joe Savona, and Kent C. Dodds
React 19 Features
React 19 introduces several new capabilities:
Actions
Actions simplify handling async operations:
function UpdateForm() {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
const response = await updateUser(formData);
if (response.error) {
return { error: response.error };
}
return { success: true };
},
{ error: null }
);
return (
<form action={submitAction}>
<input name="name" />
<button disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
{error && <p>{error}</p>}
</form>
);
}
use Hook
The use Hook reads resources like Promises and Contexts:
function UserProfile({ userPromise }) {
const user = use(userPromise);
return <div>{user.name}</div>;
}
Debugging React Internals
React DevTools
React DevTools provides insight into the component tree, props, state, and performance:
- Components tab: Inspect component hierarchy and state
- Profiler tab: Measure rendering performance
- Highlight updates: Visualize which components re-render
Strict Mode
StrictMode helps identify potential problems by intentionally double-invoking functions:
<StrictMode>
<App />
</StrictMode>
In development, Strict Mode:
- Calls component functions twice
- Runs effects twice
- Checks for deprecated APIs
- Warns about unsafe lifecycles
Resources
- React Fiber Architecture
- React - The Complete Guide (47h Course)
- React app structure (PROWEB-2021 paper)
- Opinionated folder structure by George Moller
- The future of React - Changelog podcast
- React Server Components discussion
- Hooks + multiple instances of React Issue
- Why React? - UI.dev course (free lessons available)