▶️ Live demo

Try it yourself — interact with the example below.

Loading demo…

🎯 What Is Lifting State Up?

Imagine you and your sibling both need to use the family iPad:

WITHOUT LIFTING STATE UP (The Problem):
┌─────────────────────────────────────────┐
│  🏠 THE HOUSE (Your App)                │
│                                         │
│  ┌─────────────┐  ┌─────────────┐      │
│  │ Your Room   │  │ Sibling's   │      │
│  │ (Form)      │  │ Room        │      │
│  │             │  │ (PackingList)│      │
│  │ iPad is     │  │             │      │
│  │ HERE! 📱    │  │ "Where's    │      │
│  │             │  │ the iPad?!" │      │
│  │ You can use │  │ Can't access│      │
│  │ it! ✓       │  │ it! ✗      │      │
│  └─────────────┘  └─────────────┘      │
│                                         │
│  ❌ Sibling can't use the iPad!         │
│  ❌ Data stuck in one room!             │
│  ❌ No way to share!                    │
└─────────────────────────────────────────┘

WITH LIFTING STATE UP (The Solution):
┌─────────────────────────────────────────┐
│  🏠 THE HOUSE (Your App)                │
│                                         │
│  ┌─────────────────────────────────┐  │
│  │  📱 LIVING ROOM (App Component)  │  │
│  │     iPad lives here now!         │  │
│  │     (The state lives here)        │  │
│  └─────────────────────────────────┘  │
│              │              │          │
│              ▼              ▼          │
│  ┌─────────────┐    ┌─────────────┐   │
│  │ Your Room   │    │ Sibling's   │   │
│  │ (Form)      │    │ Room        │   │
│  │             │    │ (PackingList)│   │
│  │ "Can I use  │    │ "Can I use  │   │
│  │ the iPad?" │    │ the iPad?"  │   │
│  │             │    │             │   │
│  │ You BORROW  │    │ Sibling     │   │
│  │ it from     │    │ BORROWS it  │   │
│  │ living room │    │ from living │   │
│  │ (via props) │    │ room (props)│   │
│  └─────────────┘    └─────────────┘   │
│                                         │
│  ✅ Both can use it!                    │
│  ✅ One source of truth!                │
│  ✅ Living room = Common Parent!        │
└─────────────────────────────────────────┘

THE LIFTING STATE UP DECISION FLOWCHART:
┌─────────────────────────────────────────┐
│  Do multiple components need this state?  │
│    ↓                                    │
│  Are they siblings? (same level)          │
│    → NO → Keep it where it is!          │
│    → YES → Find closest common parent!  │
│      → Move state there!                │
│      → Pass state down to children      │
│        that need to READ it             │
│      → Pass updater function down to    │
│        children that need to CHANGE it  │
│      → Everyone stays in sync! ✓        │
└─────────────────────────────────────────┘

⚠️ The Big Problem: "Where Should I Put This State?"

// ==========================================
// THE "STATE TRAPPED IN FORM" TRAP
// ==========================================

// ❌ WRONG: State locked in Form, PackingList can't see it!
function BadApp() {
  return (
    <div className="app">
      <Logo />
      <Form />           {/* Has items state? */}
      <PackingList />    {/* Needs items state! */}
      <Stats />
      {/* Form and PackingList are siblings! */}
      {/* They cannot pass props to each other! */}
    </div>
  );
}

function Form() {
  // ❌ WRONG HOME: items state lives here!
  const [items, setItems] = useState([]);  // ← Trapped in Form!
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  function handleSubmit(e) {
    e.preventDefault();
    if (!description) return;

    const newItem = {
      description,
      quantity,
      packed: false,
      id: Date.now()
    };

    // We can add items here...
    setItems((prev) => [...prev, newItem]);
    
    console.log(newItem); // But only Form sees it!
  }

  return (
    <form onSubmit={handleSubmit} className="add-form">
      <select value={quantity} onChange={e => setQuantity(Number(e.target.value))}>
        {Array.from({ length: 20 }, (_, i) => i + 1).map(num => (
          <option value={num} key={num}>{num}</option>
        ))}
      </select>
      <input 
        type="text" 
        placeholder="Item..." 
        value={description} 
        onChange={e => setDescription(e.target.value)} 
      />
      <button>Add</button>
    </form>
  );
}

function PackingList() {
  // ❌ Can't access items! Form won't share!
  // PackingList is a sibling, not a child of Form!
  const initialItems = [
    { id: 1, description: "Passports", quantity: 2, packed: false },
  ];

  return (
    <div className="list">
      <ul>
        {initialItems.map(item => (
          <li key={item.id}>
            <span style={item.packed ? { textDecoration: "line-through" } : {}}>
              {item.quantity} {item.description}
            </span>
            <button>❌</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

// Problems:
// 1. Form owns items but never displays them!
// 2. PackingList needs items but can't reach them!
// 3. Siblings CANNOT pass props sideways!
// 4. Data only flows DOWN the tree, not across!
// 5. Two sources of truth = bugs!

// ==========================================
// THE SOLUTION: Move State to Common Parent
// ==========================================

// ✅ CORRECT: State lives in App, shared via props!

function GoodApp() {
  // 🏠 HOME: App (the common parent of Form and PackingList)
  const [items, setItems] = useState([]);

  // Function to add items also lives here!
  function handleAddItems(newItem) {
    setItems((prev) => [...prev, newItem]);
  }

  return (
    <div className="app">
      <Logo />
      
      {/* Form gets the ADD function via props */}
      <Form onAddItems={handleAddItems} />
      
      {/* PackingList gets the items array via props */}
      <PackingList items={items} />
      
      <Stats />
    </div>
  );
}

function Form({ onAddItems }) {
  // Form only has LOCAL state for its inputs!
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  function handleSubmit(e) {
    e.preventDefault();
    if (!description) return;

    const newItem = {
      description,
      quantity,
      packed: false,
      id: Date.now()
    };

    // Tell parent to add this item!
    onAddItems(newItem);  // ← "Hey App, add this!"

    // Reset form
    setDescription("");
    setQuantity(1);
  }

  return (
    <form onSubmit={handleSubmit} className="add-form">
      <select value={quantity} onChange={e => setQuantity(Number(e.target.value))}>
        {Array.from({ length: 20 }, (_, i) => i + 1).map(num => (
          <option value={num} key={num}>{num}</option>
        ))}
      </select>
      <input 
        type="text" 
        placeholder="Item..." 
        value={description} 
        onChange={e => setDescription(e.target.value)} 
      />
      <button>Add</button>
    </form>
  );
}

function PackingList({ items }) {
  // Receives items from parent! Can display them!
  return (
    <div className="list">
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <span style={item.packed ? { textDecoration: "line-through" } : {}}>
              {item.quantity} {item.description}
            </span>
            <button>❌</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

// What happens when user adds "Socks":
// 
// 1. User types "Socks" in Form
//    → Form's local description state updates
//    → ONLY Form re-renders! ✓
//    → App does NOT re-render! ✓
//
// 2. User selects quantity 3
//    → Form's local quantity state updates
//    → ONLY Form re-renders! ✓
//
// 3. User clicks "Add" button
//    → Form's handleSubmit runs
//    → Creates newItem object
//    → Calls onAddItems(newItem) ← PROP FUNCTION!
//    → App's handleAddItems runs
//    → App's setItems updates state
//    → App re-renders
//    → Form AND PackingList BOTH re-render
//    → PackingList now shows "3 Socks"! ✓
//    → Stats can also see items! ✓
//
// 4. State lives in the right home!
//    → Form creates items, PackingList displays them!
//    → Both stay in sync! ✓
//    → One source of truth! ✓

📋 Complete Visual Examples

Create file: lifting-state-up.js

// ==========================================
// LIFTING STATE UP - Complete Guide
// ==========================================

/*
THE LIFTING STATE UP DECISION FLOWCHART:
┌─────────────────────────────────────────┐
│  Do multiple components need this?      │
│    ↓                                    │
│  Are they siblings?                     │
│    → NO → Keep it local!                │
│    → YES → Find closest common parent!  │
│      → Move useState there!             │
│      → Pass state down as props         │
│        to components that READ it       │
│      → Pass updater function down       │
│        as props to components that      │
│        CHANGE it                        │
│      → Children call function →         │
│        Parent updates → Everyone syncs! │
└─────────────────────────────────────────┘

LOCAL vs LIFTED STATE:
┌─────────────────────────────────────────┐
│  LOCAL STATE (Private Bedroom)            │
│    • Only one component needs it          │
│    • Example: Form input values           │
│                                         │
│  LIFTED STATE (Living Room)             │
│    • Multiple siblings need it            │
│    • Example: items array                 │
│    • Lives in common parent (App)       │
│    • Passed down via props                │
└─────────────────────────────────────────┘
*/

// ==========================================
// EXAMPLE 1: Local State (Form Inputs)
// ==========================================

import { useState } from 'react';

function Form({ onAddItems }) {
  // 🏠 HOME: Form owns these (only Form needs them!)
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  return (
    <form className="add-form" onSubmit={handleSubmit}>
      <select 
        value={quantity} 
        onChange={e => setQuantity(Number(e.target.value))}
      >
        {Array.from({ length: 20 }, (_, i) => i + 1).map(num => (
          <option value={num} key={num}>{num}</option>
        ))}
      </select>
      <input
        type="text"
        placeholder="Item..."
        value={description}
        onChange={e => setDescription(e.target.value)}
      />
      <button>Add</button>
    </form>
  );
}

// Visual Flow:
// Initial: description = "", quantity = 1
//    input shows: "" (empty)
//    select shows: 1
//
// User types "Socks":
//    onChange → setDescription("Socks")
//    React re-renders Form ONLY
//    description = "Socks"
//    input shows: "Socks"
//
// Why this is efficient:
//    → Only Form re-renders!
//    → App does NOT re-render!
//    → PackingList stays idle!
//    → Local state = Local updates! ✓

// ==========================================
// EXAMPLE 2: Parent → Child via Props (Passing the Remote)
// ==========================================

function App() {
  // 🏠 HOME: App owns the items (siblings need it!)
  const [items, setItems] = useState([]);        // ← State lives here

  function handleAddItems(newItem) {
    setItems(prev => [...prev, newItem]);         // ← Updates here
  }

  return (
    <div className="app">
      {/* Child receives the ADD function via props */}
      <Form onAddItems={handleAddItems} />        // ← Pass updater down

      {/* Child receives the items array via props */}
      <PackingList items={items} />               // ← Pass data down
    </div>
  );
}

// CHILD COMPONENT: Form can ADD items
// It receives the function and calls it!
function Form({ onAddItems }) {                  // ← Receives function
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  function handleSubmit(e) {
    e.preventDefault();
    if (!description) return;

    const newItem = {
      description,
      quantity,
      packed: false,
      id: Date.now()
    };

    onAddItems(newItem);                           // ← Calls parent's function!
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* inputs */}
      <button>Add</button>
    </form>
  );
}

// CHILD COMPONENT: PackingList can DISPLAY items
// It receives the array but doesn't own it!
function PackingList({ items }) {                 // ← Receives data
  return (
    <div className="list">
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.quantity} {item.description}
          </li>
        ))}
      </ul>
    </div>
  );
}

// Visual Flow:
// 1. App owns items = []
// 2. Form receives onAddItems function via props
// 3. PackingList receives items={[]} via props
// 4. User fills form and clicks "Add"
// 5. Form creates newItem
// 6. Form calls onAddItems(newItem)
// 7. App's handleAddItems runs → setItems([...items, newItem])
// 8. App's state updates to [{newItem}]
// 9. App re-renders
// 10. Both children receive updated props
// 11. PackingList now shows the item! ✓
//
// Key insight: Children don't own the state!
// They just BORROW it from parent via props!
// Form BORROWS the add function!
// PackingList BORROWS the items array!

// ==========================================
// EXAMPLE 3: Lifting State Up (The Sibling Problem)
// ==========================================

// THE PROBLEM: Two siblings need the SAME data
// Form needs to ADD to items
// PackingList needs to DISPLAY items
// But siblings CANNOT pass props to each other!

// ❌ WRONG: State in wrong place (Form has it!)
function WrongApp() {
  return (
    <div>
      <Form />          {/* Has items state? */}
      <PackingList />   {/* Needs items state? */}
      {/* Form can't pass items to PackingList! */}
      {/* They're siblings! */}
    </div>
  );
}

// ✅ CORRECT: Lift state to common parent
function CorrectApp() {
  // 🏠 HOME: App (the common parent of both siblings)
  // This is the FIRST component that contains BOTH
  // Form and PackingList in its tree!
  const [items, setItems] = useState([]);        // ← Lifted up here!

  function handleAddItems(newItem) {
    setItems(prev => [...prev, newItem]);        // ← Add item
  }

  function handleDeleteItems(id) {
    setItems(prev => prev.filter(item => item.id !== id)); // ← Remove
  }

  function handleToggleItem(id) {
    setItems(prev => prev.map(item => 
      item.id === id ? { ...item, packed: !item.packed } : item
    ));                                            // ← Toggle
  }

  return (
    <div className="app">
      <Logo />
      
      {/* Pass ADD function to Form */}
      <Form onAddItems={handleAddItems} />         // ← Write access

      {/* Pass items array AND functions to PackingList */}
      <PackingList 
        items={items}                              // ← Read access
        onDeleteItem={handleDeleteItems}            // ← Write access
        onToggleItem={handleToggleItem}             // ← Write access
      />

      <Stats items={items} />                      // ← Read access
    </div>
  );
}

// Form: Can ADD items (calls parent's function)
function Form({ onAddItems }) {
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  function handleSubmit(e) {
    e.preventDefault();
    if (!description) return;

    const newItem = { description, quantity, packed: false, id: Date.now() };
    onAddItems(newItem);                           // ← "Hey parent, add this!"
    setDescription("");
    setQuantity(1);
  }

  return (
    <form onSubmit={handleSubmit} className="add-form">
      <select value={quantity} onChange={e => setQuantity(Number(e.target.value))}>
        {Array.from({ length: 20 }, (_, i) => i + 1).map(num => (
          <option value={num} key={num}>{num}</option>
        ))}
      </select>
      <input 
        value={description} 
        onChange={e => setDescription(e.target.value)} 
        placeholder="Item..." 
      />
      <button>Add</button>
    </form>
  );
}

// PackingList: Can VIEW, DELETE, and TOGGLE items
function PackingList({ items, onDeleteItem, onToggleItem }) {
  return (
    <div className="list">
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <input 
              type="checkbox" 
              checked={item.packed} 
              onChange={() => onToggleItem(item.id)} 
            />
            <span style={item.packed ? { textDecoration: "line-through" } : {}}>
              {item.quantity} {item.description}
            </span>
            <button onClick={() => onDeleteItem(item.id)}>❌</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

// Stats: Shows derived information
function Stats({ items }) {
  // DERIVED STATE: Calculate from items!
  const numItems = items.length;
  const numPacked = items.filter(item => item.packed).length;
  const percentage = numItems > 0 ? Math.round((numPacked / numItems) * 100) : 0;

  return (
    <footer className="stats">
      <em>
        {percentage === 100 
          ? "You got everything! Ready to go! ✈️" 
          : `💼 You have ${numItems} items, packed ${numPacked} (${percentage}%)`
        }
      </em>
    </footer>
  );
}

// Visual Flow of Lifting State Up:
// 1. App owns items = []
// 2. Form shows inputs, can call onAddItems
// 3. PackingList shows items, can call onDeleteItem/onToggleItem
// 4. User clicks "Add" on "3 Socks"
// 5. Form calls onAddItems(newItem)
// 6. App's handleAddItems runs → setItems([...items, newItem])
// 7. App's state updates to [{3 Socks}]
// 8. App re-renders
// 9. Form AND PackingList AND Stats ALL re-render
// 10. PackingList now shows "3 Socks"! ✓
// 11. Stats shows "1 item, 0% packed"! ✓
// 12. All siblings are in sync! ✓
//
// Why this works:
//    → Parent owns the "single source of truth"
//    → Both children read from the SAME state
//    → Updates flow through parent, keeping everyone in sync!

// ==========================================
// EXAMPLE 4: The Decision Flowchart in Code
// ==========================================

function DecisionFlowchartDemo() {
  // Question 1: Do we need to store data?
  // → YES! We need packing items

  // Question 2: Will it change?
  // → YES! User adds/removes/toggles items
  const [items, setItems] = useState([]);          // ← STATE! ✓

  // Question 3: Can it be derived from existing state?
  // → NO! It's independent data entered by user

  // Question 4: Should updating re-render the component?
  // → YES! UI must show new items immediately

  // Question 5: Where should this live?
  // → Form needs to ADD items
  // → PackingList needs to DISPLAY items
  // → Stats needs to CALCULATE from items
  // → They are siblings! → LIFT TO APP! ✓

  // Question 6: What about form inputs?
  // → Only Form needs them → LOCAL STATE! ✓
  const [description, setDescription] = useState("");

  // Question 7: What about stats numbers?
  // → Derived from items → DERIVED STATE! ✓
  const numItems = items.length;

  return (
    <div className="app">
      <Form />
      <PackingList items={items} />
      <Stats items={items} />
    </div>
  );
}

// ==========================================
// EXAMPLE 5: Complete Far Away App with All Patterns
// ==========================================

const initialItems = [
  { 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 CompleteApp() {
  // GLOBAL-ish state: Shared by many components
  // 🏠 HOME: App (common parent)
  const [items, setItems] = useState([]);

  // Functions to update state
  function handleAddItems(item) {
    setItems(prev => [...prev, item]);
  }

  function handleDeleteItem(id) {
    setItems(prev => prev.filter(item => item.id !== id));
  }

  function handleToggleItem(id) {
    setItems(prev => prev.map(item => 
      item.id === id ? { ...item, packed: !item.packed } : item
    ));
  }

  // Derived state: Calculate from items
  const numItems = items.length;
  const numPacked = items.filter(item => item.packed).length;
  const percentage = numItems > 0 ? Math.round((numPacked / numItems) * 100) : 0;

  return (
    <div className="app">
      <Logo />
      <Form onAddItems={handleAddItems} />
      <PackingList 
        items={items} 
        onDeleteItem={handleDeleteItem}
        onToggleItem={handleToggleItem}
      />
      <Stats percentage={percentage} numItems={numItems} numPacked={numPacked} />
    </div>
  );
}

🚀 Interactive React Usage Examples

Complete React File: LiftingStateUpMasterClass.jsx

import React, { useState } from 'react';

// ==========================================
// INTERACTIVE LIFTING STATE UP DEMO
// ==========================================

function LiftingStateUpMasterClass() {
  const [activeDemo, setActiveDemo] = useState('flowchart');

  const demos = {
    flowchart: { title: 'Decision Flowchart', component: <FlowchartDemo /> },
    broken: { title: 'Broken (State Trapped)', component: <BrokenStateDemo /> },
    fixed: { title: 'Fixed (Lifted Up)', component: <FixedStateDemo /> },
    functions: { title: 'Passing Functions', component: <FunctionsDemo /> },
    complete: { title: 'Complete App', component: <CompleteAppDemo /> }
  };

  return (
    <div style={{ maxWidth: '900px', margin: '0 auto', padding: '20px', fontFamily: 'Arial, sans-serif' }}>
      <header style={{ background: '#264653', color: 'white', padding: '30px', borderRadius: '10px', marginBottom: '30px' }}>
        <h1 style={{ margin: 0 }}>🚀 Lifting State Up</h1>
        <p style={{ margin: '10px 0 0 0', opacity: 0.9 }}>Moving State to the Common Parent</p>
      </header>

      <nav style={{ display: 'flex', gap: '10px', marginBottom: '30px', flexWrap: 'wrap' }}>
        {Object.entries(demos).map(([key, { title }]) => (
          <button
            key={key}
            onClick={() => setActiveDemo(key)}
            style={{
              padding: '12px 24px',
              fontSize: '16px',
              border: 'none',
              borderRadius: '8px',
              cursor: 'pointer',
              backgroundColor: activeDemo === key ? '#e76f51' : '#e0e0e0',
              color: activeDemo === key ? 'white' : '#333',
              fontWeight: activeDemo === key ? 'bold' : 'normal'
            }}
          >
            {title}
          </button>
        ))}
      </nav>

      <main style={{ background: '#f8f9fa', padding: '30px', borderRadius: '10px', minHeight: '400px' }}>
        {demos[activeDemo].component}
      </main>
    </div>
  );
}

// ==========================================
// DEMO 1: The Decision Flowchart
// ==========================================

function FlowchartDemo() {
  const [step, setStep] = useState(1);

  const steps = [
    {
      num: 1,
      title: 'Need Data?',
      question: 'Do you need to store some data?',
      code: '// Any data that changes?',
      visual: '📦',
      desc: 'If no data to track, no state needed!'
    },
    {
      num: 2,
      title: 'Will It Change?',
      question: 'Will this data change over time?',
      code: 'const data = "fixed"; // No change? Use const!',
      visual: '🔄',
      desc: 'If it never changes, use a regular variable!'
    },
    {
      num: 3,
      title: 'Can It Be Derived?',
      question: 'Can you calculate it from existing state/props?',
      code: 'const total = items.reduce((s, i) => s + i.price, 0);',
      visual: '🧮',
      desc: 'If yes, DERIVE it! Don\'t store it!'
    },
    {
      num: 4,
      title: 'Need Re-render?',
      question: 'Should updating trigger a re-render?',
      code: 'const [count, setCount] = useState(0); // Re-renders!',
      visual: '⚡',
      desc: 'If yes, useState! If no, useRef (later)!'
    },
    {
      num: 5,
      title: 'Where To Place?',
      question: 'Who needs this state?',
      code: '// Only here? → Local\n// Child? → Props\n// Sibling? → Lift Up!',
      visual: '🏠',
      desc: 'Give it a home in the right component!'
    },
    {
      num: 6,
      title: 'Siblings Need It?',
      question: 'Do multiple siblings need the same state?',
      code: '// Form + PackingList both need items[]\n// → Move to App! (Common Parent)',
      visual: '⬆️',
      desc: 'Lift state up to the closest common parent!'
    }
  ];

  const current = steps[step - 1];

  return (
    <div>
      <h2>The Lifting State Up Flowchart 🧭</h2>

      <div style={{ display: 'flex', gap: '10px', marginBottom: '20px', justifyContent: 'center' }}>
        {steps.map(s => (
          <button
            key={s.num}
            onClick={() => setStep(s.num)}
            style={{
              width: '60px',
              height: '60px',
              borderRadius: '50%',
              border: 'none',
              cursor: 'pointer',
              background: step >= s.num ? '#e76f51' : '#e0e0e0',
              color: step >= s.num ? 'white' : '#333',
              fontSize: '24px',
              fontWeight: 'bold'
            }}
          >
            {s.num}
          </button>
        ))}
      </div>

      <div style={{ padding: '30px', background: 'white', borderRadius: '10px', border: '2px solid #264653' }}>
        <h3 style={{ color: '#264653', marginTop: 0 }}>Step {current.num}: {current.title}</h3>
        <p style={{ fontSize: '20px', color: '#555', fontWeight: 'bold' }}>{current.question}</p>

        <div style={{ display: 'grid', gap: '20px', gridTemplateColumns: '1fr 1fr', marginBottom: '20px' }}>
          <div>
            <pre style={{ 
              background: '#1e1e1e', 
              color: '#d4d4d4', 
              padding: '20px', 
              borderRadius: '8px',
              fontSize: '16px'
            }}>
              {current.code}
            </pre>
            <p style={{ fontSize: '18px', color: '#555' }}>{current.desc}</p>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '80px' }}>
            {current.visual}
          </div>
        </div>
      </div>

      <div style={{ marginTop: '20px', padding: '15px', background: '#e8f5e9', borderRadius: '8px' }}>
        <h4>🧠 Memory Trick:</h4>
        <p>"When siblings fight over a toy, move the toy to the living room where mom can supervise!"</p>
      </div>
    </div>
  );
}

// ==========================================
// DEMO 2: Broken State (Trapped in Form)
// ==========================================

function BrokenStateDemo() {
  const [items, setItems] = useState([]); // State trapped here!
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  function handleSubmit(e) {
    e.preventDefault();
    if (!description) return;

    const newItem = {
      description,
      quantity,
      packed: false,
      id: Date.now()
    };

    setItems(prev => [...prev, newItem]);
    setDescription("");
    setQuantity(1);
  }

  return (
    <div>
      <h2>Broken: State Trapped in Form ❌</h2>

      <div style={{ marginBottom: '20px', padding: '15px', background: '#ffebee', borderRadius: '8px' }}>
        <h4>The items state is locked inside Form! PackingList can't see it!</h4>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
        {/* Form */}
        <div style={{ padding: '20px', background: 'white', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
          <h3 style={{ color: '#264653' }}>📝 Form</h3>
          <p style={{ color: '#666', fontSize: '14px' }}>Has state, but can't share it!</p>
          <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
            <input 
              type="text" 
              placeholder="Item..." 
              value={description}
              onChange={e => setDescription(e.target.value)}
              style={{ padding: '10px', borderRadius: '5px', border: '1px solid #ddd' }}
            />
            <button style={{ padding: '10px', background: '#2a9d8f', color: 'white', border: 'none', borderRadius: '5px' }}>
              Add
            </button>
          </form>
          <div style={{ marginTop: '10px', padding: '10px', background: '#e3f2fd', borderRadius: '5px' }}>
            <strong>Items in Form state:</strong> {items.length}
          </div>
        </div>

        {/* PackingList */}
        <div style={{ padding: '20px', background: 'white', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
          <h3 style={{ color: '#264653' }}>🎒 PackingList</h3>
          <p style={{ color: '#666', fontSize: '14px' }}>Can't see Form's items!</p>
          <div style={{ padding: '20px', background: '#ffebee', borderRadius: '5px', textAlign: 'center' }}>
            <p>😢 I can't see any items!</p>
            <p style={{ fontSize: '12px', color: '#999' }}>Form won't share!</p>
          </div>
        </div>
      </div>

      <div style={{ marginTop: '20px', padding: '15px', background: '#fff3e0', borderRadius: '8px' }}>
        <h4>🧠 The Problem:</h4>
        <ol>
          <li>Form has its own <code>useState([])</code></li>
          <li>PackingList is a sibling, not a child</li>
          <li>Siblings CANNOT pass props to each other!</li>
          <li>Data only flows DOWN the tree!</li>
          <li>We need to LIFT the state up to App!</li>
        </ol>
      </div>
    </div>
  );
}

// ==========================================
// DEMO 3: Fixed State (Lifted to App)
// ==========================================

function FixedStateDemo() {
  const [items, setItems] = useState([]);

  function handleAddItems(newItem) {
    setItems(prev => [...prev, newItem]);
  }

  function handleDeleteItem(id) {
    setItems(prev => prev.filter(item => item.id !== id));
  }

  return (
    <div>
      <h2>Fixed: State Lifted to App ✅</h2>

      <div style={{ marginBottom: '20px', padding: '15px', background: '#e8f5e9', borderRadius: '8px' }}>
        <h4>App owns the state. Both children receive it via props. They stay in sync!</h4>
      </div>

      <div style={{ padding: '20px', background: '#264653', color: 'white', borderRadius: '10px', marginBottom: '20px', textAlign: 'center' }}>
        <h3 style={{ margin: '0 0 10px 0' }}>🏠 App Component</h3>
        <p style={{ margin: 0, opacity: 0.9 }}>Owns items state: [{items.map(i => i.description).join(', ') || 'empty'}]</p>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
        {/* Form */}
        <div style={{ padding: '20px', background: 'white', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
          <h3 style={{ color: '#264653' }}>📝 Form</h3>
          <p style={{ color: '#666', fontSize: '14px' }}>Can ADD items (receives function)</p>
          <FixedForm onAddItems={handleAddItems} />
        </div>

        {/* PackingList */}
        <div style={{ padding: '20px', background: 'white', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)' }}>
          <h3 style={{ color: '#264653' }}>🎒 PackingList</h3>
          <p style={{ color: '#666', fontSize: '14px' }}>Can DISPLAY items (receives array)</p>
          <FixedPackingList items={items} onDeleteItem={handleDeleteItem} />
        </div>
      </div>

      <div style={{ marginTop: '20px', padding: '15px', background: '#e8f5e9', borderRadius: '8px' }}>
        <h4>✅ How It Works:</h4>
        <ol>
          <li>App owns <code>items</code> state</li>
          <li>Form child receives <code>onAddItems</code> function</li>
          <li>PackingList child receives <code>items</code> array and <code>onDeleteItem</code></li>
          <li>Both read from the SAME source of truth!</li>
          <li>Always in sync! No duplicate data!</li>
        </ol>
      </div>
    </div>
  );
}

function FixedForm({ onAddItems }) {
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  function handleSubmit(e) {
    e.preventDefault();
    if (!description) return;

    onAddItems({
      description,
      quantity,
      packed: false,
      id: Date.now()
    });

    setDescription("");
    setQuantity(1);
  }

  return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
      <select 
        value={quantity} 
        onChange={e => setQuantity(Number(e.target.value))}
        style={{ padding: '10px', borderRadius: '5px' }}
      >
        {Array.from({ length: 10 }, (_, i) => i + 1).map(num => (
          <option key={num} value={num}>{num}</option>
        ))}
      </select>
      <input 
        type="text" 
        placeholder="Item..." 
        value={description}
        onChange={e => setDescription(e.target.value)}
        style={{ padding: '10px', borderRadius: '5px', border: '1px solid #ddd' }}
      />
      <button style={{ padding: '10px', background: '#2a9d8f', color: 'white', border: 'none', borderRadius: '5px' }}>
        Add
      </button>
    </form>
  );
}

function FixedPackingList({ items, onDeleteItem }) {
  return (
    <div>
      {items.length === 0 ? (
        <p style={{ color: '#999' }}>No items yet...</p>
      ) : (
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {items.map(item => (
            <li key={item.id} style={{ display: 'flex', justifyContent: 'space-between', padding: '8px', borderBottom: '1px solid #eee' }}>
              <span>{item.quantity} {item.description}</span>
              <button 
                onClick={() => onDeleteItem(item.id)}
                style={{ background: '#e76f51', color: 'white', border: 'none', borderRadius: '3px', cursor: 'pointer' }}
              >
                ❌
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// ==========================================
// DEMO 4: Passing Functions as Props
// ==========================================

function FunctionsDemo() {
  const [showExplanation, setShowExplanation] = useState(false);

  return (
    <div>
      <h2>Passing Functions as Props 📞</h2>

      <div style={{ marginBottom: '20px', padding: '15px', background: '#e3f2fd', borderRadius: '8px' }}>
        <h4>This is the secret trick! Parents pass functions DOWN, children call them to update UP!</h4>
      </div>

      <div style={{ 
        padding: '40px', 
        background: 'white', 
        borderRadius: '10px',
        textAlign: 'center',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)'
      }}>
        <div style={{ display: 'flex', gap: '20px', justifyContent: 'center', marginBottom: '20px' }}>
          <div style={{ padding: '20px', background: '#e8f5e9', borderRadius: '8px', minWidth: '200px' }}>
            <div style={{ fontSize: '12px', color: '#666' }}>Parent (App)</div>
            <div style={{ fontSize: '18px', fontWeight: 'bold', color: '#2e7d32' }}>
              Owns State
            </div>
            <div style={{ fontSize: '40px' }}>👨‍👩‍👧</div>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', fontSize: '24px' }}>
            ➡️ 📞 ➡️
          </div>
          <div style={{ padding: '20px', background: '#fff3e0', borderRadius: '8px', minWidth: '200px' }}>
            <div style={{ fontSize: '12px', color: '#666' }}>Child (Form)</div>
            <div style={{ fontSize: '18px', fontWeight: 'bold', color: '#e76f51' }}>
              Gets Function
            </div>
            <div style={{ fontSize: '40px' }}>👶</div>
          </div>
        </div>

        <pre style={{ 
          background: '#1e1e1e', 
          color: '#d4d4d4', 
          padding: '20px', 
          borderRadius: '8px',
          textAlign: 'left',
          fontSize: '14px'
        }}>
{`// Parent gives child a "phone number" (function)
<Form onAddItems={handleAddItems} />

// Child "calls" the parent when something happens
function Form({ onAddItems }) {
  function handleSubmit() {
    onAddItems(newItem); // ← "Hey parent, add this!"
  }
}`}
        </pre>
      </div>

      <div style={{ marginTop: '20px', textAlign: 'center' }}>
        <button 
          onClick={() => setShowExplanation(!showExplanation)}
          style={{ padding: '10px 20px', background: '#264653', color: 'white', border: 'none', borderRadius: '5px' }}
        >
          {showExplanation ? 'Hide' : 'Show'} Why This Works
        </button>
      </div>

      {showExplanation && (
        <div style={{ marginTop: '20px', padding: '20px', background: '#fff3e0', borderRadius: '8px' }}>
          <h4>🔍 The "Phone Call" Pattern:</h4>
          <ol style={{ lineHeight: '2' }}>
            <li>Parent owns the state (like owning a bank account)</li>
            <li>Parent gives child a "debit card" (function prop)</li>
            <li>Child uses the card to make a purchase (calls function)</li>
            <li>Parent's account updates (state changes)</li>
            <li>Parent gives everyone new balances (re-renders children)</li>
            <li>Everyone sees the updated amount! ✨</li>
          </ol>
        </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 handleAddItems(item) {
    setItems(prev => [...prev, item]);
  }

  function handleDeleteItem(id) {
    setItems(prev => prev.filter(item => item.id !== id));
  }

  function handleToggleItem(id) {
    setItems(prev => prev.map(item => 
      item.id === id ? { ...item, packed: !item.packed } : item
    ));
  }

  const numItems = items.length;
  const numPacked = items.filter(item => item.packed).length;
  const percentage = numItems > 0 ? Math.round((numPacked / numItems) * 100) : 0;

  return (
    <div>
      <h2>Complete Far Away App 🌴</h2>

      <div style={{ marginBottom: '20px', padding: '15px', background: '#e3f2fd', borderRadius: '8px' }}>
        <h4>All patterns together: Local state, Lifted state, Derived state, and Function props!</h4>
      </div>

      <div style={{ 
        padding: '30px', 
        background: 'white', 
        borderRadius: '10px',
        boxShadow: '0 2px 10px rgba(0,0,0,0.1)'
      }}>
        <div style={{ textAlign: 'center', marginBottom: '20px' }}>
          <h1 style={{ color: '#264653' }}>🌴 Far Away 💼</h1>
        </div>

        <CompleteForm onAddItems={handleAddItems} />

        <div style={{ marginTop: '20px' }}>
          <CompletePackingList 
            items={items} 
            onDeleteItem={handleDeleteItem}
            onToggleItem={handleToggleItem}
          />
        </div>

        <div style={{ marginTop: '20px', padding: '15px', background: '#264653', color: 'white', borderRadius: '8px', textAlign: 'center' }}>
          <em>
            {percentage === 100
              ? "You got everything! Ready to go! ✈️"
              : `💼 You have ${numItems} items, packed ${numPacked} (${percentage}%)`
            }
          </em>
        </div>
      </div>
    </div>
  );
}

function CompleteForm({ onAddItems }) {
  const [description, setDescription] = useState("");
  const [quantity, setQuantity] = useState(1);

  function handleSubmit(e) {
    e.preventDefault();
    if (!description) return;

    onAddItems({
      description,
      quantity,
      packed: false,
      id: Date.now()
    });

    setDescription("");
    setQuantity(1);
  }

  return (
    <form onSubmit={handleSubmit} style={{ display: 'flex', gap: '10px', justifyContent: 'center' }}>
      <select 
        value={quantity} 
        onChange={e => setQuantity(Number(e.target.value))}
        style={{ padding: '10px', borderRadius: '5px' }}
      >
        {Array.from({ length: 20 }, (_, i) => i + 1).map(num => (
          <option key={num} value={num}>{num}</option>
        ))}
      </select>
      <input 
        type="text" 
        placeholder="Item..." 
        value={description}
        onChange={e => setDescription(e.target.value)}
        style={{ padding: '10px', borderRadius: '5px', border: '1px solid #ddd', width: '200px' }}
      />
      <button style={{ padding: '10px 20px', background: '#2a9d8f', color: 'white', border: 'none', borderRadius: '5px' }}>
        Add
      </button>
    </form>
  );
}

function CompletePackingList({ items, onDeleteItem, onToggleItem }) {
  return (
    <div style={{ background: '#f8f9fa', padding: '20px', borderRadius: '8px' }}>
      <h3 style={{ color: '#264653', marginTop: 0 }}>Your Packing List</h3>
      {items.length === 0 ? (
        <p style={{ color: '#999', textAlign: 'center' }}>Start adding items for your trip! 🌴</p>
      ) : (
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {items.map(item => (
            <li key={item.id} style={{ 
              display: 'flex', 
              alignItems: 'center', 
              gap: '10px', 
              padding: '10px', 
              borderBottom: '1px solid #eee',
              background: item.packed ? '#e8f5e9' : 'white',
              borderRadius: '5px',
              marginBottom: '5px'
            }}>
              <input 
                type="checkbox" 
                checked={item.packed}
                onChange={() => onToggleItem(item.id)}
              />
              <span style={{ 
                flex: 1,
                textDecoration: item.packed ? 'line-through' : 'none',
                color: item.packed ? '#999' : '#333'
              }}>
                {item.quantity} {item.description}
              </span>
              <button 
                onClick={() => onDeleteItem(item.id)}
                style={{ background: '#e76f51', color: 'white', border: 'none', borderRadius: '3px', cursor: 'pointer' }}
              >
                ❌
              </button>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default LiftingStateUpMasterClass;

🧠 Memory Aids for Poor Logic Thinking

The "Pizza Order" Analogy

┌─────────────────────────────────────────────────┐
│  LIFTING STATE UP = ORDERING PIZZA TOGETHER      │
│                                                 │
│  Scenario: You and your roommate want to order   │
│  pizza together.                                │
│                                                 │
│  ❌ WRONG WAY (State in your room):              │
│  ┌──────────┐  ┌──────────┐                    │
│  │ Your Room│  │ Roommate  │                    │
│  │          │  │ Room      │                    │
│  │ "I want  │  │ "What do  │                    │
│  │ pepperoni│  │  you want?"│                    │
│  │ pizza!"  │  │           │                    │
│  │          │  │ Can't hear│                    │
│  │ 📱 Phone │  │ you!      │                    │
│  │ (state)  │  │           │                    │
│  └──────────┘  └──────────┘                    │
│                                                 │
│  You can't order together! Roommate doesn't    │
│  know what you want!                            │
│                                                 │
│  ✅ CORRECT WAY (State in kitchen):             │
│  ┌─────────────────────────────────┐          │
│  │  🍕 KITCHEN (Common Parent)      │          │
│  │  Pizza order list on fridge:     │          │
│  │  • Pepperoni (from you)           │          │
│  │  • Mushroom (from roommate)       │          │
│  │  (This is the state!)            │          │
│  └─────────────────────────────────┘          │
│         │              │                       │
│         ▼              ▼                       │
│  ┌──────────┐    ┌──────────┐               │
│  │ You walk │    │ Roommate │               │
│  │ to kitchen│   │ walks to │               │
│  │ and ADD  │    │ kitchen  │               │
│  │ to list  │    │ and READS│               │
│  │ (onAdd)  │    │ list     │               │
│  │          │    │ (items)  │               │
│  └──────────┘    └──────────┘               │
│                                                 │
│  Both access the SAME list!                     │
│  One source of truth!                           │
└─────────────────────────────────────────────────┘

The "Remote Control" Analogy

┌─────────────────────────────────────────────────┐
│  PASSING FUNCTIONS = GIVING REMOTE CONTROLS    │
│                                                 │
│  Parent (App) owns the TV (state):              │
│  ┌─────────────────────────────────┐          │
│  │  📺 TV = items state             │          │
│  │  (Only App can change channels)  │          │
│  └─────────────────────────────────┘          │
│                                                 │
│  But Parent gives children REMOTES:             │
│                                                 │
│  Form gets: "Volume Up" remote                 │
│  (onAddItems = add to the list)                │
│                                                 │
│  PackingList gets: "View Screen" remote        │
│  (items = see what's on TV)                    │
│                                                 │
│  Form CANNOT grab the TV directly!              │
│  It must use the remote (call function)        │
│  to ask App to change the channel!             │
│                                                 │
│  This is called:                                │
│  "Inverse Data Flow"                            │
│  (Child tells Parent to update)                │
│                                                 │
│  ┌─────────────────────────────────────────┐   │
│  │  NORMAL DATA FLOW: Parent → Child      │   │
│  │     (Props go DOWN)                     │   │
│  │                                          │   │
│  │  INVERSE DATA FLOW: Child → Parent       │   │
│  │     (Function calls go UP)              │   │
│  └─────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

The "Bank Account" Analogy

┌─────────────────────────────────────────────────┐
│  LIFTING STATE = FAMILY BANK ACCOUNT             │
│                                                 │
│  ❌ WRONG: Each person has own wallet            │
│  ┌──────────┐  ┌──────────┐                    │
│  │ Dad has  │  │ Mom has  │                    │
│  │ $100     │  │ $100     │                    │
│  │ (Form)   │  │ (Packing)│                    │
│  │          │  │          │                    │
│  │ Dad buys │  │ Mom buys │                    │
│  │ groceries│  │ shoes    │                    │
│  │          │  │          │                    │
│  │ "I spent │  │ "I spent │                    │
│  │  $20!"   │  │  $50!"   │                    │
│  │          │  │          │                    │
│  │ But they │  │ don't    │                    │
│  │ don't    │  │ know the │                    │
│  │ know each│  │ total!   │                    │
│  │ other's  │  │          │                    │
│  │ spending!│  │          │                    │
│  └──────────┘  └──────────┘                    │
│                                                 │
│  ✅ CORRECT: Joint bank account                │
│  ┌─────────────────────────────────┐          │
│  │  🏦 JOINT ACCOUNT (App)          │          │
│  │  Balance: $200                   │          │
│  │  (One source of truth!)           │          │
│  └─────────────────────────────────┘          │
│         │              │                       │
│         ▼              ▼                       │
│  ┌──────────┐    ┌──────────┐               │
│  │ Dad has  │    │ Mom has  │               │
│  │ DEBIT    │    │ VIEW     │               │
│  │ CARD     │    │ ACCESS   │               │
│  │ (can     │    │ (can see │               │
│  │  spend)  │    │  balance)│               │
│  │          │    │          │               │
│  │ onSpend= │    │ balance= │               │
│  │ function │    │ {balance}│               │
│  └──────────┘    └──────────┘               │
│                                                 │
│  Both see the SAME balance!                     │
│  Dad spends → Mom sees new balance!            │
└─────────────────────────────────────────────────┘

The "Elevator" Analogy for Lifting State Up

┌─────────────────────────────────────────────────┐
│  LIFTING STATE UP = MOVING THE ELEVATOR        │
│                                                 │
│  Imagine a building with floors:               │
│                                                 │
│  ┌─────────────────────────────────────────┐   │
│  │  🏢 FLOOR 3: App Component              │   │
│  │     ┌──────────┐  ┌──────────┐         │   │
│  │     │   Form   │  │ Packing  │         │   │
│  │     │ (Floor 1)│  │ List     │         │   │
│  │     │          │  │ (Floor 2)│         │   │
│  │     └──────────┘  └──────────┘         │   │
│  │         ↑              ↑               │   │
│  │         └──────┬───────┘               │   │
│  │                │                       │   │
│  │         ┌──────┴───────┐               │   │
│  │         │  ELEVATOR    │               │   │
│  │         │  (State)     │               │   │
│  │         └──────────────┘               │   │
│  └─────────────────────────────────────────┘   │
│                                                 │
│  PROBLEM: items state is in Form (Floor 1)     │
│  Packing List (Floor 2) CANNOT access it!       │
│  They're on different floors!                   │
│                                                 │
│  SOLUTION: Move the elevator to Floor 3 (App)! │
│  Now BOTH floors can access the items!         │
│                                                 │
│  ┌─────────────────────────────────────────┐   │
│  │  🏢 FLOOR 3: App (Now has items state!)   │   │
│  │     ┌──────────┐  ┌──────────┐         │   │
│  │     │   Form   │  │ Packing  │         │   │
│  │     │ receives │  │ receives │         │   │
│  │     │ onAddItems│  │ items    │         │   │
│  │     │ via props│  │ via props│         │   │
│  │     │    ✓     │  │    ✓     │         │   │
│  │     └──────────┘  └──────────┘         │   │
│  └─────────────────────────────────────────┘   │
│                                                 │
│  The "Elevator" (state) moved UP to the common  │
│  parent so ALL children can use it!             │
└─────────────────────────────────────────────────┘

🎓 Practice Exercises

Exercise 1: Identify Where to Lift State

Look at this component tree. Where should todos state live?

App
├── Header
│   └── TodoCount
├── TodoForm
│   └── Input + Button
└── TodoList
    └── TodoItem (x many)

Questions:

  1. Which component creates new todos? → TodoForm
  2. Which component displays todos? → TodoList
  3. Are they siblings? → Yes!
  4. What's their common parent? → App

Solution:

// ==========================================
// SOLUTION: Where to Lift the State
// ==========================================

function App() {
  // 🏠 HOME: App (common parent of TodoForm and TodoList)
  const [todos, setTodos] = useState([]);

  function addTodo(todo) {
    setTodos((prev) => [...prev, todo]);
  }

  function deleteTodo(id) {
    setTodos((prev) => prev.filter((t) => t.id !== id));
  }

  return (
    <div>
      <Header count={todos.length} />
      <TodoForm onAddTodo={addTodo} />
      <TodoList todos={todos} onDeleteTodo={deleteTodo} />
    </div>
  );
}

function Header({ count }) {
  return <h1>You have {count} todos</h1>;
}

function TodoForm({ onAddTodo }) {
  // Local state ONLY for the input!
  const [text, setText] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    if (!text) return;

    onAddTodo({ text, id: Date.now(), done: false });
    setText("");
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button>Add</button>
    </form>
  );
}

function TodoList({ todos, onDeleteTodo }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          {todo.text}
          <button onClick={() => onDeleteTodo(todo.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

Exercise 2: Fix the Broken Code

This code has a bug. The counter in Display and the buttons in Controls don't sync. Fix it by lifting state up!

// ❌ BROKEN: State in wrong place
function App() {
  return (
    <div>
      <CounterDisplay />
      <CounterControls />
    </div>
  );
}

function CounterDisplay() {
  const [count, setCount] = useState(0); // ❌ Wrong home!
  return <p>Count: {count}</p>;
}

function CounterControls() {
  const [count, setCount] = useState(0); // ❌ Different state!
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  );
}

Solution:

// ✅ FIXED: State lifted to App
function App() {
  // 🏠 HOME: App (common parent of Display and Controls)
  const [count, setCount] = useState(0);

  function handleIncrement() {
    setCount((c) => c + 1);
  }

  function handleDecrement() {
    setCount((c) => c - 1);
  }

  return (
    <div>
      <CounterDisplay count={count} />
      <CounterControls
        onIncrement={handleIncrement}
        onDecrement={handleDecrement}
      />
    </div>
  );
}

function CounterDisplay({ count }) {
  // No state here! Just receives props
  return <p>Count: {count}</p>;
}

function CounterControls({ onIncrement, onDecrement }) {
  return (
    <div>
      <button onClick={onIncrement}>+</button>
      <button onClick={onDecrement}>-</button>
    </div>
  );
}

Exercise 3: The Lifting State Up Flowchart

You need to track a list of expenses. Walk through the decision chart:

Questions:

  1. Will this data change? → Yes/No
  2. Can it be derived? → Yes/No
  3. Should it re-render? → Yes/No
  4. Where should it live?

Solution:

function ExpenseApp() {
  // 1. Does it change? → YES! (user adds expenses)
  // 2. Can it be derived? → NO! (independent data)
  // 3. Should it re-render? → YES! (show new expenses)
  // 4. Where? → ExpenseForm + ExpenseList both need it
  //    → They are siblings! → LIFT TO APP! ✓

  const [expenses, setExpenses] = useState([]);

  function addExpense(expense) {
    setExpenses((prev) => [...prev, expense]);
  }

  // Derived state!
  const total = expenses.reduce((sum, e) => sum + e.amount, 0);

  return (
    <div>
      <ExpenseForm onAddExpense={addExpense} />
      <ExpenseList expenses={expenses} />
      <ExpenseTotal total={total} />
    </div>
  );
}

function ExpenseForm({ onAddExpense }) {
  // Local state ONLY for form inputs
  const [name, setName] = useState("");
  const [amount, setAmount] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    onAddExpense({ name, amount: Number(amount), id: Date.now() });
    setName("");
    setAmount("");
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="Name" />
      <input value={amount} onChange={(e) => setAmount(e.target.value)} placeholder="Amount" />
      <button>Add</button>
    </form>
  );
}

function ExpenseList({ expenses }) {
  return (
    <ul>
      {expenses.map((e) => (
        <li key={e.id}>
          {e.name}: ${e.amount}
        </li>
      ))}
    </ul>
  );
}

function ExpenseTotal({ total }) {
  return <h3>Total: ${total}</h3>;
}

💡 Key Takeaways

ConceptWhat It MeansExample
Lifting State UpMoving state to the closest common parentitems moved from Form to App
Common ParentFirst ancestor shared by all components that need the stateApp is parent of Form and PackingList
Props DownParent passes data to childrenApp passes items to PackingList
Functions DownParent passes updater functions to childrenApp passes onAddItems to Form
Inverse Data FlowChild calls parent's function to update stateForm calls onAddItems(newItem)
One Source of TruthState lives in ONE place onlyitems only in App, nowhere else
Local StateOnly one component needs itForm inputs (description, quantity)
Derived StateCalculate from existing statenumPacked = items.filter(...).length

The Lifting State Up Pattern:

function LiftingStatePattern() {
  // Step 1: Find the sibling components that need the same state
  // Step 2: Find their closest common parent
  // Step 3: Move useState to that parent
  // Step 4: Pass the STATE down to components that need to READ it
  // Step 5: Pass the SETTER FUNCTION down to components that need to CHANGE it
  // Step 6: Child calls the function → Parent updates → Everyone re-renders
}

Golden Rules:

  1. State should live in the lowest common ancestor of all components that need it
  2. Pass data down via props — never try to pass sideways between siblings
  3. Pass functions down to let children update parent state — this is the "inverse data flow"
  4. Keep local state local — only lift when a sibling needs it too
  5. One source of truth — never duplicate the same state in two components
  6. Props only flow down — Parent → Child, never reverse directly!
  7. If siblings need to share, lift it up — Move to the living room!

One Sentence Summary:

> "Lifting state up in React means moving a piece of state from a child component to its closest common parent component when multiple sibling components need to access or modify that same state, then passing the state down as props to components that need to read it and passing updater functions down as props to components that need to modify it, ensuring there is only one source of truth that flows downward through the component tree while allowing children to communicate changes back up through function calls!"