π What is a Pure Component?
A Pure Component is a type of React component that only re-renders when necessary i.e., when its props or state change. It helps improve performance by preventing unnecessary re-renders.
β Think of it like a smart component that checks: βAre my inputs the same as before? If yes, I wonβt update!β
π§© Class vs Functional Pure Components
Class Component: Extend
React.PureComponentFunctional Component: Use
React.memo()
πΉ Class Pure Component:
import React, { PureComponent } from 'react';
class MyComponent extends PureComponent {
render() {
return <h2>Hello, {this.props.name}</h2>;
}
}PureComponentautomatically implementsshouldComponentUpdate()with a shallow comparison of props and state.Use this only for class-based components.
πΉ Functional Pure Component:
import React from 'react';
const MyComponent = React.memo((props) => {
return <h2>Hello, {props.name}</h2>;
});React.memo()is a Higher-Order Component that wraps functional components to add memoization.It does the same shallow comparison logic as
PureComponent.
π How Does It Work?
Pure components use a shallow comparison of props and state:
If no change is found β skips render
If change is found β re-renders
Shallow Comparison:
Compares primitive values (like strings, numbers) by value
Compares objects and arrays by reference, not content
π When to Use?
β Use Pure Components when:
You want to optimize performance
Props/state are simple and not deeply nested
Components re-render frequently without real changes
β Avoid if:
Youβre passing complex objects/arrays that often look same but have different references
You need custom logic to compare props/state
βοΈ Very Important
Why PureComponent/React.memo might still re-render even if data looks the same
Letβs say you have two objects like this:
const obj1 = { name: "Alex" };
const obj2 = { name: "Alex" };
console.log(obj1 === obj2); // falseEven though both objects have the same content, they are not equal in JavaScript because they are stored in different memory locations. This is called a reference comparison.
How this affects PureComponent or React.memo
If you pass a new object or array to a component every time it renders, React sees it as a "change" β even if the content is the same.
<MyComponent user={{ name: "Alex" }} />If this line runs again, a new object is created, so React thinks the props have changed and will re-render the component β even if the data is the same.
What to do instead?
β
FIX 1: Use useMemo (Best practice)
import React, { useState, useMemo }from"react";
import Child from"./Child";
functionParent() {
const [count, setCount] =useState(0);
// β
Object is created only once
const user = useMemo(() => {
return {name:"Alex" };
}, []);
return (
<div>
<button onClick={() => setCount(count+1)}> Count: {count} </button>
<Childuser = {user} />
</div>
);
}
export default Parent;β Why this works
useMemostores the object in memoryOn re-render, React reuses the same object
Reference does NOT change
React.memoskips re-renderβ
Child renderedlogs only once
β FIX 2: Use a variable outside render
import React, { useState } from "react";
import Child from "./Child";
// β
Created once (outside the component)
const user = { name: "Alex" };
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<Child user={user} />
</div>
);
}
export default Parent;β Why βvariable outside renderβ works
Key rule:
Code outside the component runs once, not on every render.
<div class="infographic-purecomp" id="pc">
<style>
.infographic-purecomp {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-deep: #1942a8;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--bad: #dc2626;
--bad-soft: #fde3e3;
--good: #16a34a;
--good-soft: #dcf5e3;
--radius: 8px;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 860px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
color: var(--ink);
font-family: var(--body-font);
}
.infographic-purecomp[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171f;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-deep: #8fb4ff;
--accent-soft: #20293f;
--line: #2c313d;
--bad-soft: #3a1414;
--good-soft: #103a1e;
}
.infographic-purecomp * { box-sizing: border-box; min-width: 0; }
.pc-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;
}
.pc-eyebrow::before { content: ""; width: 14px; height: 2px; background: var(--accent); display: inline-block; }
.pc-title {
font-family: var(--display-font); font-size: clamp(1.35rem, 4vw, 2rem); text-align: center;
margin: 0 0 0.5rem; line-height: 1.2; letter-spacing: -0.01em;
}
.pc-sub { color: var(--ink-soft); font-size: 0.95rem; text-align: center; margin: 0 0 1.5rem; max-width: 560px; margin-left: auto; margin-right: auto; }
/* --- parent state code block --- */
.pc-state-label {
text-align: center; font-family: var(--mono-font); font-size: 0.68rem; color: var(--ink-soft);
text-transform: uppercase; letter-spacing: 0.06em; margin: 0 0 0.5rem;
}
.pc-state-block {
background: #0d0d10; border: 1px solid #24252d; border-radius: var(--radius);
padding: 0.9rem 1.1rem; max-width: 340px; margin: 0 auto 1.6rem;
}
.pc-state-block code { font-family: var(--mono-font); font-size: 0.8rem; line-height: 1.85; color: #d4d4d8; white-space: pre; display: block; }
.tok-attr { color: #d19a66; }
.tok-str { color: #98c379; }
.tok-expr { color: #56b6c2; }
.tok-punc { color: #6b7280; }
.pc-state-line { border-radius: 3px; padding: 0 3px; margin: 0 -3px; transition: background 0.9s ease; }
.pc-state-line.is-lit { background: rgba(34,197,94,0.35); }
/* --- update buttons --- */
.pc-btn-row { display: flex; justify-content: center; gap: 0.7rem; flex-wrap: wrap; margin-bottom: 1.7rem; }
.pc-btn {
display: flex; align-items: center; gap: 0.5rem; font: inherit; font-weight: 600; font-size: 0.8rem;
font-family: var(--mono-font); color: #fff; background: var(--accent); border: none; border-radius: 100px;
padding: 0.6rem 1.1rem; cursor: pointer; box-shadow: 0 6px 16px -4px rgba(37,99,235,0.45);
transition: transform 0.15s, background 0.15s;
}
.pc-btn:hover { transform: translateY(-1px); }
.pc-btn:focus-visible { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.pc-btn.pc-secondary { background: var(--ink); box-shadow: 0 6px 16px -4px rgba(26,26,26,0.35); }
.infographic-purecomp[data-theme="dark"] .pc-btn.pc-secondary { background: #384254; }
/* --- child panels --- */
.pc-panels { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; margin-bottom: 1rem; }
@media (max-width: 600px) { .pc-panels { grid-template-columns: 1fr; } }
.pc-panel {
border: 1px solid var(--line); border-radius: var(--radius); padding: 1.1rem 1rem; background: var(--paper);
display: flex; flex-direction: column; align-items: center; gap: 0.5rem; text-align: center;
transition: background 0.4s ease, box-shadow 0.4s ease;
}
.pc-panel.pc-flash-good { background: var(--good-soft); box-shadow: 0 0 0 2px var(--good); }
.pc-panel.pc-flash-skip { background: var(--bad-soft); box-shadow: 0 0 0 2px var(--bad); }
.pc-panel-title { font-family: var(--display-font); font-size: 1.02rem; color: var(--ink); }
.pc-panel-sig {
font-family: var(--mono-font); font-size: 0.68rem; color: var(--ink-soft); background: var(--paper-alt);
border-radius: 5px; padding: 0.3rem 0.5rem;
}
.pc-panel-sig .tok-attr { color: #b5651d; }
.infographic-purecomp[data-theme="dark"] .pc-panel-sig .tok-attr { color: #d19a66; }
.pc-render-badge {
font-family: var(--mono-font); font-size: 0.68rem; font-weight: 700; color: var(--ink-soft);
border-radius: 100px; padding: 0.1rem 0.55rem;
}
.pc-verdict { font-size: 0.8rem; font-weight: 700; min-height: 1.4em; }
.pc-verdict.pc-verdict-good { color: var(--good); }
.pc-verdict.pc-verdict-bad { color: var(--bad); }
.pc-caption {
text-align: center; font-size: 0.9rem; color: var(--ink); min-height: 2.6em; display: flex;
align-items: center; justify-content: center; max-width: 580px; margin: 0.9rem auto 1.6rem; line-height: 1.45;
}
.pc-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: 660px; margin: 0 auto;
}
.pc-takeaway b { color: var(--accent-deep); }
.pc-takeaway code { font-family: var(--mono-font); }
@media (prefers-reduced-motion: reduce) {
.infographic-purecomp *, .infographic-purecomp *::before, .infographic-purecomp *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
<div class="pc-eyebrow">Performance</div>
<p class="pc-sub">Both children below only care about <code>username</code>. Update <code>cartCount</code> instead and see who still re-renders anyway.</p>
<p class="pc-state-label">parent state</p>
<div class="pc-state-block">
<code>{
<span class="pc-state-line" id="pcLineUsername"><span class="tok-attr">username</span><span class="tok-punc">:</span> <span class="tok-str">"<span id="pcUsernameVal">Raj</span>"</span><span class="tok-punc">,</span></span>
<span class="pc-state-line" id="pcLineCart"><span class="tok-attr">cartCount</span><span class="tok-punc">:</span> <span class="tok-expr" id="pcCartVal">0</span></span>
}</code>
</div>
<div class="pc-btn-row">
<button class="pc-btn pc-secondary" id="pcCartBtn" type="button">setState({ cartCount++ })</button>
<button class="pc-btn" id="pcNameBtn" type="button">setState({ username: "next name" })</button>
</div>
<div class="pc-panels">
<div class="pc-panel" id="pcClassPanel">
<span class="pc-panel-title">ChildA extends Component</span>
<span class="pc-panel-sig">props = { <span class="tok-attr">username</span> }</span>
<span class="pc-render-badge" id="pcClassRenders">renders: 0</span>
<div class="pc-verdict" id="pcClassVerdict"> </div>
</div>
<div class="pc-panel" id="pcPurePanel">
<span class="pc-panel-title">ChildB extends PureComponent</span>
<span class="pc-panel-sig">props = { <span class="tok-attr">username</span> }</span>
<span class="pc-render-badge" id="pcPureRenders">renders: 0</span>
<div class="pc-verdict" id="pcPureVerdict"> </div>
</div>
</div>
<p class="pc-caption" id="pcCaption">Both children receive the same <code>username</code> prop. Try updating cartCount first.</p>
</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('pc');
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 usernameValEl = root.querySelector('#pcUsernameVal');
var cartValEl = root.querySelector('#pcCartVal');
var lineUsername = root.querySelector('#pcLineUsername');
var lineCart = root.querySelector('#pcLineCart');
var classPanel = root.querySelector('#pcClassPanel');
var classRendersEl = root.querySelector('#pcClassRenders');
var classVerdictEl = root.querySelector('#pcClassVerdict');
var purePanel = root.querySelector('#pcPurePanel');
var pureRendersEl = root.querySelector('#pcPureRenders');
var pureVerdictEl = root.querySelector('#pcPureVerdict');
var cartBtn = root.querySelector('#pcCartBtn');
var nameBtn = root.querySelector('#pcNameBtn');
var caption = root.querySelector('#pcCaption');
var names = ['Raj', 'Priya', 'Karan', 'Meera', 'Dev'];
var nameIndex = 0;
var username = names[0];
var cartCount = 0;
var classRenders = 0;
var pureRenders = 0;
var pureLastUsername = username;
function flashLine(line) {
line.classList.remove('is-lit');
void line.offsetWidth;
line.classList.add('is-lit');
setTimeout(function () { line.classList.remove('is-lit'); }, 900);
}
function flashPanel(panel, kind) {
panel.classList.remove('pc-flash-good', 'pc-flash-skip');
void panel.offsetWidth;
panel.classList.add(kind);
setTimeout(function () { panel.classList.remove(kind); }, 700);
}
// Every setState call re-renders the parent, and by default every
// plain Component child re-renders with it β whether or not the
// prop it actually reads (`username`) is what changed.
function parentRerenders(usernameChanged, changeLabel) {
// ChildA (plain Component): no comparison, always re-renders.
classRenders += 1;
classRendersEl.textContent = 'renders: ' + classRenders;
classVerdictEl.textContent = 'π re-rendered (no comparison)';
classVerdictEl.className = 'pc-verdict pc-verdict-good';
flashPanel(classPanel, 'pc-flash-good');
// ChildB (PureComponent): shallow-compares its own props first.
if (usernameChanged) {
pureLastUsername = username;
pureRenders += 1;
pureRendersEl.textContent = 'renders: ' + pureRenders;
pureVerdictEl.textContent = 'β
username changed β re-rendered';
pureVerdictEl.className = 'pc-verdict pc-verdict-good';
flashPanel(purePanel, 'pc-flash-good');
caption.textContent = changeLabel + ' username actually changed, so both children re-rendered.';
} else {
pureVerdictEl.textContent = 'β username unchanged β skipped';
pureVerdictEl.className = 'pc-verdict pc-verdict-bad';
flashPanel(purePanel, 'pc-flash-skip');
caption.textContent = changeLabel + ' username didn\u2019t change, so ChildB (PureComponent) skipped rendering β but ChildA still re-rendered for no reason.';
}
}
cartBtn.addEventListener('click', function () {
cartCount += 1;
cartValEl.textContent = cartCount;
flashLine(lineCart);
parentRerenders(false, 'π cartCount changed β');
});
nameBtn.addEventListener('click', function () {
nameIndex = (nameIndex + 1) % names.length;
username = names[nameIndex];
usernameValEl.textContent = username;
flashLine(lineUsername);
parentRerenders(true, 'βοΈ username changed β');
});
})();
</script>What happens internally:
user object is created only once
Every render uses the same reference.
React compares:
previousUser===nextUser// trueReact.memo says: β βNo prop changeβ
Child does not re-render.