▢️ Live demo

Try it yourself β€” interact with the example below.

Loading demo…

🎯 What Is Lifting State Up?

Imagine you and your friend at a restaurant splitting a bill:

WITHOUT LIFTING STATE UP (Everyone Tracks Their Own):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  🍽️ THE RESTAURANT (Your App)           β”‚
β”‚                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚ You         β”‚  β”‚ Your Friend  β”‚      β”‚
β”‚  β”‚             β”‚  β”‚             β”‚      β”‚
β”‚  β”‚ Bill: $100  β”‚  β”‚ Bill: ???   β”‚      β”‚
β”‚  β”‚ Tip: 10%    β”‚  β”‚ Tip: 0%     β”‚      β”‚
β”‚  β”‚             β”‚  β”‚             β”‚      β”‚
β”‚  β”‚ You can't   β”‚  β”‚ Friend can'tβ”‚      β”‚
β”‚  β”‚ see friend'sβ”‚  β”‚ see yours!  β”‚      β”‚
β”‚  β”‚ tip!        β”‚  β”‚             β”‚      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚                                         β”‚
β”‚  ❌ No one can calculate the total!     β”‚
β”‚  ❌ Each person has their own data!     β”‚
β”‚  ❌ Components can't talk to siblings!  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

WITH LIFTING STATE UP (One Person Holds the Bill):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  🍽️ THE RESTAURANT (Your App)           β”‚
β”‚                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  🧾 THE WAITER (TipCalculator)   β”‚   β”‚
β”‚  β”‚     Holds the bill & all tips!   β”‚   β”‚
β”‚  β”‚     (State lives here!)            β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚              β”‚              β”‚          β”‚
β”‚              β–Ό              β–Ό          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ You         β”‚    β”‚ Your Friend  β”‚   β”‚
β”‚  β”‚ says:       β”‚    β”‚ says:       β”‚   β”‚
β”‚  β”‚ "I tip 10%" β”‚    β”‚ "I tip 0%"  β”‚   β”‚
β”‚  β”‚             β”‚    β”‚             β”‚   β”‚
β”‚  β”‚ You tell    β”‚    β”‚ Friend tellsβ”‚   β”‚
β”‚  β”‚ waiter      β”‚    β”‚ waiter      β”‚   β”‚
β”‚  β”‚ (via props) β”‚    β”‚ (via props) β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚              β”‚              β”‚          β”‚
β”‚              β–Ό              β–Ό          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  🧾 THE WAITER CALCULATES:      β”‚   β”‚
β”‚  β”‚     Average tip = (10+0)/2 = 5% β”‚   β”‚
β”‚  β”‚     Total = $100 + $5 = $105    β”‚   β”‚
β”‚  β”‚     (Derived state!)              β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                         β”‚
β”‚  βœ… Only ONE source of truth!           β”‚
β”‚  βœ… Everyone reports to the waiter!     β”‚
β”‚  βœ… Calculation happens in ONE place!   β”‚
β”‚  βœ… All components stay in sync!        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

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    β”‚
β”‚      β†’ Parent calculates derived values!  β”‚
β”‚      β†’ Everyone stays in sync! βœ“        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⚠️ The Big Problem: "State Trapped in Children"

// ==========================================
// THE "TRAPPED STATE" TRAP (Wrong Way)
// ==========================================

// ❌ WRONG: Each input has its OWN state
function BadTipCalculator() {
  return (
    <div>
      <BadBillInput />        {/* Has bill state */}
      <BadSelectPercentage /> {/* Has percentage1 state */}
      <BadSelectPercentage /> {/* Has percentage2 state */}
      {/* No one can calculate the total! */}
    </div>
  );
}

function BadBillInput() {
  const [bill, setBill] = useState(''); // ← Trapped here!

  return (
    <div>
      <label>How much was the bill?</label>
      <input 
        value={bill}
        onChange={(e) => setBill(e.target.value)}
      />
    </div>
  );
}

function BadSelectPercentage() {
  const [percentage, setPercentage] = useState(0); // ← Trapped here!

  return (
    <div>
      <label>How did you like the service?</label>
      <select 
        value={percentage}
        onChange={(e) => setPercentage(Number(e.target.value))}
      >
        <option value="0">Dissatisfied (0%)</option>
        <option value="5">It was okay (5%)</option>
        <option value="10">It was good (10%)</option>
        <option value="20">Absolutely amazing (20%)</option>
      </select>
    </div>
  );
}

// What happens:
// 1. User types $100 in BadBillInput
// 2. $100 is trapped in BadBillInput's state
// 3. BadTipCalculator (parent) has NO IDEA what the bill is!
// 4. User selects 10% in first BadSelectPercentage
// 5. 10% is trapped in that component's state
// 6. User selects 0% in second BadSelectPercentage
// 7. 0% is trapped in that component's state
// 8. NO ONE can calculate: $100 + average tip!
// 9. Output component can't display anything!

// Problems:
// 1. State is trapped in children
// 2. Parent can't calculate the total
// 3. Siblings can't share data
// 4. No single source of truth

// ==========================================
// THE SOLUTION: Lift State Up to Parent
// ==========================================

// βœ… CORRECT: State in parent, passed down to children
function GoodTipCalculator() {
  // 🏠 STATE LIVES IN PARENT (TipCalculator)
  const [bill, setBill] = useState('');           // Bill amount
  const [percentage1, setPercentage1] = useState(0); // My tip %
  const [percentage2, setPercentage2] = useState(0); // Friend's tip %

  // Derived state - calculated from existing state!
  const tip = bill * ((percentage1 + percentage2) / 2 / 100);
  const total = Number(bill) + tip;

  return (
    <div>
      <BillInput 
        bill={bill}           // ← Pass state DOWN (read)
        onSetBill={setBill}   // ← Pass setter DOWN (write)
      />
      
      <SelectPercentage 
        percentage={percentage1}  // ← Pass state DOWN
        onSelect={setPercentage1} // ← Pass setter DOWN
      >
        How did you like the service?
      </SelectPercentage>
      
      <SelectPercentage 
        percentage={percentage2}  // ← Pass state DOWN
        onSelect={setPercentage2} // ← Pass setter DOWN
      >
        How did your friend like the service?
      </SelectPercentage>
      
      {/* Output reads from parent's derived state */}
      <Output bill={bill} tip={tip} total={total} />
      
      {/* Reset calls parent's state setters */}
      <Reset onReset={handleReset} />
    </div>
  );
}

πŸ“‹ Complete Visual Examples

The Data Flow:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  TipCalculator (Parent)                 β”‚
β”‚  const [bill, setBill] = useState('')   β”‚
β”‚  const [p1, setP1] = useState(0)         β”‚
β”‚  const [p2, setP2] = useState(0)         β”‚
β”‚                                         β”‚
β”‚  const tip = bill * ((p1+p2)/2/100)     β”‚
β”‚  const total = bill + tip                β”‚
β”‚                                         β”‚
β”‚  β”‚                                      β”‚
β”‚  β”œβ”€β”€β”€ bill={bill} ───────► BillInput   β”‚
β”‚  β”‚    onSetBill={setBill} ───► "Show $100β”‚
β”‚  β”‚                            User types β”‚
β”‚  β”‚                            Tell parentβ”‚
β”‚  β”‚                                      β”‚
β”‚  β”œβ”€β”€β”€ percentage={p1} ───► Select%     β”‚
β”‚  β”‚    onSelect={setP1} ────► "Show 10%  β”‚
β”‚  β”‚                            User picks β”‚
β”‚  β”‚                            Tell parentβ”‚
β”‚  β”‚                                      β”‚
β”‚  β”œβ”€β”€β”€ percentage={p2} ───► Select%     β”‚
β”‚  β”‚    onSelect={setP2} ────► "Show 0%   β”‚
β”‚  β”‚                            User picks β”‚
β”‚  β”‚                            Tell parentβ”‚
β”‚  β”‚                                      β”‚
β”‚  β”œβ”€β”€β”€ bill={bill} ───────► Output      β”‚
β”‚  β”‚    tip={tip} ──────────► "Show $105  β”‚
β”‚  β”‚    total={total}                    β”‚
β”‚  β”‚                                      β”‚
β”‚  └─── onReset={handleReset} ──► Reset  β”‚
β”‚       "Clear everything!"               β”‚
β”‚                                         β”‚
β”‚  User types $100:                       β”‚
β”‚  1. BillInput calls onSetBill(100)      β”‚
β”‚  2. Parent's setBill(100) runs          β”‚
β”‚  3. Parent re-renders with bill=100     β”‚
β”‚  4. All children receive new bill prop    β”‚
β”‚  5. Output recalculates: tip = $5        β”‚
β”‚  6. Output shows: "You pay $105"        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸš€ Complete Working Code

import { useState } from 'react';

// ==========================================
// PARENT COMPONENT - The "Brain"
// ==========================================

function TipCalculator() {
  // 🏠 ALL STATE LIVES IN PARENT (lifted up from children)
  const [bill, setBill] = useState('');              // Bill amount (starts empty)
  const [percentage1, setPercentage1] = useState(0); // My tip % (starts 0)
  const [percentage2, setPercentage2] = useState(0);   // Friend's tip % (starts 0)

  // Derived state - calculated from existing state (NOT stored separately!)
  const tip = bill * ((percentage1 + percentage2) / 2 / 100);
  const total = Number(bill) + tip;

  // Reset all values back to initial state
  function handleReset() {
    setBill('');
    setPercentage1(0);
    setPercentage2(0);
  }

  return (
    <div className="container py-5" style={{ maxWidth: '500px' }}>
      <h2 className="mb-4">🧾 Tip Calculator</h2>
      
      {/* Bill Input - receives value and updater */}
      <BillInput bill={bill} onSetBill={setBill} />
      
      {/* Select Percentage - reused twice with different text! */}
      <SelectPercentage 
        percentage={percentage1} 
        onSelect={setPercentage1}
      >
        How did you like the service?
      </SelectPercentage>
      
      <SelectPercentage 
        percentage={percentage2} 
        onSelect={setPercentage2}
      >
        How did your friend like the service?
      </SelectPercentage>
      
      {/* Only show output and reset if bill > 0 */}
      {bill > 0 && (
        <>
          <Output bill={Number(bill)} tip={tip} total={total} />
          <Reset onReset={handleReset} />
        </>
      )}
    </div>
  );
}

// ==========================================
// CHILD COMPONENT 1: Bill Input
// ==========================================

function BillInput({ bill, onSetBill }) {
  return (
    <div className="mb-3">
      <label className="form-label fw-bold">How much was the bill?</label>
      <input
        type="text"
        className="form-control"
        placeholder="Bill value"
        value={bill}  // Controlled by parent
        onChange={(e) => onSetBill(Number(e.target.value))}  // Tell parent to update
      />
    </div>
  );
}

// ==========================================
// CHILD COMPONENT 2: Select Percentage (REUSABLE!)
// ==========================================

function SelectPercentage({ children, percentage, onSelect }) {
  return (
    <div className="mb-3">
      <label className="form-label fw-bold">{children}</label>
      <select 
        className="form-select"
        value={percentage}  // Controlled by parent
        onChange={(e) => onSelect(Number(e.target.value))}  // Tell parent to update
      >
        <option value="0">Dissatisfied (0%)</option>
        <option value="5">It was okay (5%)</option>
        <option value="10">It was good (10%)</option>
        <option value="20">Absolutely amazing (20%)</option>
      </select>
    </div>
  );
}

// ==========================================
// CHILD COMPONENT 3: Output Display
// ==========================================

function Output({ bill, tip, total }) {
  return (
    <div className="alert alert-info mb-3">
      <h5 className="mb-0">
        You pay ${total} (${bill} + ${tip} tip)
      </h5>
    </div>
  );
}

// ==========================================
// CHILD COMPONENT 4: Reset Button
// ==========================================

function Reset({ onReset }) {
  return (
    <button className="btn btn-warning w-100" onClick={onReset}>
      Reset
    </button>
  );
}

export default TipCalculator;

🧠 Memory Aids for Poor Logic Thinking

The "Restaurant Waiter" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  LIFTED STATE = THE WAITER HOLDS THE CHECK       β”‚
β”‚                                                 β”‚
β”‚  BEFORE (Everyone has their own receipt):         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  You: "I owe $100" πŸ“„                  β”‚    β”‚
β”‚  β”‚  Friend: "I owe... wait, what?"          β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You: "I want to tip 10%"               β”‚    β”‚
β”‚  β”‚  Friend: "I want to tip 0%"             β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  NO ONE knows the total!               β”‚    β”‚
β”‚  β”‚  Can't calculate tip!                  β”‚    β”‚
β”‚  β”‚  Can't split the bill!                 β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  AFTER (Waiter holds everything):               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  🧾 WAITER (TipCalculator)              β”‚    β”‚
β”‚  β”‚     Holds: bill, tip1, tip2             β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You tell waiter: "Bill is $100"       β”‚    β”‚
β”‚  β”‚  β†’ Waiter writes it down (setBill)      β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You tell waiter: "I tip 10%"          β”‚    β”‚
β”‚  β”‚  β†’ Waiter writes it down (setP1)        β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Friend tells waiter: "I tip 0%"       β”‚    β”‚
β”‚  β”‚  β†’ Waiter writes it down (setP2)        β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Waiter calculates:                     β”‚    β”‚
β”‚  β”‚    Average = (10 + 0) / 2 = 5%         β”‚    β”‚
β”‚  β”‚    Tip = $100 Γ— 5% = $5                β”‚    β”‚
β”‚  β”‚    Total = $100 + $5 = $105            β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Waiter announces: "You pay $105!"     β”‚    β”‚
β”‚  β”‚  β†’ Everyone hears the same number!     β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  You = BillInput + SelectPercentage            β”‚
β”‚  Friend = SelectPercentage                     β”‚
β”‚  Waiter = TipCalculator (parent with state)    β”‚
β”‚  Announcement = Output component               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The "Bank Account" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  CONTROLLED COMPONENTS = BANK TELLER SYSTEM      β”‚
β”‚                                                 β”‚
β”‚  You (the input) want to withdraw money:      β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  πŸ’° THE BANK (React State)              β”‚    β”‚
β”‚  β”‚     balance = $1000                     β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You fill out form: "I want $500"      β”‚    β”‚
β”‚  β”‚  β†’ Form shows $500 (value={balance})   β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  BUT the bank hasn't approved it yet!   β”‚    β”‚
β”‚  β”‚  β†’ onChange tells bank: "User wants $500"β”‚   β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Bank updates: balance = $500          β”‚    β”‚
β”‚  β”‚  β†’ Form now shows $500 (new value prop) β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  The form CANNOT change by itself!     β”‚    β”‚
β”‚  β”‚  It MUST ask the bank (state) first!   β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  value={state} + onChange={setState}   β”‚    β”‚
β”‚  β”‚  = CONTROLLED COMPONENT                  β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  Uncontrolled = You keep cash under mattress   β”‚
β”‚  Controlled = Money in bank, you use debit card β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The "Derived State" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  DERIVED STATE = RECIPE INGREDIENTS              β”‚
β”‚                                                 β”‚
β”‚  You have ingredients (state):                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  πŸ₯š INGREDIENTS (State)                 β”‚    β”‚
β”‚  β”‚     flour = 2 cups                      β”‚    β”‚
β”‚  β”‚     eggs = 3                            β”‚    β”‚
β”‚  β”‚     sugar = 1 cup                       β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You DON'T store "cake" separately!    β”‚    β”‚
β”‚  β”‚  You bake it when needed!              β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  const cake = bake(flour, eggs, sugar) β”‚    β”‚
β”‚  β”‚  β†’ Calculate on the fly!                 β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  If flour changes β†’ cake recalculates  β”‚    β”‚
β”‚  β”‚  If eggs change β†’ cake recalculates    β”‚    β”‚
β”‚  β”‚  ALWAYS correct! NEVER out of sync!    β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  In React:                                      β”‚
β”‚  const [bill, setBill] = useState(100)         β”‚
β”‚  const [p1, setP1] = useState(10)              β”‚
β”‚  const [p2, setP2] = useState(0)              β”‚
β”‚                                                 β”‚
β”‚  const tip = bill * ((p1 + p2) / 2 / 100)     β”‚
β”‚  β†’ tip is "baked" from bill, p1, p2            β”‚
β”‚  β†’ No need to store tip in state!              β”‚
β”‚  β†’ Automatically updates when inputs change!   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The "Props Passing" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  PROPS = PASSING NOTES IN CLASS                  β”‚
β”‚                                                 β”‚
β”‚  Teacher (Parent) writes a note:                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  πŸ‘¨β€πŸ« TEACHER (TipCalculator)           β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Note to Student A: "The answer is 42"   β”‚    β”‚
β”‚  β”‚  β†’ bill={42}                            β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Note to Student B: "Tell me if you     β”‚    β”‚
β”‚  β”‚  change your mind"                       β”‚    β”‚
β”‚  β”‚  β†’ onSelect={setPercentage1}              β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Student A reads note: "Oh, answer is 42"β”‚   β”‚
β”‚  β”‚  β†’ const { bill } = props               β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Student B has idea: "I choose 10%!"     β”‚    β”‚
β”‚  β”‚  β†’ Writes back to teacher: onSelect(10)  β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Teacher updates grade book: setP1(10)   β”‚    β”‚
β”‚  β”‚  β†’ Re-calculates class average!          β”‚    β”‚
β”‚  β”‚  β†’ Sends new notes to ALL students!     β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  Props = One-way street (Parent β†’ Child)       β”‚
β”‚  Functions = Return street (Child β†’ Parent)     β”‚
β”‚  Re-render = Teacher sends updated notes!      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The "Children Prop" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  CHILDREN PROP = ENVELOPE WITH CUSTOM LETTER   β”‚
β”‚                                                 β”‚
β”‚  You buy a greeting card (component):           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  πŸ“¨ THE ENVELOPE (SelectPercentage)     β”‚    β”‚
β”‚  β”‚     Same envelope for everyone!          β”‚    β”‚
β”‚  β”‚     Same structure, same stamps!        β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  But the LETTER inside is different!    β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Envelope 1:                            β”‚    β”‚
β”‚  β”‚  <SelectPercentage>                     β”‚    β”‚
β”‚  β”‚    How did you like the service?        β”‚    β”‚
β”‚  β”‚  </SelectPercentage>                    β”‚    β”‚
β”‚  β”‚  β†’ children = "How did you like..."      β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Envelope 2:                            β”‚    β”‚
β”‚  β”‚  <SelectPercentage>                     β”‚    β”‚
β”‚  β”‚    How did your friend like...          β”‚    β”‚
β”‚  β”‚  </SelectPercentage>                    β”‚    β”‚
β”‚  β”‚  β†’ children = "How did your friend..."  β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Same component, different content!      β”‚    β”‚
β”‚  β”‚  Reusable! Flexible! Powerful!           β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  Whatever is between opening and closing tags   β”‚
β”‚  becomes the "children" prop automatically!     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸŽ“ Practice Exercises

Exercise 1: Identify Where State Should Live

Look at this component tree. Where should each piece of state live?

App
β”œβ”€β”€ TipCalculator
β”‚   β”œβ”€β”€ BillInput
β”‚   β”œβ”€β”€ SelectPercentage (You)
β”‚   β”œβ”€β”€ SelectPercentage (Friend)
β”‚   β”œβ”€β”€ Output
β”‚   └── Reset

Questions:

  1. Which component needs to know the bill amount? β†’ Output needs to display it
  2. Are BillInput and Output siblings? β†’ Yes!
  3. What's their common parent? β†’ TipCalculator
  4. Should BillInput have its own bill state? β†’ No! Lift it up!

Solution:

function TipCalculator() {
  // 🏠 STATE LIVES IN PARENT (TipCalculator)
  const [bill, setBill] = useState('');
  const [percentage1, setPercentage1] = useState(0);
  const [percentage2, setPercentage2] = useState(0);

  const tip = bill * ((percentage1 + percentage2) / 2 / 100);

  return (
    <div>
      <BillInput bill={bill} onSetBill={setBill} />
      <SelectPercentage percentage={percentage1} onSelect={setPercentage1}>
        How did you like the service?
      </SelectPercentage>
      <SelectPercentage percentage={percentage2} onSelect={setPercentage2}>
        How did your friend like the service?
      </SelectPercentage>
      <Output bill={bill} tip={tip} />
      <Reset onReset={handleReset} />
    </div>
  );
}

Exercise 2: Fix the Broken Controlled Input

This code has a bug - the input doesn't update when you type. Fix it!

function BillInput({ bill, onSetBill }) {
  return (
    <div>
      <label>How much was the bill?</label>
      <input
        type="text"
        placeholder="Bill value"
        // TODO: Add value and onChange
      />
    </div>
  );
}

Solution:

function BillInput({ bill, onSetBill }) {
  return (
    <div>
      <label>How much was the bill?</label>
      <input
        type="text"
        placeholder="Bill value"
        value={bill}  // βœ… Controlled by parent
        onChange={(e) => onSetBill(Number(e.target.value))}  // βœ… Tell parent
      />
    </div>
  );
}

Exercise 3: Build a Simple Counter with Lifted State

Build a counter where two buttons share the same count:

function App() {
  // TODO: Add state for count
  // TODO: Pass count and setter to both buttons
  
  return (
    <div>
      {/* Your code here */}
    </div>
  );
}

// Two CounterButton components that share state
function CounterButton({ label, count, onIncrement }) {
  return (
    <button onClick={onIncrement}>
      {label}: {count}
    </button>
  );
}

Solution:

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);

  function handleIncrement() {
    setCount(count + 1);
  }

  return (
    <div>
      <CounterButton 
        label="Button A" 
        count={count} 
        onIncrement={handleIncrement} 
      />
      <CounterButton 
        label="Button B" 
        count={count} 
        onIncrement={handleIncrement} 
      />
      <p>Total clicks: {count}</p>
    </div>
  );
}

function CounterButton({ label, count, onIncrement }) {
  return (
    <button className="btn btn-primary m-2" onClick={onIncrement}>
      {label}: {count}
    </button>
  );
}

Exercise 4: Add a Third Person to the Tip Calculator

Add a third SelectPercentage for a third friend:

function TipCalculator() {
  const [bill, setBill] = useState('');
  const [percentage1, setPercentage1] = useState(0);
  const [percentage2, setPercentage2] = useState(0);
  // TODO: Add percentage3 state

  // TODO: Update tip calculation for 3 people
  const tip = bill * ((percentage1 + percentage2) / 2 / 100);

  return (
    <div>
      <BillInput bill={bill} onSetBill={setBill} />
      <SelectPercentage percentage={percentage1} onSelect={setPercentage1}>
        How did you like the service?
      </SelectPercentage>
      <SelectPercentage percentage={percentage2} onSelect={setPercentage2}>
        How did your friend like the service?
      </SelectPercentage>
      {/* TODO: Add third SelectPercentage */}
      <Output bill={Number(bill)} tip={tip} />
    </div>
  );
}

Solution:

function TipCalculator() {
  const [bill, setBill] = useState('');
  const [percentage1, setPercentage1] = useState(0);
  const [percentage2, setPercentage2] = useState(0);
  const [percentage3, setPercentage3] = useState(0); // βœ… Added

  // βœ… Updated for 3 people: divide by 3
  const tip = bill * ((percentage1 + percentage2 + percentage3) / 3 / 100);
  const total = Number(bill) + tip;

  return (
    <div>
      <BillInput bill={bill} onSetBill={setBill} />
      <SelectPercentage percentage={percentage1} onSelect={setPercentage1}>
        How did you like the service?
      </SelectPercentage>
      <SelectPercentage percentage={percentage2} onSelect={setPercentage2}>
        How did your friend like the service?
      </SelectPercentage>
      <SelectPercentage percentage={percentage3} onSelect={setPercentage3}>
        How did your other friend like the service?
      </SelectPercentage>
      <Output bill={Number(bill)} tip={tip} total={total} />
    </div>
  );
}

πŸ’‘ Key Takeaways

ConceptWhat It MeansExample
ComponentsReusable building blocksBillInput, SelectPercentage, Output, Reset
PropsPass data down (parent β†’ child)bill={bill}, percentage={p1}
StateComponent memoryconst [bill, setBill] = useState('')
Controlled ComponentsReact owns the input completelyvalue={bill} + onChange={onSetBill}
Lifting State UpMove state to common parentbill moved from BillInput to TipCalculator
Derived StateCalculate from existing stateconst tip = bill * ((p1+p2)/2/100)
Conditional RenderingShow/hide with &&{bill > 0 && <><Output /><Reset /></>}
Children PropContent between tags<SelectPercentage>Text here</SelectPercentage>
ReusabilitySame component, different propsSelectPercentage used twice
Fragment <></>Invisible wrapper for multiple elements<>...</> wraps Output + Reset

The Lifting State Up Pattern:

function Parent() {
  const [sharedState, setSharedState] = useState(initialValue);

  return (
    <div>
      <ChildA 
        value={sharedState}
        onUpdate={setSharedState}
      />
      <ChildB 
        value={sharedState}
        onUpdate={setSharedState}
      />
      <Display derivedValue={calculate(sharedState)} />
    </div>
  );
}

Golden Rules:

  1. One component per piece β€” BillInput, SelectPercentage, Output, Reset
  2. Lift state to common parent β€” When siblings need to share data
  3. Pass state down as props β€” For children to READ
  4. Pass functions down as props β€” For children to WRITE (update)
  5. Use controlled elements β€” Always pair value with onChange
  6. Use derived state β€” Calculate tip from bill and percentages
  7. Use Number() conversion β€” Inputs are strings, math needs numbers
  8. Use && for conditional rendering β€” Show output only when bill > 0
  9. Use <></> fragments β€” Wrap multiple elements without extra HTML
  10. Reuse components β€” Same SelectPercentage with different children

One Sentence Summary: > "The Tip Calculator teaches you to build small reusable components, lift their state up to a common parent so all siblings can stay in sync, pass data down through props and updater functions down through props, use controlled inputs so React manages every keystroke, calculate derived values like tip instead of storing them in state, conditionally render output only when there's a bill to calculate, and use the children prop to make components flexible and reusable β€” all while keeping one single source of truth in the parent component!"