React Internals
Reconciliation
How React updates the DOM efficiently
Advanced
react diffing vdom performance
Definition
Reconciliation is the algorithm React uses to diff one tree with another to determine which parts need to be updated. It enables React to update the DOM efficiently by computing the minimum set of changes needed.
The Diffing Algorithm
Principles
// 1. Different element types produce different trees
// 2. Keys can hint at which child elements may be stable
Element Type Comparison
// React compares element types at the same position
// Before
<div>
<Counter />
</div>
// After - Different type, unmount Counter, mount span
<span>
<Counter />
</span>
// Result: Counter state is lost, new instance created
Props Comparison
// Same element type, different props
<div className="before" title="old" />
// After
<div className="after" title="new" />
// React updates: className and title attributes only
// No DOM element recreation
Keys and Lists
Without Keys (Problem)
// Initial state
[
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
// After inserting at beginning
[
{ id: 3, name: 'Charlie' }, // New
{ id: 1, name: 'Alice' }, // Was index 0
{ id: 2, name: 'Bob' } // Was index 1
]
// Without keys, React sees:
// Index 0: Alice → Charlie (update)
// Index 1: Bob → Alice (update)
// Index 2: Nothing → Bob (create)
// Result: Unnecessary updates, state loss
With Keys (Solution)
// Using unique IDs as keys
{
items.map(item => (
<ListItem key={item.id} name={item.name} />
))
}
// React sees:
// Key 3: Not present → Create
// Key 1: Same position → Keep
// Key 2: Same position → Keep
// Result: Minimal updates, state preserved
Key Best Practices
// Good: Stable, unique IDs
items.map(item => (
<div key={item.id}>...</div>
))
// Bad: Array index (unstable)
items.map((item, index) => (
<div key={index}>...</div>
))
// Bad: Random values
items.map(item => (
<div key={Math.random()}>...</div>
))
State Preservation
Same Position, Same Type
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
// Parent re-renders but Counter stays at same position
function App() {
return (
<div>
<Header />
<Counter /> {/* State preserved */}
</div>
);
}
State Reset Patterns
// Method 1: Different key forces remount
<UserProfile key={userId} userId={userId} />
// Method 2: Conditional rendering
{showA ? <ComponentA /> : <ComponentB />}
// Method 3: Different component types
{
isAdmin ? <AdminDashboard /> : <UserDashboard />
}
Reconciliation Process
// 1. Create virtual DOM tree (React elements)
const element = <div className="container">Hello</div>;
// 2. Compare with previous tree
// - Element type same? Update props
// - Element type different? Replace
// - Array items? Check keys
// 3. Generate list of DOM operations
operations = [
{ type: 'UPDATE_PROP', target: div, prop: 'className', value: 'container' },
{ type: 'UPDATE_TEXT', target: textNode, value: 'Hello' }
]
// 4. Batch and apply operations
// Uses requestIdleCallback for low priority
// Commit phase applies to DOM
Performance Implications
Expensive Re-renders
// Bad: Inline object creates new reference every render
<Child config={{ setting: 'value' }} />
// Good: Stable reference
const config = useMemo(() => ({ setting: 'value' }), []);
<Child config={config} />
Component Splitting
// Before: Entire form re-renders
function Form() {
const [values, setValues] = useState({});
return (
<form>
<input value={values.a} onChange={...} />
<input value={values.b} onChange={...} />
<input value={values.c} onChange={...} />
{/* All inputs re-render on any change */}
</form>
);
}
// After: Isolated re-renders
function Form() {
return (
<form>
<Field name="a" />
<Field name="b" />
<Field name="c" />
</form>
);
}
function Field({ name }) {
const [value, setValue] = useState('');
// Only this field re-renders
return <input value={value} onChange={...} />;
}
Debugging Re-renders
React DevTools Profiler
// Record and analyze component renders
// Shows why each component rendered
// Identifies unnecessary re-renders
Custom Hook
function useWhyDidYouUpdate(name, props) {
const previousProps = useRef();
useEffect(() => {
if (previousProps.current) {
const allKeys = Object.keys({ ...previousProps.current, ...props });
const changesObj = {};
allKeys.forEach(key => {
if (previousProps.current[key] !== props[key]) {
changesObj[key] = {
from: previousProps.current[key],
to: props[key]
};
}
});
if (Object.keys(changesObj).length) {
console.log('[why-did-you-update]', name, changesObj);
}
}
previousProps.current = props;
});
}
Key Takeaway
React’s reconciliation algorithm efficiently updates the UI by comparing trees and applying minimal changes. Use stable keys for lists, understand when state is preserved or reset, and split components to minimize re-render scope. Profile your app to identify and optimize expensive renders.