Component Rendering Lifecycle — Complete Guide
In this tutorial, you will learn about Component Rendering Lifecycle. We cover key concepts, practical examples, and best practices to help you master this topic.
The component rendering lifecycle describes how components move from creation to DOM insertion to updates and finally removal, with hooks at each stage for custom logic.
What You'll Learn
- The phases of a component's lifecycle
- How the Virtual Dom interacts with lifecycle hooks
- How React's useEffect maps to lifecycle phases
- How to avoid common lifecycle pitfalls
Why It Matters
Lifecycle hooks control when side effects run, when subscriptions are set up, and when resources are cleaned up. Misunderstanding the lifecycle causes memory leaks, stale data, and rendering bugs.
flowchart LR
A[Component Created] --> B[Render Phase]
B --> C[Virtual DOM created]
C --> D[Diffs with old tree]
D --> E[Commit Phase]
E --> F[DOM inserted/updated]
F --> G[Lifecycle effects run]
G --> H{Updates?}
H -->|Yes| B
H -->|No| I[Unmount Phase]
I --> J[Cleanup effects]
J --> K[DOM removed]
The Three Main Phases
Every component goes through mount, update, and unmount phases.
// Mount phase: component is created and inserted into the DOM
// 1. Constructor / initial render
// 2. Virtual DOM node created
// 3. Diffed (no previous tree, so all nodes are new)
// 4. Committed to DOM (Placement effects)
// 5. Lifecycle hooks fire (useEffect callbacks)
// Update phase: component re-renders due to state or prop changes
// 1. State change triggers re-render
// 2. New virtual tree created
// 3. Diffed against old tree
// 4. Patches committed to DOM
// 5. Cleanup of previous effects (if dependencies changed)
// 6. New effects fire
// Unmount phase: component is removed from the DOM
// 1. Component removed from virtual tree
// 2. Diff detects deletion
// 3. Cleanup effects run
// 4. DOM node removed
Building a Lifecycle-Aware Virtual DOM
Track component states through the lifecycle.
// Lifecycle states
const Lifecycle = {
CREATED: 'created',
MOUNTING: 'mounting',
MOUNTED: 'mounted',
UPDATING: 'updating',
UPDATED: 'updated',
UNMOUNTING: 'unmounting',
UNMOUNTED: 'unmounted'
};
class LifecycleComponent {
constructor(props) {
this.props = props;
this.state = {};
this._lifecycle = Lifecycle.CREATED;
this._effectCleanups = [];
this._vnode = null;
}
// Called by the framework during render phase
render() {
// Building virtual tree
return this._render();
}
_render() {
return {
type: 'div',
props: {},
children: ['Hello ' + this.props.name]
};
}
// Called by the framework after DOM commit
_didMount() {
this._lifecycle = Lifecycle.MOUNTED;
// Run effects
this._runEffects();
}
// Called by the framework when props/state change
_willUpdate(nextProps, nextState) {
this._lifecycle = Lifecycle.UPDATING;
}
_didUpdate(prevProps, prevState) {
this._lifecycle = Lifecycle.UPDATED;
// Cleanup old effects
this._cleanupEffects();
// Run new effects
this._runEffects();
}
// Called by the framework before DOM removal
_willUnmount() {
this._lifecycle = Lifecycle.UNMOUNTING;
this._cleanupEffects();
this._lifecycle = Lifecycle.UNMOUNTED;
}
_runEffects() {
// Effects registered via useEffect equivalent
this._effects.forEach(effect => {
const cleanup = effect();
if (typeof cleanup === 'function') {
this._effectCleanups.push(cleanup);
}
});
}
_cleanupEffects() {
this._effectCleanups.forEach(cleanup => cleanup());
this._effectCleanups = [];
}
}
React Hooks Mapped to Lifecycle
React hooks map to specific lifecycle points.
import React, { useState, useEffect, useLayoutEffect, useRef } from 'react';
function LifecycleDemo({ userId }) {
const [data, setData] = useState(null);
const prevUserIdRef = useRef();
// useEffect runs after the browser paints (async)
useEffect(() => {
// This runs after EVERY completed render
// where [userId] changed
console.log('After paint: userId changed');
// Simulate data fetching
fetch('/api/users/' + userId)
.then(r => r.json())
.then(data => setData(data));
// Cleanup function runs:
// 1. Before the next effect run (if userId changes)
// 2. On unmount
return () => {
console.log('Cleanup: abort fetch or unsubscribe');
// AbortController would go here
};
}, [userId]);
// useLayoutEffect runs synchronously after DOM mutations
// but before the browser paints
useLayoutEffect(() => {
// Use this for measuring DOM or preventing visual flicker
// Similar to componentDidMount/componentDidUpdate
console.log('Before paint: DOM updated');
}, [userId]);
// Ref stores previous value across renders
useEffect(() => {
prevUserIdRef.current = userId;
});
const prevUserId = prevUserIdRef.current;
return (
<div>
<p>Current: {userId}</p>
<p>Previous: {prevUserId}</p>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
Lifecycle and the Virtual DOM
The reconciler manages lifecycle transitions based on virtual tree comparison.
// The reconciler decides lifecycle transitions from diff results
function reconcileFiber(fiber, newVNode) {
if (fiber === null) {
// New fiber: MOUNT lifecycle
return mountFiber(newVNode);
}
if (newVNode === null) {
// Fiber exists but no new vnode: UNMOUNT lifecycle
fiber.effectTag = 'DELETION';
return null;
}
if (fiber.type !== newVNode.type) {
// Different type: UNMOUNT old, MOUNT new
fiber.effectTag = 'DELETION';
return mountFiber(newVNode);
}
// Same type: UPDATE lifecycle
fiber.effectTag = 'UPDATE';
fiber.pendingProps = newVNode.props;
// Children reconciliation determines
// child component lifecycle transitions
reconcileChildren(fiber, newVNode.props.children);
return fiber;
}
// After all fibers are processed, the commit phase
// walks the effect list and calls lifecycle hooks:
// 1. For DELETION fibers: componentWillUnmount
// 2. For PLACEMENT fibers: componentDidMount
// 3. For UPDATE fibers: componentDidUpdate
Common Lifecycle Pitfalls
Avoid these common mistakes with lifecycle hooks.
// Pitfall 1: Missing cleanup causes memory leaks
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
// Missing cleanup: timer keeps running after unmount
// return () => clearInterval(timer); // FIX
}, []);
// Pitfall 2: Stale closure in effects
useEffect(() => {
// This closure captures the value of count at effect creation time
// If count changes, the interval still uses the old value
const timer = setInterval(() => {
console.log(count); // Always logs the initial count
}, 1000);
return () => clearInterval(timer);
}, []); // Empty dependency array: effect never re-runs
// FIX: Add count to dependencies or use ref
// Pitfall 3: Infinite update loops
useEffect(() => {
// This effect sets state, which triggers re-render,
// which triggers this effect again... infinite loop
setData({ ...data, updated: true });
}, [data]); // data changes every render
// FIX: Use functional update or remove data from deps
// Pitfall 4: Wrong lifecycle for DOM measurements
useEffect(() => {
// useEffect runs AFTER paint
// Measuring DOM here shows the POST-PAINT state
const width = elementRef.current.offsetWidth;
// This causes a forced reflow after paint — wasteful
}, []);
// FIX: useLayoutEffect for DOM measurements
useLayoutEffect(() => {
// Runs BEFORE paint, measurement happens before browser renders
const width = elementRef.current.offsetWidth;
}, []);
Common Mistakes
- Running side effects in the render phase (should only happen in commit phase).
- Forgetting cleanup functions in useEffect, causing memory leaks.
- Using useEffect for synchronous DOM measurements (use useLayoutEffect instead).
- Having stale closures by omitting dependencies from useEffect arrays.
- Setting state in useEffect without proper dependency management, causing infinite loops.
Practice Questions
- What are the three main lifecycle phases? Mount, update, and unmount.
- When does useEffect run? After the browser paints (asynchronously).
- When does useLayoutEffect run? Synchronously after DOM mutations but before the browser paints.
- What happens during the unmount phase? Cleanup functions run, then the DOM node is removed.
Challenge
Build a component lifecycle visualizer that shows the current phase of each component in a tree. Use different colors for mount (green), update (yellow), and unmount (red). Animate the state transitions. Include lifecycle timing information (render time, commit time, effect duration).
FAQ
Mini Project
Build a set of lifecycle demo components. Create components using class-based (componentDidMount, componentDidUpdate, componentWillUnmount) and hook-based (useEffect, useLayoutEffect) lifecycles. Show a timeline of when each hook fires relative to the render and paint cycles.
What's Next
Lesson 10: Virtual DOM in React
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro