π§© What is a Component in React?
Think of a React component like a reusable piece of UI. For example:
A button on your webpage can be a component.
A navigation bar is a component.
A form can be another component.
You can create these components either using:
Class components (older style)
Function components (modern way using Hooks)
This explanation is focused on Class Components and how they live their life β just like a human! π―
𧬠What is a Lifecycle?
Every component in React goes through a life:
It is born β added to the webpage
It lives β updates when needed
It dies β removed from the webpage
These steps are called the Component Lifecycle.
<div class="infographic-lifecycle" id="lc">
<style>
.infographic-lifecycle {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-deep: #1942a8;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--blue: #cfe0fb;
--blue-ink: #1942a8;
--tan: #f7e6b8;
--tan-ink: #7a5b0a;
--green: #cdeccb;
--green-ink: #1f6b2c;
--radius: 8px;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 960px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
color: var(--ink);
font-family: var(--body-font);
}
.infographic-lifecycle[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171f;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-deep: #8fb4ff;
--accent-soft: #20293f;
--line: #2c313d;
--blue: #223256;
--blue-ink: #a9c4ff;
--tan: #4a3c17;
--tan-ink: #f0d998;
--green: #234a2c;
--green-ink: #a6e2ac;
}
.infographic-lifecycle * { box-sizing: border-box; min-width: 0; }
.lc-eyebrow {
font-family: var(--mono-font); font-size: 0.72rem; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--accent); margin: 0 0 0.45rem;
display: flex; align-items: center; justify-content: center; gap: 0.4rem;
}
.lc-eyebrow::before { content: ""; width: 14px; height: 2px; background: var(--accent); display: inline-block; }
.lc-title {
font-family: var(--display-font); font-size: clamp(1.4rem, 4vw, 2.15rem); text-align: center;
margin: 0 0 0.5rem; line-height: 1.18; letter-spacing: -0.01em;
}
.lc-sub { color: var(--ink-soft); font-size: 0.95rem; text-align: center; margin: 0 0 1.4rem; }
.lc-caption {
text-align: center; font-size: 0.92rem; color: var(--ink); min-height: 2.6em; display: flex;
align-items: center; justify-content: center; max-width: 620px; margin: 0 auto 1.6rem; line-height: 1.45;
padding: 0.5rem 1rem; background: var(--accent-soft); border-radius: 100px;
}
.lc-caption b { color: var(--accent-deep); }
.lc-columns { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.1rem; margin-bottom: 1.6rem; }
@media (max-width: 760px) { .lc-columns { grid-template-columns: 1fr; } }
.lc-col {
border: 1px solid var(--line); border-radius: var(--radius); padding: 1rem 0.9rem 1.1rem;
background: var(--paper-alt); display: flex; flex-direction: column;
}
.lc-col-title {
font-family: var(--display-font); font-size: 1.05rem; text-align: center; margin: 0 0 0.9rem; color: var(--ink);
}
.lc-triggers { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.35rem; margin-bottom: 0.5rem; }
.lc-trigger-chip {
font: inherit; font-family: var(--mono-font); font-size: 0.66rem; font-weight: 600; color: var(--accent-deep);
background: var(--accent-soft); border: 1px solid transparent; border-radius: 100px; padding: 0.28rem 0.6rem;
cursor: pointer; transition: border-color 0.2s, transform 0.15s;
}
.lc-trigger-chip:hover { transform: translateY(-1px); }
.lc-trigger-chip.is-active { border-color: var(--accent); }
.lc-trigger-chip:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.lc-arrow { height: 18px; width: 2px; background: repeating-linear-gradient(to bottom, var(--ink-soft) 0 4px, transparent 4px 8px); margin: 0 auto; position: relative; }
.lc-arrow::after {
content: ""; position: absolute; bottom: -1px; left: 50%; transform: translateX(-50%);
border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 5px solid var(--ink-soft);
}
.lc-arrow.is-lit { background: repeating-linear-gradient(to bottom, var(--accent) 0 4px, transparent 4px 8px); }
.lc-arrow.is-lit::after { border-top-color: var(--accent); }
.lc-box {
font: inherit; font-weight: 700; font-size: 0.82rem; text-align: center; color: var(--ink);
border: 2px solid transparent; border-radius: 6px; padding: 0.6rem 0.7rem; cursor: pointer; width: 100%;
transition: box-shadow 0.25s ease, border-color 0.25s ease, transform 0.2s ease;
}
.lc-box.lc-blue { background: var(--blue); color: var(--blue-ink); }
.lc-box.lc-tan { background: var(--tan); color: var(--tan-ink); font-weight: 600; font-style: italic; font-size: 0.78rem; }
.lc-box.lc-green { background: var(--green); color: var(--green-ink); }
.lc-box:hover { transform: translateY(-1px); }
.lc-box:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.lc-box.is-active { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.lc-unmount-visual { display: flex; justify-content: center; margin-top: 0.7rem; }
.lc-node-chip {
font-family: var(--mono-font); font-size: 0.72rem; color: var(--ink-soft); border: 1px dashed var(--line);
border-radius: 6px; padding: 0.35rem 0.7rem; transition: opacity 0.7s ease, transform 0.7s ease;
}
.lc-node-chip.is-gone { opacity: 0; transform: scale(0.85); }
.lc-detail {
margin-top: 0.9rem; padding-top: 0.8rem; border-top: 1px dashed var(--line);
font-size: 0.8rem; color: var(--ink-soft); line-height: 1.45; min-height: 2.6em;
}
.lc-detail b { color: var(--ink); }
.lc-play-btn {
margin-top: 0.9rem; align-self: center; font: inherit; font-weight: 600; font-size: 0.78rem;
color: #fff; background: var(--accent); border: none; border-radius: 100px; padding: 0.45rem 1rem;
cursor: pointer; box-shadow: 0 6px 16px -4px rgba(37,99,235,0.4); transition: transform 0.15s, opacity 0.15s;
}
.lc-play-btn:hover:not(:disabled) { transform: translateY(-1px); }
.lc-play-btn:disabled { opacity: 0.55; cursor: default; }
.lc-play-btn:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.lc-takeaway {
padding: 0.95rem 1.2rem; background: var(--accent-soft); border-left: 3px solid var(--accent);
border-radius: 4px; font-size: 0.9rem; color: var(--ink); line-height: 1.5; max-width: 700px; margin: 0 auto;
}
.lc-takeaway b { color: var(--accent-deep); }
.lc-takeaway code { font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-lifecycle *, .infographic-lifecycle *::before, .infographic-lifecycle *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
<div class="lc-eyebrow">Class Components</div>
<h1 class="lc-title">The Component Lifecycle</h1>
<p class="lc-sub">Click any step for what it does, or press Play to watch a phase run in order.</p>
<p class="lc-caption" id="lcCaption">Click a step below, or hit Play on a phase.</p>
<div class="lc-columns">
<!-- MOUNTING -->
<div class="lc-col" data-phase="mount">
<h2 class="lc-col-title">Mounting</h2>
<div class="lc-arrow" data-arrow="mount-0"></div>
<button class="lc-box lc-blue" data-step="mount-0" data-detail="Runs once, before anything renders. Set initial state and bind handlers here.">constructor</button>
<div class="lc-arrow" data-arrow="mount-1"></div>
<button class="lc-box lc-blue" data-step="mount-1" data-detail="React figures out what the UI should look like. No DOM writes happen yet.">render</button>
<div class="lc-arrow" data-arrow="mount-2"></div>
<button class="lc-box lc-tan" data-step="mount-2" data-detail="React commits render's output to the actual DOM and sets refs.">React updates DOM and refs</button>
<div class="lc-arrow" data-arrow="mount-3"></div>
<button class="lc-box lc-green" data-step="mount-3" data-detail="DOM is live. Safe place to fetch data, start timers, or read refs.">componentDidMount</button>
<div class="lc-detail" id="lcDetail-mount">Click a step above.</div>
<button class="lc-play-btn" data-play="mount">βΆ Play mount</button>
</div>
<!-- UPDATING -->
<div class="lc-col" data-phase="update">
<h2 class="lc-col-title">Updating</h2>
<div class="lc-triggers">
<button class="lc-trigger-chip" data-trigger="props" data-detail="A parent re-rendered and passed this component new props.">New props</button>
<button class="lc-trigger-chip" data-trigger="state" data-detail="The component called this.setState() on itself.">setState()</button>
<button class="lc-trigger-chip" data-trigger="force" data-detail="forceUpdate() manually triggers a re-render, skipping shouldComponentUpdate.">forceUpdate()</button>
</div>
<div class="lc-arrow" data-arrow="update-1"></div>
<button class="lc-box lc-blue" data-step="update-1" data-detail="React re-describes the UI using the new props or state.">render</button>
<div class="lc-arrow" data-arrow="update-2"></div>
<button class="lc-box lc-tan" data-step="update-2" data-detail="React diffs the new output against the old DOM and patches only what changed.">React updates DOM and refs</button>
<div class="lc-arrow" data-arrow="update-3"></div>
<button class="lc-box lc-green" data-step="update-3" data-detail="DOM already reflects the change β good place to compare prevProps or trigger side effects.">componentDidUpdate</button>
<div class="lc-detail" id="lcDetail-update">Click a trigger or step above.</div>
<button class="lc-play-btn" data-play="update">βΆ Play update (setState)</button>
</div>
<!-- UNMOUNTING -->
<div class="lc-col" data-phase="unmount">
<h2 class="lc-col-title">Unmounting</h2>
<div class="lc-arrow" data-arrow="unmount-0" style="height: 56px;"></div>
<button class="lc-box lc-green" data-step="unmount-0" data-detail="Last chance to clean up β clear timers, cancel subscriptions, remove event listeners.">componentWillUnmount</button>
<div class="lc-unmount-visual">
<span class="lc-node-chip" id="lcNodeChip"><Component /> in the tree</span>
</div>
<div class="lc-detail" id="lcDetail-unmount">Click the step above.</div>
<button class="lc-play-btn" data-play="unmount">βΆ Play unmount</button>
</div>
</div>
<div class="lc-takeaway"><b>Same three questions, every time:</b> mounting asks "how do I start?", updating asks "how do I respond to change?", unmounting asks "how do I clean up?" β <code>render()</code> is the only method every phase but unmounting shares, and it never touches the DOM directly.</div>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('lc');
if (!root) return;
/* --- dark mode detection --- */
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'darkMode') {
if (e.data.value) root.setAttribute('data-theme', 'dark');
else root.removeAttribute('data-theme');
}
});
var caption = root.querySelector('#lcCaption');
var allBoxes = Array.prototype.slice.call(root.querySelectorAll('.lc-box'));
var allChips = Array.prototype.slice.call(root.querySelectorAll('.lc-trigger-chip'));
var allArrows = Array.prototype.slice.call(root.querySelectorAll('.lc-arrow'));
var playTimers = [];
function clearActive() {
allBoxes.forEach(function (b) { b.classList.remove('is-active'); });
allArrows.forEach(function (a) { a.classList.remove('is-lit'); });
}
function setDetail(phase, text) {
var el = root.querySelector('#lcDetail-' + phase);
if (el) el.innerHTML = text;
}
// click-to-reveal on every step box
allBoxes.forEach(function (box) {
box.addEventListener('click', function () {
clearActive();
box.classList.add('is-active');
var phase = box.closest('.lc-col').getAttribute('data-phase');
setDetail(phase, box.textContent + ' β ' + box.getAttribute('data-detail'));
caption.innerHTML = '<b>' + box.textContent + '</b> β ' + box.getAttribute('data-detail');
});
});
// trigger chips (updating column)
allChips.forEach(function (chip) {
chip.addEventListener('click', function () {
allChips.forEach(function (c) { c.classList.remove('is-active'); });
chip.classList.add('is-active');
setDetail('update', chip.textContent + ' β ' + chip.getAttribute('data-detail'));
caption.innerHTML = '<b>' + chip.textContent + '</b> β ' + chip.getAttribute('data-detail');
});
});
function stopPlay() {
playTimers.forEach(function (t) { clearTimeout(t); });
playTimers = [];
}
function playSequence(steps, phase, playBtn) {
stopPlay();
clearActive();
var otherBtns = root.querySelectorAll('.lc-play-btn');
otherBtns.forEach(function (b) { b.disabled = true; });
steps.forEach(function (step, i) {
var t = setTimeout(function () {
clearActive();
var arrow = root.querySelector('[data-arrow="' + step.arrow + '"]');
var box = root.querySelector('[data-step="' + step.box + '"]');
if (arrow) arrow.classList.add('is-lit');
if (box) box.classList.add('is-active');
setDetail(phase, step.label + ' β ' + step.detail);
caption.innerHTML = '<b>Step ' + (i + 1) + ' of ' + steps.length + ': ' + step.label + '</b> β ' + step.detail;
if (phase === 'unmount' && i === steps.length - 1) {
var chip = root.querySelector('#lcNodeChip');
setTimeout(function () { chip.classList.add('is-gone'); }, 400);
}
if (i === steps.length - 1) {
setTimeout(function () {
otherBtns.forEach(function (b) { b.disabled = false; });
}, 900);
}
}, i * 1250);
playTimers.push(t);
});
}
root.querySelector('[data-play="mount"]').addEventListener('click', function (btn) {
playSequence([
{ arrow: 'mount-0', box: 'mount-0', label: 'constructor', detail: 'Set initial state and bind handlers.' },
{ arrow: 'mount-1', box: 'mount-1', label: 'render', detail: 'Describes the UI β no DOM writes yet.' },
{ arrow: 'mount-2', box: 'mount-2', label: 'React updates DOM and refs', detail: 'The description is committed to the real DOM.' },
{ arrow: 'mount-3', box: 'mount-3', label: 'componentDidMount', detail: 'DOM is live β safe to fetch data or start timers.' },
], 'mount', this);
});
root.querySelector('[data-play="update"]').addEventListener('click', function () {
var chip = root.querySelector('.lc-trigger-chip[data-trigger="state"]');
allChips.forEach(function (c) { c.classList.remove('is-active'); });
if (chip) chip.classList.add('is-active');
playSequence([
{ arrow: 'update-1', box: 'update-1', label: 'setState() called', detail: 'A state change is scheduled.' },
{ arrow: 'update-1', box: 'update-1', label: 'render', detail: 'React re-describes the UI with the new state.' },
{ arrow: 'update-2', box: 'update-2', label: 'React updates DOM and refs', detail: 'Only the changed parts of the DOM are patched.' },
{ arrow: 'update-3', box: 'update-3', label: 'componentDidUpdate', detail: 'DOM already reflects the change.' },
], 'update', this);
});
root.querySelector('[data-play="unmount"]').addEventListener('click', function () {
root.querySelector('#lcNodeChip').classList.remove('is-gone');
playSequence([
{ arrow: 'unmount-0', box: 'unmount-0', label: 'componentWillUnmount', detail: 'Clean up timers, subscriptions, and listeners β the component is about to disappear.' },
], 'unmount', this);
});
})();
</script>π¦ 1. Mounting (When the component is created and added to the screen)
Mounting means when a component is created and shown on the screen for the first time.
Mounting Lifecycle Flow (in order)
constructor β render β componentDidMount(Some methods exist in between, but these are the most important)
π§ Think of it like entering a movie theatre:
First, the lights and seats are prepared (setup before anything shows)
Then, the screen turns on (UI appears)
Finally, the movie starts playing (data loads, effects run)
Mapping to React Mounting π
π¬ Setup seats & lights β
constructor()πΊ Screen turns on β
render()βΆοΈ Movie starts β
componentDidMount()
Lifecycle methods used:
Method | What it does | Easy Explanation |
|---|---|---|
| Initializes state and binds methods | Setting things up before the component appears |
| Syncs state with props (rare) | Adjust state if props change before rendering |
| Returns JSX (UI) | Decides what appears on the screen |
| Runs after UI is visible | Best place to fetch API data |
β Example:
import React from "react";
class UserProfile extends React.Component {
constructor(props) {
super(props);
// Step 1: Setup initial state
this.state = {
name: "Loading..."
};
}
// Step 3: Called AFTER component appears on screen
componentDidMount() {
// Simulating API call
setTimeout(() => {
this.setState({ name: "Codeveda" });
}, 2000);
}
// Step 2: Shows UI on screen
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
export default UserProfile;π Key Takeaways
β Mounting = first time component appears
β render() is mandatory
β componentDidMount() is perfect for API calls
β constructor() is for initial setup only
π¨ 2. Updating (When the component changes and re-renders)
The updating phase happens when a component is already on the screen, but something changes.
This change can happen because:
State changes (
setState)Props change (new data from parent)
π§ Think of it like changing channels on a TV:
The TV is already ON
You press the remote button
The screen updates with a new channel
The TV doesnβt restart β only the content changes.
Updating Lifecycle Flow
Props / State Change
β
getDerivedStateFromProps()
β
shouldComponentUpdate()
β
render()
β
componentDidUpdate()Lifecycle Methods
Method | What it does | Easy Explanation |
|---|---|---|
| Initializes state and binds methods | Setting things up before the component appears |
| Syncs state with props (rare) | Adjust state if props change before rendering |
| Returns JSX (UI) | Decides what appears on the screen |
| Runs after UI is visible | Best place to fetch API data |
β Example
import React from "react";
class Counter extends React.Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 });
};
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
console.log("Count updated:", this.state.count);
}
}
render() {
return (
<div>
<h2>Count: {this.state.count}</h2>
<button onClick={this.increment}>Increase</button>
</div>
);
}
}
export default Counter;π Key Takeaways
β Updating happens when state or props change
β render() runs again
β componentDidUpdate() is good for side effects
β Avoid calling setState() blindly inside componentDidUpdate()
π₯ 3. Unmounting (When the component is removed from the screen)
The unmounting phase happens when a component is removed from the screen.
This usually happens when:
You navigate to another page
A condition becomes
falseA component is no longer needed
What does componentWillUnmount() do?
Purpose | Simple Explanation |
|---|---|
Cleanup | Remove timers, listeners, subscriptions |
Prevent memory leaks | Stop things running in background |
Final step | Component will never render again |
Lifecycle method used:
Method | What it does | Simple Explanation |
|---|---|---|
| Runs right before the component is removed. | Good place to clean up timers or stop API calls. |
β Example:
import React from "react";
class Timer extends React.Component {
componentDidMount() {
this.interval = setInterval(() => {
console.log("Timer running...");
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
console.log("Timer stopped");
}
render() {
return <h2>Timer is running</h2>;
}
}
export default Timer;π¨ Important Rules
β Do NOT call setState() here
β Do NOT fetch data here
β Only cleanup logic belongs here