▶️ Live demo
Try it yourself — interact with the example below.
Loading demo…
🎯 What Is Derived State?
Imagine you have a shopping receipt:
WITHOUT DERIVED STATE (Manual Tracking):
┌─────────────────────────────────────────┐
│ 🧾 RECEIPT (Your App) │
│ │
│ Items: │
│ • Apple $1 │
│ • Banana $2 │
│ • Orange $3 │
│ │
│ ❌ Manual counters: │
│ Total items: 3 (I counted myself!) │
│ Total cost: $6 (I calculated myself!) │
│ Tax: $0.50 (I calculated myself!) │
│ │
│ Problems: │
│ • If I add an item, I must update ALL │
│ • Easy to forget one! │
│ • Numbers get out of sync! │
│ • Multiple sources of truth! │
└─────────────────────────────────────────┘
WITH DERIVED STATE (Auto-Calculated):
┌─────────────────────────────────────────┐
│ 🧾 RECEIPT (Your App) │
│ │
│ Items: │
│ • Apple $1 │
│ • Banana $2 │
│ • Orange $3 │
│ │
│ ✅ Auto-calculated: │
│ Total items: items.length = 3 │
│ Total cost: items.reduce(...) = $6 │
│ Tax: total * 0.08 = $0.48 │
│ │
│ Benefits: │
│ • Add item → ALL numbers auto-update! │
│ • Never out of sync! │
│ • One source of truth: the items array!│
│ • No extra state needed! │
└─────────────────────────────────────────┘
THE DERIVED STATE DECISION FLOWCHART:
┌─────────────────────────────────────────┐
│ Need to show a number or text? │
│ ↓ │
│ Can it be calculated from existing │
│ state or props? │
│ → NO → Create useState! │
│ → YES → DERIVE IT! Don't store it! │
│ → Calculate in render │
│ → React recalculates every render │
│ → ALWAYS up-to-date! ✓ │
└─────────────────────────────────────────┘
⚠️ The Big Problem: "Storing What You Can Calculate"
// ==========================================
// THE "MANUAL COUNTERS" TRAP
// ==========================================
// ❌ WRONG: Creating unnecessary state for things you can calculate!
function BadApp() {
const [items, setItems] = useState([]);
// ❌ WRONG: These are DERIVED values, not independent state!
const [numItems, setNumItems] = useState(0); // ← Don't store this!
const [numPacked, setNumPacked] = useState(0); // ← Don't store this!
const [percentage, setPercentage] = useState(0); // ← Don't store this!
function handleAddItems(item) {
setItems(prev => [...prev, item]);
// ❌ MANUAL SYNC: Must remember to update this too!
setNumItems(prev => prev + 1); // ← Easy to forget!
}
function handleToggleItem(id) {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, packed: !item.packed } : item
));
// ❌ MANUAL SYNC: Must recalculate packed count!
// But where? In handleToggle? What about handleDelete?
// This gets MESSY! 😱
}
function handleDeleteItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
// ❌ MANUAL SYNC: Must decrement numItems!
setNumItems(prev => prev - 1); // ← Another place to update!
}
return (
<div className="app">
<PackingList items={items} />
<Stats numItems={numItems} numPacked={numPacked} percentage={percentage} />
</div>
);
}
// Problems:
// 1. THREE pieces of state for ONE piece of data!
// 2. Must manually sync every time items changes!
// 3. Easy to forget setNumItems in one function!
// 4. Multiple re-renders (setItems + setNumItems)
// 5. Bugs when state gets out of sync!
// 6. Hard to maintain!
// ==========================================
// THE SOLUTION: Calculate From Items!
// ==========================================
// ✅ CORRECT: Derive values from the items array!
function GoodApp() {
// 🏠 HOME: Only ONE piece of state needed!
const [items, setItems] = useState([]);
function handleAddItems(item) {
setItems(prev => [...prev, item]);
// ✅ No need to update anything else!
// numItems will auto-recalculate on next render!
}
function handleToggleItem(id) {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, packed: !item.packed } : item
));
// ✅ No need to update numPacked!
// It will auto-recalculate!
}
function handleDeleteItem(id) {
setItems(prev => prev.filter(item => item.id !== id));
// ✅ No need to update anything!
// All derived values auto-update!
}
return (
<div className="app">
<PackingList items={items} />
{/* ✅ Pass the SOURCE data, not derived values! */}
<Stats items={items} />
</div>
);
}
// Stats derives everything from items!
function Stats({ items }) {
// ✅ DERIVED: Calculate from props!
const numItems = items.length; // ← Derived!
const numPacked = items.filter(item => item.packed).length; // ← Derived!
const percentage = numItems > 0
? Math.round((numPacked / numItems) * 100)
: 0; // ← Derived!
// ✅ EARLY RETURN: Handle empty state cleanly!
if (!numItems) {
return (
<footer className="stats">
<em>Start adding some items to your packing list 🚀</em>
</footer>
);
}
return (
<footer className="stats">
<em>
{percentage === 100
? "You got everything! Ready to go! ✈️"
: `You have ${numItems} items on your list, and you already packed ${numPacked} (${percentage}%)`
}
</em>
</footer>
);
}
// What happens when user adds "Socks":
//
// 1. User clicks "Add" in Form
// 2. App's handleAddItems runs
// 3. setItems updates items array
// 4. App re-renders
// 5. Stats receives new items prop
// 6. Stats CALCULATES:
// numItems = items.length = 1
// numPacked = items.filter(i => i.packed).length = 0
// percentage = 0
// 7. Stats renders: "You have 1 items... packed 0 (0%)"
//
// User toggles checkbox on "Socks":
// 1. App's handleToggleItem runs
// 2. setItems updates items array (Socks now packed: true)
// 3. App re-renders
// 4. Stats receives new items prop
// 5. Stats CALCULATES:
// numItems = 1 (still)
// numPacked = 1 (Socks is packed!)
// percentage = 100
// 6. Stats renders: "You got everything! Ready to go! ✈️"
//
// Why this works:
// → ONE source of truth: items array ✓
// → No manual syncing needed! ✓
// → Always up-to-date! ✓
// → Less state = less bugs! ✓
📋 Complete Visual Examples
Create file: derived-state.js
// ==========================================
// DERIVED STATE - Complete Guide
// ==========================================
/*
THE DERIVED STATE DECISION FLOWCHART:
┌─────────────────────────────────────────┐
│ Need to show a number or text? │
│ ↓ │
│ Can it be calculated from existing │
│ state or props? │
│ → NO → Create useState! │
│ → YES → DERIVE IT! │
│ → Calculate in component body │
│ → React recalculates every render │
│ → ALWAYS correct! No syncing! │
│ │
│ ❌ WRONG: const [count, setCount] = │
│ useState(items.length) │
│ ✅ CORRECT: const count = items.length │
└─────────────────────────────────────────┘
DERIVED vs STORED STATE:
┌─────────────────────────────────────────┐
│ STORED STATE (useState) │
│ • Independent data │
│ • User input, API data │
│ • Example: items array, form inputs │
│ │
│ DERIVED STATE (Calculation) │
│ • Computed from stored state │
│ • Always in sync automatically │
│ • Example: count, total, percentage │
│ • NEVER use useState for derived! │
└─────────────────────────────────────────┘
*/
// ==========================================
// EXAMPLE 1: The Wrong Way (Stored Derived State)
// ==========================================
import { useState } from 'react';
function BadStats({ items }) {
// ❌ WRONG: Creating state for derived values!
const [numItems, setNumItems] = useState(items.length);
const [numPacked, setNumPacked] = useState(0);
// ❌ WRONG: Must manually update in every function!
// What if we forget to update numPacked in handleToggle?
// What if we add a new feature and forget to sync?
// BUG CITY! 🐛🐛🐛
return (
<footer>
<em>You have {numItems} items, packed {numPacked}</em>
</footer>
);
}
// Visual Flow:
// Initial: items = []
// numItems = 0 (manually set)
// numPacked = 0 (manually set)
//
// User adds 3 items:
// items = [A, B, C]
// ❌ Forgot to call setNumItems(3)!
// numItems still shows 0! 😢
// OUT OF SYNC!
//
// Why this is broken:
// → Two sources of truth! ✗
// → Manual syncing required! ✗
// → Easy to forget updates! ✗
// → Bugs when out of sync! ✗
// ==========================================
// EXAMPLE 2: The Right Way (Derived State)
// ==========================================
function GoodStats({ items }) {
// ✅ CORRECT: Calculate from props!
// These are NOT state! They are computed values!
const numItems = items.length; // ← Derived!
const numPacked = items.filter(item => item.packed).length; // ← Derived!
const percentage = numItems > 0
? Math.round((numPacked / numItems) * 100)
: 0; // ← Derived!
// ✅ EARLY RETURN: Handle edge case first!
if (!numItems) {
return (
<footer className="stats">
<em>Start adding some items to your packing list 🚀</em>
</footer>
);
}
return (
<footer className="stats">
<em>
{percentage === 100
? "You got everything! Ready to go! ✈️"
: `You have ${numItems} items on your list, and you already packed ${numPacked} (${percentage}%)`
}
</em>
</footer>
);
}
// Visual Flow:
// Initial: items = []
// numItems = [].length = 0
// numPacked = [].filter(...).length = 0
// percentage = 0
// Early return: "Start adding items..."
//
// User adds 3 items:
// items = [A, B, C]
// numItems = 3 (auto-calculated!)
// numPacked = 0 (auto-calculated!)
// percentage = 0
// Renders: "You have 3 items... packed 0 (0%)"
//
// User packs 2 items:
// items = [A✅, B✅, C]
// numItems = 3 (auto-calculated!)
// numPacked = 2 (auto-calculated!)
// percentage = 67
// Renders: "You have 3 items... packed 2 (67%)"
//
// User packs last item:
// items = [A✅, B✅, C✅]
// numItems = 3
// numPacked = 3
// percentage = 100
// Renders: "You got everything! Ready to go! ✈️"
//
// Why this works:
// → ONE source of truth! ✓
// → Auto-calculated every render! ✓
// → Never out of sync! ✓
// → Less code, less bugs! ✓
// ==========================================
// EXAMPLE 3: Common Derived State Patterns
// ==========================================
function DerivedPatterns({ items, users, cart }) {
// COUNTING
const totalItems = items.length; // Count array
const completedCount = items.filter(i => i.done).length; // Count matching
const activeUsers = users.filter(u => u.isOnline).length; // Count filtered
// SUMMING
const totalPrice = cart.reduce((sum, item) => sum + item.price, 0); // Sum values
const avgRating = reviews.length > 0
? reviews.reduce((s, r) => s + r.stars, 0) / reviews.length
: 0; // Average
// BOOLEAN FLAGS
const isEmpty = items.length === 0; // Empty check
const isAllPacked = items.length > 0 && items.every(i => i.packed); // All match
const hasExpensiveItem = cart.some(item => item.price > 100); // Any match
// STRING FORMATTING
const itemText = items.length === 1 ? "item" : "items"; // Pluralize
const statusMessage = isAllPacked ? "Done!" : "Keep packing!"; // Conditional text
// SORTING/TRANSFORMING (for display only)
const sortedItems = [...items].sort((a, b) => a.name.localeCompare(b.name)); // Sorted copy
const itemNames = items.map(item => item.name).join(", "); // String join
return (
<div>
<p>Total: {totalItems} {itemText}</p>
<p>Completed: {completedCount}</p>
<p>Total Price: ${totalPrice}</p>
<p>Status: {statusMessage}</p>
</div>
);
}
// ==========================================
// EXAMPLE 4: The Complete Far Away Stats
// ==========================================
function App() {
const [items, setItems] = useState([]);
// ... handlers ...
return (
<div className="app">
<Logo />
<Form onAddItems={handleAddItems} />
<PackingList
items={items}
onDeleteItem={handleDeleteItem}
onToggleItem={handleToggleItem}
/>
{/* ✅ Pass ONLY the source data! */}
<Stats items={items} />
</div>
);
}
function Stats({ items }) {
// ✅ DERIVED: All calculated from items!
const numItems = items.length;
const numPacked = items.filter(item => item.packed).length;
const percentage = numItems > 0
? Math.round((numPacked / numItems) * 100)
: 0;
// ✅ EARLY RETURN: Empty list
if (!numItems) {
return (
<footer className="stats">
<em>Start adding some items to your packing list 🚀</em>
</footer>
);
}
// ✅ EARLY RETURN: All packed (100%)
// Or use ternary in JSX
return (
<footer className="stats">
<em>
{percentage === 100
? "You got everything! Ready to go! ✈️"
: `You have ${numItems} items on your list, and you already packed ${numPacked} (${percentage}%)`
}
</em>
</footer>
);
}
// Visual Flow of Stats:
// 1. App passes items={[]} to Stats
// 2. Stats calculates: numItems = 0
// 3. Early return: "Start adding items..."
//
// 4. User adds 3 items
// 5. App passes items={[A, B, C]} to Stats
// 6. Stats calculates:
// numItems = 3
// numPacked = 0
// percentage = 0
// 7. Renders: "You have 3 items... packed 0 (0%)"
//
// 8. User packs all 3
// 9. App passes items={[A✅, B✅, C✅]} to Stats
// 10. Stats calculates:
// numItems = 3
// numPacked = 3
// percentage = 100
// 11. Renders: "You got everything! Ready to go! ✈️"
// ==========================================
// EXAMPLE 5: Early Returns for Clean Code
// ==========================================
function EarlyReturnDemo({ items }) {
// ✅ EARLY RETURN: Handle empty state first!
if (items.length === 0) {
return <EmptyState />;
}
// ✅ EARLY RETURN: Handle loading state!
if (items === null) {
return <LoadingSpinner />;
}
// ✅ EARLY RETURN: Handle error state!
if (items === undefined) {
return <ErrorMessage />;
}
// Main logic only runs when data is ready!
const numItems = items.length;
const numPacked = items.filter(i => i.packed).length;
return (
<div>
<p>Items: {numItems}</p>
<p>Packed: {numPacked}</p>
</div>
);
}
// Without early returns (messy):
function MessyDemo({ items }) {
return (
<div>
{items.length === 0 ? (
<EmptyState />
) : items === null ? (
<LoadingSpinner />
) : (
<div>
<p>Items: {items.length}</p>
</div>
)}
</div>
);
// Hard to read! Nested ternaries!
}
// ==========================================
// EXAMPLE 6: The Decision Flowchart in Code
// ==========================================
function DecisionFlowchartDemo() {
const [items, setItems] = useState([]);
// Question 1: Do we need to show stats?
// → YES!
// Question 2: Can stats be derived from items?
// → YES! count, filter, math!
// Question 3: Should we store in useState?
// → NO! Derived state needs NO useState!
// Question 4: Where to calculate?
// → In Stats component (needs items anyway)
// Question 5: What about empty list?
// → Early return! Clean and readable!
return (
<div>
<Stats items={items} />
</div>
);
}
🚀 Interactive React Usage Examples
Complete React File: DerivedStateMasterClass.jsx
import React, { useState } from 'react';
// ==========================================
// INTERACTIVE DERIVED STATE DEMO
// ==========================================
function DerivedStateMasterClass() {
const [activeDemo, setActiveDemo] = useState('flowchart');
const demos = {
flowchart: { title: 'Decision Flowchart', component: <FlowchartDemo /> },
wrong: { title: 'Wrong (Stored State)', component: <WrongWayDemo /> },
right: { title: 'Right (Derived)', component: <RightWayDemo /> },
patterns: { title: 'Common Patterns', component: <PatternsDemo /> },
complete: { title: 'Complete App', component: <CompleteAppDemo /> }
};
return (
<div className="container py-4">
<header className="bg-dark text-white p-4 rounded mb-4">
<h1 className="mb-0">🧮 Derived State</h1>
<p className="mb-0 opacity-75">Calculate, Don't Store!</p>
</header>
<nav className="d-flex gap-2 flex-wrap mb-4">
{Object.entries(demos).map(([key, { title }]) => (
<button
key={key}
onClick={() => setActiveDemo(key)}
className={`btn ${activeDemo === key ? 'btn-primary' : 'btn-outline-secondary'}`}
>
{title}
</button>
))}
</nav>
<main className="bg-light p-4 rounded min-vh-50">
{demos[activeDemo].component}
</main>
</div>
);
}
// ==========================================
// DEMO 1: The Decision Flowchart
// ==========================================
function FlowchartDemo() {
const [step, setStep] = useState(1);
const steps = [
{ num: 1, title: 'Need a Value?', question: 'Do you need to show a number or text?', code: '// Any computed value?', visual: '📊', desc: 'Could be count, total, percentage...' },
{ num: 2, title: 'Can It Derive?', question: 'Can you calculate it from existing state/props?', code: 'const total = items.length;', visual: '🧮', desc: 'If yes, DERIVE IT!' },
{ num: 3, title: 'No useState!', question: 'Are you tempted to use useState?', code: '// ❌ const [count, setCount] = useState(0)', visual: '🚫', desc: 'DON\'T store derived values!' },
{ num: 4, title: 'Calculate!', question: 'Compute in component body!', code: 'const count = items.length;', visual: '✅', desc: 'Simple variable, no useState!' },
{ num: 5, title: 'Auto-Update!', question: 'Does it update when source changes?', code: '// React recalculates on every render!', visual: '🔄', desc: 'Always in sync! No manual updates!' }
];
const current = steps[step - 1];
return (
<div>
<h2>The Derived State Flowchart 🧭</h2>
<div className="d-flex gap-2 justify-content-center mb-4">
{steps.map(s => (
<button
key={s.num}
onClick={() => setStep(s.num)}
className={`btn rounded-circle ${step >= s.num ? 'btn-primary' : 'btn-outline-secondary'}`}
style={{ width: '60px', height: '60px', fontSize: '24px' }}
>
{s.num}
</button>
))}
</div>
<div className="card border-primary">
<div className="card-body">
<h3 className="card-title text-primary">Step {current.num}: {current.title}</h3>
<p className="fs-5 fw-bold">{current.question}</p>
<div className="row">
<div className="col-md-8">
<pre className="bg-dark text-light p-3 rounded">{current.code}</pre>
<p className="fs-5">{current.desc}</p>
</div>
<div className="col-md-4 d-flex align-items-center justify-content-center">
<span className="display-1">{current.visual}</span>
</div>
</div>
</div>
</div>
<div className="alert alert-success mt-3">
<h4>🧠 Memory Trick:</h4>
<p>"If you can calculate it, don't store it! Let React do the math!"</p>
</div>
</div>
);
}
// ==========================================
// DEMO 2: Wrong Way (Stored State)
// ==========================================
function WrongWayDemo() {
const [items, setItems] = useState([]);
const [numItems, setNumItems] = useState(0);
const [syncIssues, setSyncIssues] = useState([]);
function handleAdd() {
const newItem = { id: Date.now(), name: `Item ${items.length + 1}`, packed: false };
setItems(prev => [...prev, newItem]);
// ✅ Correctly synced here
setNumItems(prev => prev + 1);
}
function handleDeleteBroken() {
// ❌ BUG: Forgot to update numItems!
setItems(prev => prev.slice(0, -1));
// setNumItems is MISSING! Out of sync!
setSyncIssues(prev => [...prev, "Deleted item but forgot to update count!"]);
}
function handleDeleteFixed() {
setItems(prev => prev.slice(0, -1));
setNumItems(prev => prev - 1);
}
return (
<div>
<h2>Wrong Way: Stored State ❌</h2>
<div className="alert alert-danger">
<h4>Manual syncing is error-prone!</h4>
</div>
<div className="row">
<div className="col-md-6">
<div className="card">
<div className="card-body">
<h5 className="card-title">State</h5>
<p>items.length: {items.length}</p>
<p>numItems (stored): {numItems}</p>
<p className={items.length !== numItems ? 'text-danger fw-bold' : 'text-success'}>
{items.length !== numItems ? '❌ OUT OF SYNC!' : '✅ In sync'}
</p>
</div>
</div>
</div>
<div className="col-md-6">
<div className="btn-group mb-3">
<button onClick={handleAdd} className="btn btn-success">Add Item</button>
<button onClick={handleDeleteBroken} className="btn btn-warning">Delete (Broken)</button>
<button onClick={handleDeleteFixed} className="btn btn-info">Delete (Fixed)</button>
</div>
{syncIssues.length > 0 && (
<div className="alert alert-warning">
<h6>Sync Issues:</h6>
<ul className="mb-0">
{syncIssues.map((issue, i) => <li key={i}>{issue}</li>)}
</ul>
</div>
)}
</div>
</div>
</div>
);
}
// ==========================================
// DEMO 3: Right Way (Derived)
// ==========================================
function RightWayDemo() {
const [items, setItems] = useState([]);
// ✅ DERIVED: Auto-calculated!
const numItems = items.length;
const numPacked = items.filter(i => i.packed).length;
const percentage = numItems > 0 ? Math.round((numPacked / numItems) * 100) : 0;
function handleAdd() {
const newItem = { id: Date.now(), name: `Item ${items.length + 1}`, packed: false };
setItems(prev => [...prev, newItem]);
// ✅ Nothing else to update! Derived values auto-calculate!
}
function handleToggle(id) {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, packed: !item.packed } : item
));
}
function handleDelete() {
setItems(prev => prev.slice(0, -1));
// ✅ Nothing else to update!
}
return (
<div>
<h2>Right Way: Derived State ✅</h2>
<div className="alert alert-success">
<h4>Values auto-calculate! Never out of sync!</h4>
</div>
<div className="btn-group mb-3">
<button onClick={handleAdd} className="btn btn-success">Add</button>
<button onClick={handleDelete} className="btn btn-danger">Delete</button>
</div>
<div className="row">
<div className="col-md-4">
<div className="card bg-primary text-white">
<div className="card-body text-center">
<h3 className="display-4">{numItems}</h3>
<p className="mb-0">Total Items</p>
<small>items.length</small>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card bg-success text-white">
<div className="card-body text-center">
<h3 className="display-4">{numPacked}</h3>
<p className="mb-0">Packed</p>
<small>{`items.filter(i => i.packed).length`}</small>
</div>
</div>
</div>
<div className="col-md-4">
<div className="card bg-info text-white">
<div className="card-body text-center">
<h3 className="display-4">{percentage}%</h3>
<p className="mb-0">Complete</p>
<small>Math.round((packed/total)*100)</small>
</div>
</div>
</div>
</div>
<div className="mt-3">
{items.map(item => (
<div key={item.id} className="form-check">
<input
className="form-check-input"
type="checkbox"
checked={item.packed}
onChange={() => handleToggle(item.id)}
/>
<label className={`form-check-label ${item.packed ? 'text-decoration-line-through' : ''}`}>
{item.name}
</label>
</div>
))}
</div>
</div>
);
}
// ==========================================
// DEMO 4: Common Patterns
// ==========================================
function PatternsDemo() {
const items = [
{ id: 1, name: "Apple", price: 1, category: "fruit", inStock: true },
{ id: 2, name: "Bread", price: 3, category: "bakery", inStock: true },
{ id: 3, name: "Milk", price: 2, category: "dairy", inStock: false },
{ id: 4, name: "Eggs", price: 4, category: "dairy", inStock: true },
];
const totalPrice = items.reduce((sum, item) => sum + item.price, 0);
const avgPrice = items.length > 0 ? totalPrice / items.length : 0;
const inStockCount = items.filter(i => i.inStock).length;
const allInStock = items.length > 0 && items.every(i => i.inStock);
const hasExpensive = items.some(i => i.price > 3);
const dairyItems = items.filter(i => i.category === "dairy").map(i => i.name).join(", ");
return (
<div>
<h2>Common Derived Patterns 📊</h2>
<div className="alert alert-info">
<h4>All calculated from ONE items array!</h4>
</div>
<div className="row">
<div className="col-md-6">
<div className="card mb-3">
<div className="card-header bg-primary text-white">Counting</div>
<div className="card-body">
<p><strong>Total:</strong> {items.length} items</p>
<p><strong>In Stock:</strong> {inStockCount} items</p>
<p><strong>Out of Stock:</strong> {items.length - inStockCount} items</p>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card mb-3">
<div className="card-header bg-success text-white">Summing</div>
<div className="card-body">
<p><strong>Total Price:</strong> ${totalPrice}</p>
<p><strong>Average Price:</strong> ${avgPrice.toFixed(2)}</p>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card mb-3">
<div className="card-header bg-warning">Boolean Flags</div>
<div className="card-body">
<p><strong>All in stock?</strong> {allInStock ? "Yes ✅" : "No ❌"}</p>
<p><strong>Has expensive item?</strong> {hasExpensive ? "Yes ✅" : "No ❌"}</p>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card mb-3">
<div className="card-header bg-info text-white">Transforming</div>
<div className="card-body">
<p><strong>Dairy items:</strong> {dairyItems}</p>
</div>
</div>
</div>
</div>
</div>
);
}
// ==========================================
// DEMO 5: Complete Far Away App
// ==========================================
function CompleteAppDemo() {
const [items, setItems] = useState([
{ id: 1, description: "Passports", quantity: 2, packed: false },
{ id: 2, description: "Socks", quantity: 6, packed: false },
{ id: 3, description: "Charger", quantity: 1, packed: true },
]);
function handleToggle(id) {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, packed: !item.packed } : item
));
}
function handleDelete(id) {
setItems(prev => prev.filter(item => item.id !== id));
}
const numItems = items.length;
const numPacked = items.filter(i => i.packed).length;
const percentage = numItems > 0 ? Math.round((numPacked / numItems) * 100) : 0;
return (
<div>
<h2>Complete Far Away App 🌴</h2>
<div className="card shadow">
<div className="card-header bg-dark text-white text-center">
<h1 className="h3 mb-0">🌴 Far Away 💼</h1>
</div>
<div className="card-body">
<ul className="list-group">
{items.map(item => (
<li key={item.id} className={`list-group-item d-flex align-items-center ${item.packed ? 'list-group-item-success' : ''}`}>
<input
className="form-check-input me-3"
type="checkbox"
checked={item.packed}
onChange={() => handleToggle(item.id)}
/>
<span className={`flex-grow-1 ${item.packed ? 'text-decoration-line-through text-muted' : ''}`}>
{item.quantity} {item.description}
</span>
<button onClick={() => handleDelete(item.id)} className="btn btn-sm btn-danger">
❌
</button>
</li>
))}
</ul>
</div>
<div className="card-footer bg-primary text-white text-center">
{numItems === 0 ? (
<em>Start adding some items to your packing list 🚀</em>
) : percentage === 100 ? (
<em>You got everything! Ready to go! ✈️</em>
) : (
<em>💼 You have {numItems} items, packed {numPacked} ({percentage}%)</em>
)}
</div>
</div>
</div>
);
}
export default DerivedStateMasterClass;
🧠 Memory Aids for Poor Logic Thinking
The "Receipt Calculator" Analogy
┌─────────────────────────────────────────────────┐
│ DERIVED STATE = RECEIPT CALCULATOR │
│ │
│ You buy items at a store: │
│ • Apple $1 │
│ • Banana $2 │
│ • Orange $3 │
│ │
│ ❌ WRONG: Cashier counts manually on paper │
│ ┌─────────────────────────────────────────┐ │
│ │ Subtotal: $6 (I wrote this down) │ │
│ │ Tax: $0.50 (I calculated this) │ │
│ │ Total: $6.50 (I added these up) │ │
│ │ │ │
│ │ ❌ If I add a cookie, I must update │ │
│ │ ALL three numbers! Easy to forget! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ✅ CORRECT: Cash register auto-calculates │
│ ┌─────────────────────────────────────────┐ │
│ │ Items: Apple $1, Banana $2, Orange $3 │ │
│ │ │ │
│ │ Subtotal: $6 (auto: 1+2+3) │ │
│ │ Tax: $0.52 (auto: 6 * 0.0875) │ │
│ │ Total: $6.52 (auto: 6 + 0.52) │ │
│ │ │ │
│ │ ✅ Add a cookie? ALL numbers update │ │
│ │ automatically! No manual work! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Memory Trick: │
│ "The cash register doesn't 'remember' the │
│ total — it CALCULATES it from the items! │
│ Derived state is your cash register!" │
└─────────────────────────────────────────────────┘
The "Shadow" Analogy
┌─────────────────────────────────────────────────┐
│ DERIVED STATE = YOUR SHADOW │
│ │
│ You (State) walk in the sun: │
│ ┌─────────────────────────────────────────┐ │
│ │ │ │
│ │ 👤 YOU (State) │ │
│ │ items = [A, B, C] │ │
│ │ │ │
│ │ ↓ │ │
│ │ 🖤 SHADOW (Derived State) │ │
│ │ count = 3, packed = 1, % = 33 │ │
│ │ │ │
│ │ The shadow is NOT a separate person! │ │
│ │ It's calculated from your position! │ │
│ │ │ │
│ │ You move → Shadow moves automatically! │ │
│ │ You jump → Shadow jumps automatically! │ │
│ │ │ │
│ │ ❌ WRONG: Try to move shadow separately │ │
│ │ ✅ CORRECT: Move yourself, shadow follows│ │
│ └─────────────────────────────────────────┘ │
│ │
│ Memory Trick: │
│ "Derived state is your shadow — it follows │
│ you automatically! You don't 'store' your │
│ shadow, it's just always there!" │
└─────────────────────────────────────────────────┘
The "Recipe Scaling" Analogy
┌─────────────────────────────────────────────────┐
│ DERIVED STATE = RECIPE SCALING │
│ │
│ Original recipe serves 4 people: │
│ ┌─────────────────────────────────────────┐ │
│ │ 🍪 COOKIE RECIPE │ │
│ │ Flour: 2 cups │ │
│ │ Sugar: 1 cup │ │
│ │ Butter: 0.5 cup │ │
│ │ Serves: 4 │ │
│ └─────────────────────────────────────────┘ │
│ │
│ You need to serve 8 people (double): │
│ │
│ ❌ WRONG: Write a whole new recipe! │
│ ┌─────────────────────────────────────────┐ │
│ │ 🍪 NEW RECIPE (waste of paper!) │ │
│ │ Flour: 4 cups │ │
│ │ Sugar: 2 cups │ │
│ │ Butter: 1 cup │ │
│ │ Serves: 8 │ │
│ │ │ │
│ │ ❌ Now you have TWO recipes to maintain! │ │
│ │ ❌ Change original? Must update copy too! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ✅ CORRECT: Scale from original! │
│ ┌─────────────────────────────────────────┐ │
│ │ 🍪 ORIGINAL RECIPE (one source!) │ │
│ │ Flour: 2 cups │ │
│ │ Sugar: 1 cup │ │
│ │ Butter: 0.5 cup │ │
│ │ Serves: 4 │ │
│ │ │ │
│ │ For 8 people: multiply everything × 2 │ │
│ │ Flour: 2 × 2 = 4 cups (derived!) │ │
│ │ Sugar: 1 × 2 = 2 cups (derived!) │ │
│ │ Butter: 0.5 × 2 = 1 cup (derived!) │ │
│ │ │ │
│ │ ✅ Original recipe never changes! │ │
│ │ ✅ For 12 people? Just multiply × 3! │ │
│ │ ✅ One source of truth! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ Memory Trick: │
│ "Derived state is like scaling a recipe — you │
│ don't rewrite the whole thing, you just │
│ calculate from the original!" │
└─────────────────────────────────────────────────┘
The "Early Return" Analogy
┌─────────────────────────────────────────────────┐
│ EARLY RETURN = SECURITY CHECKPOINT │
│ │
│ You're entering a building: │
│ │
│ ❌ WRONG: Go all the way in, then check │
│ ┌─────────────────────────────────────────┐ │
│ │ 🚶 Walk to elevator │ │
│ │ 🚶 Press floor button │ │
│ │ 🚶 Walk to office door │ │
│ │ 🚶 Knock │ │
│ │ 🚪 "Sorry, building is closed!" │ │
│ │ 🚶 Walk all the way back out! │ │
│ │ │ │
│ │ ❌ Wasted all that effort! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ ✅ CORRECT: Check at the door first! │
│ ┌─────────────────────────────────────────┐ │
│ │ 🛂 SECURITY GUARD AT ENTRANCE │ │
│ │ │ │
│ │ "Building closed today." │ │
│ │ → STOP! Go home! (early return) │ │
│ │ │ │
│ │ ✅ No wasted effort! │ │
│ │ ✅ Clear and immediate! │ │
│ └─────────────────────────────────────────┘ │
│ │
│ In code: │
│ if (!items.length) return <EmptyState />; │
│ // Don't calculate, don't render stats, │
│ // just show the empty message and exit! │
│ │
│ Memory Trick: │
│ "Early return is like a security guard — if │
│ you're not allowed in, they stop you at the │
│ door, not after you've walked all the way!" │
└─────────────────────────────────────────────────┘
🎓 Practice Exercises
Exercise 1: Identify Derived vs Stored State
Look at this code. Which variables should be derived?
function App() {
const [users, setUsers] = useState([]);
const [userCount, setUserCount] = useState(0); // Derived or Stored?
const [activeUsers, setActiveUsers] = useState([]); // Derived or Stored?
const [searchQuery, setSearchQuery] = useState(""); // Derived or Stored?
const [filteredUsers, setFilteredUsers] = useState([]); // Derived or Stored?
// ...
}
Questions:
userCount→ Derived!users.lengthactiveUsers→ Derived!users.filter(u => u.isActive)searchQuery→ Stored! User inputfilteredUsers→ Derived!users.filter(u => u.name.includes(searchQuery))
Solution:
function App() {
const [users, setUsers] = useState([]);
const [searchQuery, setSearchQuery] = useState(""); // ← STORED: user input
// ✅ DERIVED: Calculate from state!
const userCount = users.length;
const activeUsers = users.filter(u => u.isActive);
const filteredUsers = users.filter(u =>
u.name.toLowerCase().includes(searchQuery.toLowerCase())
);
return (
<div>
<p>Total: {userCount}</p>
<p>Active: {activeUsers.length}</p>
<UserList users={filteredUsers} />
</div>
);
}
Exercise 2: Fix the Stats Component
This component has a bug. It stores derived state. Fix it!
// ❌ BROKEN: Storing derived values
function Stats({ items }) {
const [numItems, setNumItems] = useState(items.length);
const [numPacked, setNumPacked] = useState(0);
// ❌ Must manually update everywhere!
useEffect(() => {
setNumItems(items.length);
setNumPacked(items.filter(i => i.packed).length);
}, [items]);
const percentage = Math.round((numPacked / numItems) * 100);
return (
<footer>
<em>You have {numItems} items, packed {numPacked} ({percentage}%)</em>
</footer>
);
}
Solution:
// ✅ FIXED: Derived values, no useState!
function Stats({ items }) {
const numItems = items.length; // ← Derived!
const numPacked = items.filter(i => i.packed).length; // ← Derived!
const percentage = numItems > 0
? Math.round((numPacked / numItems) * 100)
: 0; // ← Derived!
// ✅ Early return for empty list
if (!numItems) {
return <footer><em>Start adding items! 🚀</em></footer>;
}
return (
<footer>
<em>
{percentage === 100
? "You got everything! Ready to go! ✈️"
: `You have ${numItems} items, packed ${numPacked} (${percentage}%)`
}
</em>
</footer>
);
}
Exercise 3: Build a Shopping Cart Stats
Build a stats component for a shopping cart:
Requirements:
- Show total items
- Show total price
- Show if cart is empty
- Show "Free shipping!" if total > $50
Solution:
function CartStats({ cart }) {
// ✅ DERIVED: All calculated from cart!
const itemCount = cart.length;
const totalPrice = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
const hasFreeShipping = totalPrice > 50;
// ✅ EARLY RETURN: Empty cart
if (!itemCount) {
return <div className="alert alert-info">Your cart is empty 🛒</div>;
}
return (
<div className="card">
<div className="card-body">
<h5 className="card-title">Cart Summary</h5>
<p>Items: {itemCount}</p>
<p>Total: ${totalPrice.toFixed(2)}</p>
{hasFreeShipping && (
<div className="alert alert-success">🎉 Free shipping unlocked!</div>
)}
{!hasFreeShipping && (
<p className="text-muted">
Add ${(50 - totalPrice).toFixed(2)} more for free shipping
</p>
)}
</div>
</div>
);
}
💡 Key Takeaways
| Concept | What It Means | Example |
|---|---|---|
| Derived State | Calculate from existing state/props | const count = items.length |
| Stored State | Independent data that changes | const [items, setItems] = useState([]) |
| useState for Derived | ❌ WRONG! Never do this! | const [count, setCount] = useState(items.length) |
| Calculate in Render | ✅ CORRECT! Re-calc every render | const count = items.length |
| Auto-Sync | Derived values always match source | When items changes, count auto-updates |
| Early Return | Handle edge cases at top of component | if (!items.length) return <Empty /> |
| One Source of Truth | Store minimum state, derive the rest | items array = only state needed |
The Derived State Pattern:
function DerivedStatePattern() {
// Step 1: Store the MINIMUM state
const [items, setItems] = useState([]);
// Step 2: Derive everything else
const count = items.length;
const completed = items.filter(i => i.done).length;
const percentage = count > 0 ? Math.round((completed / count) * 100) : 0;
const isEmpty = count === 0;
const isComplete = percentage === 100;
// Step 3: Use early returns for clean UI
if (isEmpty) return <EmptyState />;
if (isComplete) return <SuccessState />;
// Step 4: Render main UI
return <MainUI count={count} percentage={percentage} />;
}
Golden Rules:
- If you can calculate it, don't store it! — Use simple variables, not useState
- One source of truth — Store the array, derive counts/totals/percentages
- React recalculates every render — Derived values are always up-to-date!
- Early returns for edge cases — Empty state, loading state, error state
- Never use useEffect to sync derived state — That's a code smell!
- Pass source data, not derived values —
<Stats items={items} />not<Stats count={count} /> - Use nullish checks —
items.length > 0 ? ... : 0prevents divide-by-zero
One Sentence Summary:
> "Derived state in React is the practice of computing values like counts, totals, percentages, and boolean flags directly from existing state or props within the component body rather than storing them in useState, which ensures the derived values are always automatically synchronized with their source data, eliminates the need for manual syncing that causes bugs and unnecessary re-renders, and follows the principle of storing only the minimum necessary state while deriving everything else that can be calculated from that core data!"