🎯 What Is "Updating State Based on Current State"?

Imagine you're counting money in your wallet:

SIMPLE UPDATE (Not based on current):
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ’° REPLACING MONEY                     β”‚
β”‚                                         β”‚
β”‚  You have: $10                          β”‚
β”‚  Someone gives you: $20                 β”‚
β”‚                                         β”‚
β”‚  New total: $20 (You LOST the $10!)     β”‚
β”‚                                         β”‚
β”‚  setMoney(20)  ← Replaces, doesn't add  β”‚
β”‚                                         β”‚
β”‚  Problem: You ignored what you already  β”‚
β”‚  had! You replaced instead of updated!  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

UPDATE BASED ON CURRENT:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ’° ADDING TO EXISTING MONEY            β”‚
β”‚                                         β”‚
β”‚  You have: $10                          β”‚
β”‚  Someone gives you: $20                 β”‚
β”‚                                         β”‚
β”‚  New total: $10 + $20 = $30             β”‚
β”‚                                         β”‚
β”‚  setMoney(current => current + 20)      β”‚
β”‚                                         β”‚
β”‚  "Take what I have, add $20 to it"      β”‚
β”‚                                         β”‚
β”‚  You used the CURRENT amount to         β”‚
β”‚  calculate the NEW amount!              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

THE BUG THAT BREAKS EVERYTHING:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  πŸ› THE "DOUBLE CLICK" PROBLEM          β”‚
β”‚                                         β”‚
β”‚  Counter: 0                             β”‚
β”‚                                         β”‚
β”‚  You click "Add 1" twice quickly:       β”‚
β”‚                                         β”‚
β”‚  ❌ WRONG WAY:                          β”‚
β”‚  setCount(count + 1)  // sees 0, sets 1 β”‚
β”‚  setCount(count + 1)  // sees 0, sets 1 β”‚
β”‚  Result: 1 (Should be 2!)               β”‚
β”‚                                         β”‚
β”‚  βœ… CORRECT WAY:                        β”‚
β”‚  setCount(c => c + 1) // sees 0, sets 1 β”‚
β”‚  setCount(c => c + 1) // sees 1, sets 2 β”‚
β”‚  Result: 2 (Correct!)                   β”‚
β”‚                                         β”‚
β”‚  The callback "queues up" properly!     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

⚠️ The Big Problem: "It Works Now, But Breaks Later"

// ==========================================
// THE "LOOKS FINE" TRAP
// ==========================================

// ❌ WRONG: Using current state directly
function Steps() {
  const [step, setStep] = useState(1);

  function handleNext() {
    if (step < 3) setStep(step + 1);  // ← Using 'step' directly
  }

  function handlePrevious() {
    if (step > 1) setStep(step - 1);  // ← Using 'step' directly
  }

  return (
    <div>
      <p>Step: {step}</p>
      <button onClick={handlePrevious}>Previous</button>
      <button onClick={handleNext}>Next</button>
    </div>
  );
}

// This WORKS for simple cases!
// But it's a ticking time bomb πŸ’£

// ==========================================
// THE DISASTER: When You Need to Update Twice
// ==========================================

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

  function handleNext() {
    // You come back months later and think:
    // "Let's move forward 2 steps at once!"
    
    if (step < 3) {
      setStep(step + 1);  // ← Both see step = 1
      setStep(step + 1);  // ← Both see step = 1
    }
  }

  return (
    <div>
      <p>Step: {step}</p>
      <button onClick={handleNext}>Next (x2)</button>
    </div>
  );
}

// WHAT YOU EXPECT:
// Start: step = 1
// setStep(1 + 1) β†’ setStep(2)
// setStep(2 + 1) β†’ setStep(3)
// Result: step = 3 βœ“

// WHAT ACTUALLY HAPPENS:
// Start: step = 1
// setStep(1 + 1) β†’ setStep(2)  // Both read step = 1!
// setStep(1 + 1) β†’ setStep(2)  // Both read step = 1!
// Result: step = 2 βœ—

// WHY? React batches updates! Both calls see the OLD value!
// It's like two people both looking at a sign that says "1"
// and both writing "2" - nobody saw the other's update!

// ==========================================
// THE SOLUTION: Callback Function
// ==========================================

// βœ… CORRECT: Using callback (arrow function)
function Steps() {
  const [step, setStep] = useState(1);

  function handleNext() {
    if (step < 3) {
      setStep(s => s + 1);  // ← Callback! Receives CURRENT value
      setStep(s => s + 1);  // ← Sees the UPDATED value!
    }
  }

  return (
    <div>
      <p>Step: {step}</p>
      <button onClick={handleNext}>Next (x2)</button>
    </div>
  );
}

// WHAT HAPPENS NOW:
// Start: step = 1
// setStep(1 => 1 + 1) β†’ setStep(2)  // React queues this
// setStep(2 => 2 + 1) β†’ setStep(3)  // Sees the queued update!
// Result: step = 3 βœ“

// The callback "waits in line" and gets the FRESH value!

πŸ“‹ Complete Visual Examples

Create file: react-state-callback-pattern.js

// ==========================================
// STATE CALLBACK PATTERN - Complete Guide
// ==========================================

/*
CALLBACK PATTERN:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  When updating based on current value:  β”‚
β”‚                                          β”‚
β”‚  ❌ DON'T: setCount(count + 1)          β”‚
β”‚     ↑ Uses stale value                  β”‚
β”‚                                          β”‚
β”‚  βœ… DO:    setCount(c => c + 1)         β”‚
β”‚     ↑ Uses fresh value                  β”‚
β”‚                                          β”‚
β”‚  The callback receives the CURRENT      β”‚
β”‚  state value as its argument!           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

WHY CALLBACKS ARE SAFE:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Direct Value (Unsafe):                 β”‚
β”‚  setStep(step + 1)                      β”‚
β”‚  β†’ "Use whatever 'step' is RIGHT NOW"   β”‚
β”‚  β†’ Might be outdated!                   β”‚
β”‚                                          β”‚
β”‚  Callback (Safe):                       β”‚
β”‚  setStep(s => s + 1)                    β”‚
β”‚  β†’ "React, tell me the LATEST value"    β”‚
β”‚  β†’ Always fresh!                        β”‚
β”‚  β†’ Can queue multiple updates!          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
*/

// ==========================================
// EXAMPLE 1: The Classic Counter Bug
// ==========================================

import { useState } from 'react';

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

  // ❌ WRONG: Direct state access
  function handleWrongClick() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    // All three see count = 0
    // All three set count to 1
    // Result: 1 (NOT 3!)
  }

  // βœ… CORRECT: Callback function
  function handleCorrectClick() {
    setCount(c => c + 1);  // sees 0, sets 1
    setCount(c => c + 1);  // sees 1, sets 2
    setCount(c => c + 1);  // sees 2, sets 3
    // Result: 3 βœ“
  }

  return (
    <div>
      <h2>Count: {count}</h2>
      
      <button onClick={handleWrongClick}>
        Wrong Way (+1 three times)
      </button>
      {/* Clicking this: 0 β†’ 1 (Disaster!) */}
      
      <button onClick={handleCorrectClick}>
        Correct Way (+1 three times)
      </button>
      {/* Clicking this: 0 β†’ 3 (Perfect!) */}
    </div>
  );
}

// Visual Flow - WRONG WAY:
// count = 0
//    ↓
// setCount(count + 1) β†’ setCount(0 + 1) β†’ setCount(1)
//    ↑ Uses count = 0 (stale!)
// setCount(count + 1) β†’ setCount(0 + 1) β†’ setCount(1)
//    ↑ Uses count = 0 (stale!)
// setCount(count + 1) β†’ setCount(0 + 1) β†’ setCount(1)
//    ↑ Uses count = 0 (stale!)
// Result: count = 1 βœ—

// Visual Flow - CORRECT WAY:
// count = 0
//    ↓
// setCount(c => c + 1) β†’ callback receives 0 β†’ returns 1
//    ↓ React queues update, next callback sees 1
// setCount(c => c + 1) β†’ callback receives 1 β†’ returns 2
//    ↓ React queues update, next callback sees 2
// setCount(c => c + 1) β†’ callback receives 2 β†’ returns 3
// Result: count = 3 βœ“

// ==========================================
// EXAMPLE 2: Steps Component (Fixed)
// ==========================================

function StepsFixed() {
  const [step, setStep] = useState(1);
  const [isOpen, setIsOpen] = useState(true);

  const messages = [
    "Learn React βš›οΈ",
    "Apply for jobs πŸ’Ό",
    "Invest your new income πŸ€‘"
  ];

  function handlePrevious() {
    // βœ… CALLBACK: Receives current step, returns new step
    setStep(s => Math.max(1, s - 1));
    // "Take current step (s), subtract 1, but don't go below 1"
  }

  function handleNext() {
    // βœ… CALLBACK: Receives current step, returns new step
    setStep(s => Math.min(3, s + 1));
    // "Take current step (s), add 1, but don't go above 3"
  }

  return (
    <>
      <button className="close" onClick={() => setIsOpen(open => !open)}>
        &times;
      </button>
      {/* βœ… CALLBACK for toggle too! */}
      {/* setIsOpen(open => !open) */}
      {/* "Take current open state, flip it" */}

      {isOpen && (
        <div className="steps">
          <div className="numbers">
            <div className={step >= 1 ? 'active' : ''}>1</div>
            <div className={step >= 2 ? 'active' : ''}>2</div>
            <div className={step >= 3 ? 'active' : ''}>3</div>
          </div>

          <p className="message">
            Step {step}: {messages[step - 1]}
          </p>

          <div className="buttons">
            <button onClick={handlePrevious}>Previous</button>
            <button onClick={handleNext}>Next</button>
          </div>
        </div>
      )}
    </>
  );
}

// ==========================================
// EXAMPLE 3: When NOT to Use Callback
// ==========================================

function FormExample() {
  const [name, setName] = useState("");

  function handleChange(e) {
    // ❌ DON'T need callback - not based on current state!
    setName(e.target.value);
    // "Set name to whatever user typed"
    // Not using current name at all!
  }

  const [user, setUser] = useState({ name: "Alice", age: 25 });

  function updateName(newName) {
    // ❌ DON'T need callback - replacing entire object
    setUser({ name: newName, age: 25 });
    // "Create brand new object"
    // Not using current user object!
  }

  function incrementAge() {
    // βœ… NEED callback - based on current age!
    setUser(u => ({ ...u, age: u.age + 1 }));
    // "Take current user (u), keep everything same,
    //  but increase age by 1"
  }

  return (
    <div>
      <input value={name} onChange={handleChange} />
      <button onClick={() => updateName("Bob")}>Change Name</button>
      <button onClick={incrementAge}>Increment Age</button>
    </div>
  );
}

// RULE OF THUMB:
// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
// β”‚  Are you using the CURRENT value to     β”‚
//  calculate the NEW value?                 β”‚
//  β”‚                                          β”‚
//  β”‚  YES β†’ Use callback: setX(x => x + 1)   β”‚
//  β”‚  NO  β†’ Use direct:   setX(newValue)     β”‚
//  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

// ==========================================
// EXAMPLE 4: Multiple Updates in Event Handler
// ==========================================

function ShoppingCart() {
  const [items, setItems] = useState(0);
  const [totalPrice, setTotalPrice] = useState(0);

  function addItem(price) {
    // βœ… Both use callbacks because they depend on current values
    setItems(currentItems => currentItems + 1);
    setTotalPrice(currentTotal => currentTotal + price);
  }

  function removeItem(price) {
    setItems(currentItems => Math.max(0, currentItems - 1));
    setTotalPrice(currentTotal => Math.max(0, currentTotal - price));
  }

  return (
    <div>
      <p>Items in cart: {items}</p>
      <p>Total: ${totalPrice}</p>
      <button onClick={() => addItem(10)}>Add $10 Item</button>
      <button onClick={() => removeItem(10)}>Remove $10 Item</button>
    </div>
  );
}

πŸš€ Interactive React Usage Examples

Complete React File: StateCallbackMasterClass.jsx

import React, { useState } from 'react';

// ==========================================
// INTERACTIVE STATE CALLBACK PATTERN DEMO
// ==========================================

function StateCallbackMasterClass() {
  const [activeDemo, setActiveDemo] = useState('counter');

  const demos = {
    counter: { title: 'The +3 Bug', component: <CounterBugDemo /> },
    steps: { title: 'Steps Component', component: <StepsDemo /> },
    toggle: { title: 'Toggle Pattern', component: <ToggleDemo /> },
    rules: { title: 'When to Use What', component: <RulesDemo /> }
  };

  return (
    <div style={{ maxWidth: '900px', margin: '0 auto', padding: '20px', fontFamily: 'Arial, sans-serif' }}>
      <header style={{ background: '#e76f51', color: 'white', padding: '30px', borderRadius: '10px', marginBottom: '30px' }}>
        <h1 style={{ margin: 0 }}>πŸ”„ State Callback Pattern</h1>
        <p style={{ margin: '10px 0 0 0', opacity: 0.9 }}>Updating State Based on Current State</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 ? '#264653' : '#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 Infamous +3 Bug
// ==========================================

function CounterBugDemo() {
  const [wrongCount, setWrongCount] = useState(0);
  const [correctCount, setCorrectCount] = useState(0);

  function handleWrong() {
    // ❌ WRONG: All three see wrongCount = 0
    setWrongCount(wrongCount + 1);
    setWrongCount(wrongCount + 1);
    setWrongCount(wrongCount + 1);
  }

  function handleCorrect() {
    // βœ… CORRECT: Each sees the updated value
    setCorrectCount(c => c + 1);
    setCorrectCount(c => c + 1);
    setCorrectCount(c => c + 1);
  }

  function reset() {
    setWrongCount(0);
    setCorrectCount(0);
  }

  return (
    <div>
      <h2>The "Add 3" Bug πŸ›</h2>
      
      <div style={{ marginBottom: '20px', padding: '15px', background: '#fff3e0', borderRadius: '8px' }}>
        <h4>What happens when we call setState 3 times?</h4>
        <p>Both counters start at 0. Click the buttons and see the difference!</p>
      </div>

      <div style={{ display: 'grid', gap: '20px', gridTemplateColumns: '1fr 1fr' }}>
        {/* WRONG WAY */}
        <div style={{ padding: '20px', background: '#ffebee', borderRadius: '8px', border: '2px solid #ef5350' }}>
          <h3 style={{ color: '#c62828' }}>❌ Wrong Way</h3>
          <pre style={{ background: '#1e1e1e', color: '#d4d4d4', padding: '15px', borderRadius: '4px', fontSize: '12px' }}>
{`setCount(count + 1)
setCount(count + 1)
setCount(count + 1)`}
          </pre>
          <div style={{ fontSize: '48px', fontWeight: 'bold', color: '#c62828', textAlign: 'center', margin: '20px 0' }}>
            {wrongCount}
          </div>
          <button 
            onClick={handleWrong}
            style={{ width: '100%', padding: '15px', background: '#ef5350', color: 'white', border: 'none', borderRadius: '5px', fontSize: '16px' }}
          >
            Click Me (Should add 3)
          </button>
          <p style={{ color: '#c62828', fontSize: '14px', marginTop: '10px', textAlign: 'center' }}>
            {wrongCount === 1 ? "Only added 1! Bug! πŸ›" : ""}
          </p>
        </div>

        {/* CORRECT WAY */}
        <div style={{ padding: '20px', background: '#e8f5e9', borderRadius: '8px', border: '2px solid #66bb6a' }}>
          <h3 style={{ color: '#2e7d32' }}>βœ… Correct Way</h3>
          <pre style={{ background: '#1e1e1e', color: '#d4d4d4', padding: '15px', borderRadius: '4px', fontSize: '12px' }}>
{`setCount(c => c + 1)
setCount(c => c + 1)
setCount(c => c + 1)`}
          </pre>
          <div style={{ fontSize: '48px', fontWeight: 'bold', color: '#2e7d32', textAlign: 'center', margin: '20px 0' }}>
            {correctCount}
          </div>
          <button 
            onClick={handleCorrect}
            style={{ width: '100%', padding: '15px', background: '#66bb6a', color: 'white', border: 'none', borderRadius: '5px', fontSize: '16px' }}
          >
            Click Me (Adds 3 correctly)
          </button>
          <p style={{ color: '#2e7d32', fontSize: '14px', marginTop: '10px', textAlign: 'center' }}>
            {correctCount === 3 ? "Perfect! Added 3! βœ…" : ""}
          </p>
        </div>
      </div>

      <div style={{ marginTop: '20px', textAlign: 'center' }}>
        <button onClick={reset} style={{ padding: '10px 30px', background: '#78909c', color: 'white', border: 'none', borderRadius: '5px' }}>
          Reset Both Counters
        </button>
      </div>

      <div style={{ marginTop: '20px', padding: '15px', background: '#e3f2fd', borderRadius: '8px' }}>
        <h4>πŸ” Why Does This Happen?</h4>
        <p><strong>Wrong way:</strong> All three lines read <code>count = 0</code> at the same time. They all calculate <code>0 + 1 = 1</code>. React batches them and only the last one wins.</p>
        <p><strong>Correct way:</strong> Each callback waits its turn. The first gets 0β†’1, the second gets 1β†’2, the third gets 2β†’3. They queue up properly!</p>
      </div>
    </div>
  );
}

// ==========================================
// DEMO 2: Steps with Callbacks
// ==========================================

function StepsDemo() {
  const [step, setStep] = useState(1);
  const [history, setHistory] = useState([]);

  function handlePrevious() {
    const newStep = Math.max(1, step - 1);
    setStep(s => Math.max(1, s - 1));
    setHistory(h => [...h, `Previous: ${step} β†’ ${newStep}`]);
  }

  function handleNext() {
    const newStep = Math.min(3, step + 1);
    setStep(s => Math.min(3, s + 1));
    setHistory(h => [...h, `Next: ${step} β†’ ${newStep}`]);
  }

  function handleDoubleNext() {
    // This ONLY works with callbacks!
    setStep(s => Math.min(3, s + 1));
    setStep(s => Math.min(3, s + 1));
  }

  const messages = [
    "Learn React βš›οΈ",
    "Apply for jobs πŸ’Ό",
    "Invest your new income πŸ€‘"
  ];

  return (
    <div>
      <h2>Steps with Callbacks</h2>
      
      <div style={{ padding: '30px', background: 'white', borderRadius: '10px', boxShadow: '0 2px 10px rgba(0,0,0,0.1)', marginBottom: '20px' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '30px' }}>
          {[1, 2, 3].map(num => (
            <div key={num} style={{
              width: '60px',
              height: '60px',
              borderRadius: '50%',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              fontWeight: 'bold',
              fontSize: '20px',
              backgroundColor: step >= num ? '#7950f2' : '#e0e0e0',
              color: step >= num ? 'white' : '#333',
              transition: 'all 0.3s'
            }}>
              {num}
            </div>
          ))}
        </div>

        <p style={{ textAlign: 'center', fontSize: '24px', marginBottom: '30px' }}>
          Step {step}: {messages[step - 1]}
        </p>

        <div style={{ display: 'flex', gap: '10px', justifyContent: 'center' }}>
          <button
            onClick={handlePrevious}
            disabled={step === 1}
            style={{ padding: '12px 24px', backgroundColor: step === 1 ? '#ccc' : '#7950f2', color: 'white', border: 'none', borderRadius: '5px', cursor: step === 1 ? 'not-allowed' : 'pointer' }}
          >
            Previous
          </button>
          <button
            onClick={handleNext}
            disabled={step === 3}
            style={{ padding: '12px 24px', backgroundColor: step === 3 ? '#ccc' : '#7950f2', color: 'white', border: 'none', borderRadius: '5px', cursor: step === 3 ? 'not-allowed' : 'pointer' }}
          >
            Next
          </button>
          <button
            onClick={handleDoubleNext}
            disabled={step >= 2}
            style={{ padding: '12px 24px', backgroundColor: step >= 2 ? '#ccc' : '#e76f51', color: 'white', border: 'none', borderRadius: '5px', cursor: step >= 2 ? 'not-allowed' : 'pointer' }}
          >
            +2 Steps (Callback Magic!)
          </button>
        </div>
      </div>

      <div style={{ padding: '15px', background: '#f5f5f5', borderRadius: '8px' }}>
        <h4>Code Behind the Magic:</h4>
        <pre style={{ background: '#1e1e1e', color: '#d4d4d4', padding: '15px', borderRadius: '4px', fontSize: '13px' }}>
{`// +2 Steps ONLY works with callbacks!
setStep(s => Math.min(3, s + 1));  // First +1
setStep(s => Math.min(3, s + 1));  // Second +1
// Second call sees the result of the first!`}
        </pre>
      </div>
    </div>
  );
}

// ==========================================
// DEMO 3: Toggle Pattern Deep Dive
// ==========================================

function ToggleDemo() {
  const [isOn, setIsOn] = useState(false);

  // ❌ WRONG: Direct toggle
  function wrongToggle() {
    setIsOn(!isOn);  // Might use stale value!
  }

  // βœ… CORRECT: Callback toggle
  function correctToggle() {
    setIsOn(current => !current);  // Always fresh!
  }

  return (
    <div>
      <h2>The Toggle Pattern</h2>
      
      <div style={{ display: 'grid', gap: '20px', gridTemplateColumns: '1fr 1fr' }}>
        <div style={{ padding: '20px', background: '#ffebee', borderRadius: '8px' }}>
          <h3>❌ Risky Toggle</h3>
          <pre style={{ background: '#1e1e1e', color: '#d4d4d4', padding: '15px', borderRadius: '4px', fontSize: '13px' }}>
{`setIsOn(!isOn)`}
          </pre>
          <button 
            onClick={wrongToggle}
            style={{ padding: '20px 40px', fontSize: '18px', background: isOn ? '#ef5350' : '#66bb6a', color: 'white', border: 'none', borderRadius: '10px' }}
          >
            {isOn ? 'ON' : 'OFF'}
          </button>
          <p style={{ color: '#666', fontSize: '14px', marginTop: '10px' }}>
            Works 99% of the time, but can fail in rapid clicks!
          </p>
        </div>

        <div style={{ padding: '20px', background: '#e8f5e9', borderRadius: '8px' }}>
          <h3>βœ… Safe Toggle</h3>
          <pre style={{ background: '#1e1e1e', color: '#d4d4d4', padding: '15px', borderRadius: '4px', fontSize: '13px' }}>
{`setIsOn(current => !current)`}
          </pre>
          <button 
            onClick={correctToggle}
            style={{ padding: '20px 40px', fontSize: '18px', background: isOn ? '#ef5350' : '#66bb6a', color: 'white', border: 'none', borderRadius: '10px' }}
          >
            {isOn ? 'ON' : 'OFF'}
          </button>
          <p style={{ color: '#666', fontSize: '14px', marginTop: '10px' }}>
            Always correct, even with rapid clicks!
          </p>
        </div>
      </div>

      <div style={{ marginTop: '20px', padding: '15px', background: '#fff3e0', borderRadius: '8px' }}>
        <h4>🧠 Memory Trick:</h4>
        <p>Think of it like a light switch:</p>
        <ul>
          <li><strong>!isOn</strong> = "I think the light is off, so flip it" (Might be wrong!)</li>
          <li><strong>current => !current</strong> = "Check the ACTUAL state, then flip it" (Always right!)</li>
        </ul>
      </div>
    </div>
  );
}

// ==========================================
// DEMO 4: Rules & Decision Tree
// ==========================================

function RulesDemo() {
  const [scenario, setScenario] = useState('direct');

  const scenarios = {
    direct: {
      title: 'Direct Value (No Callback)',
      example: `setName("Alice")
setAge(25)
setUser({ name: "Bob" })`,
      when: 'Setting to a completely new value that does NOT depend on current state',
      color: '#2196f3'
    },
    callback: {
      title: 'Callback Function',
      example: `setCount(c => c + 1)
setItems(i => i + 1)
setOpen(o => !o)`,
      when: 'New value DEPENDS on current value (increment, toggle, append, etc.)',
      color: '#4caf50'
    }
  };

  const current = scenarios[scenario];

  return (
    <div>
      <h2>When to Use What? πŸ€”</h2>
      
      <div style={{ display: 'flex', gap: '10px', marginBottom: '20px' }}>
        <button 
          onClick={() => setScenario('direct')}
          style={{ padding: '10px 20px', background: scenario === 'direct' ? '#2196f3' : '#e0e0e0', color: scenario === 'direct' ? 'white' : '#333', border: 'none', borderRadius: '5px' }}
        >
          Direct Value
        </button>
        <button 
          onClick={() => setScenario('callback')}
          style={{ padding: '10px 20px', background: scenario === 'callback' ? '#4caf50' : '#e0e0e0', color: scenario === 'callback' ? 'white' : '#333', border: 'none', borderRadius: '5px' }}
        >
          Callback
        </button>
      </div>

      <div style={{ padding: '30px', background: 'white', borderRadius: '10px', border: `3px solid ${current.color}` }}>
        <h3 style={{ color: current.color, marginTop: 0 }}>{current.title}</h3>
        
        <div style={{ background: '#1e1e1e', color: '#d4d4d4', padding: '20px', borderRadius: '8px', marginBottom: '20px' }}>
          <pre style={{ margin: 0, fontSize: '16px' }}>{current.example}</pre>
        </div>

        <div style={{ padding: '15px', background: '#f5f5f5', borderRadius: '8px' }}>
          <h4 style={{ marginTop: 0 }}>Use When:</h4>
          <p style={{ fontSize: '18px', margin: 0 }}>{current.when}</p>
        </div>
      </div>

      <div style={{ marginTop: '20px', padding: '20px', background: '#e3f2fd', borderRadius: '8px' }}>
        <h4>🎯 Quick Decision Tree:</h4>
        <div style={{ fontFamily: 'monospace', fontSize: '16px', lineHeight: '2' }}>
          Are you using the current state to calculate the new state?<br/>
          β”œβ”€ YES β†’ Use callback: <code>setX(x => x + 1)</code><br/>
          └─ NO  β†’ Use direct:  <code>setX(newValue)</code>
        </div>
      </div>
    </div>
  );
}

export default StateCallbackMasterClass;

🧠 Memory Aids for Poor Logic Thinking

The "Bank Teller" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  DIRECT STATE UPDATE = RUDE CUSTOMER             β”‚
β”‚                                                 β”‚
β”‚  You walk into a bank and say:                  β”‚
β”‚  "Set my balance to $100!"                      β”‚
β”‚                                                 β”‚
β”‚  Teller: "But sir, you have $50..."             β”‚
β”‚  You: "I DON'T CARE! Set it to $100!"           β”‚
β”‚                                                 β”‚
β”‚  Result: You LOST track of your money!          β”‚
β”‚  The $50 disappeared!                           β”‚
β”‚                                                 β”‚
β”‚  In React:                                      β”‚
β”‚  setBalance(100)  ← Ignores current balance!    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  CALLBACK UPDATE = SMART CUSTOMER                β”‚
β”‚                                                 β”‚
β”‚  You walk into a bank and say:                  β”‚
β”‚  "Add $50 to my current balance"                β”‚
β”‚                                                 β”‚
β”‚  Teller: "Your current balance is $50"          β”‚
β”‚  You: "Great, add $50 to that"                  β”‚
β”‚                                                 β”‚
β”‚  Result: $50 + $50 = $100 βœ“                     β”‚
β”‚  You USED the current value!                    β”‚
β”‚                                                 β”‚
β”‚  In React:                                      β”‚
β”‚  setBalance(b => b + 50)  ← Uses current!       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The "Line at the DMV" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  WHY CALLBACKS QUEUE PROPERLY                    β”‚
β”‚                                                 β”‚
β”‚  SCENARIO: 3 people need to update a number     β”‚
β”‚                                                 β”‚
β”‚  ❌ WRONG WAY (Direct):                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  Counter shows: 0                       β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Person 1: "I see 0, make it 1"         β”‚    β”‚
β”‚  β”‚  Person 2: "I see 0, make it 1"         β”‚    β”‚
β”‚  β”‚  Person 3: "I see 0, make it 1"         β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  They all looked at the SAME time!       β”‚    β”‚
β”‚  β”‚  Result: 1 (Everyone said 1!)            β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  βœ… CORRECT WAY (Callback):                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  Counter shows: 0                       β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Person 1: "Give me current, I'll add 1"β”‚    β”‚
β”‚  β”‚  β†’ Gets 0, returns 1                     β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Person 2: "Give me current, I'll add 1"β”‚    β”‚
β”‚  β”‚  β†’ Gets 1, returns 2                     β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Person 3: "Give me current, I'll add 1"β”‚    β”‚
β”‚  β”‚  β†’ Gets 2, returns 3                     β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  They WAIT IN LINE!                      β”‚    β”‚
β”‚  β”‚  Result: 3 βœ“                             β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  MEMORY TRICK:                                   β”‚
β”‚  Callback = "Take a number and wait your turn"  β”‚
β”‚  Direct = "Everyone rushes the counter at once" β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The "Photocopy" Analogy

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  STALE STATE = OLD PHOTOCOPY                     β”‚
β”‚                                                 β”‚
β”‚  Direct state access:                            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  You take a PHOTO of the number: 5      β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  πŸ“Έ [Photo: 5]                           β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You wait 5 minutes...                   β”‚    β”‚
β”‚  β”‚  Someone changes the number to 8         β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You look at your PHOTO: "It's 5"        β”‚    β”‚
β”‚  β”‚  You add 1: "5 + 1 = 6"                  β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  But the REAL number is 8!               β”‚    β”‚
β”‚  β”‚  You used an OLD photo (stale state)!    β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  Callback function:                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  You say: "Tell me the CURRENT number"  β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  React: "The current number is 8"        β”‚    β”‚
β”‚  β”‚  You: "8 + 1 = 9"                        β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  You always get the FRESH value!         β”‚    β”‚
β”‚  β”‚  No old photos!                          β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  MEMORY TRICK:                                   β”‚
β”‚  setCount(count + 1) = "Use my old photo"       β”‚
β”‚  setCount(c => c + 1) = "Show me the real thing"β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The "Naming Convention" Guide

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  WHAT TO NAME THE CALLBACK ARGUMENT              β”‚
β”‚                                                 β”‚
β”‚  You can name it ANYTHING, but conventions help: β”‚
β”‚                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  const [count, setCount] = useState(0)  β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  Options:                                β”‚    β”‚
β”‚  β”‚  setCount(count => count + 1)           β”‚    β”‚
β”‚  β”‚     ↑ Same name (can be confusing)      β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  setCount(currentCount => currentCount + 1)β”‚ β”‚
β”‚  β”‚     ↑ Descriptive (verbose)             β”‚    β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  setCount(c => c + 1)                   β”‚    β”‚
β”‚  β”‚     ↑ Abbreviation (most common!)       β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚  const [isOpen, setIsOpen] = useState(true)β”‚  β”‚
β”‚  β”‚                                          β”‚    β”‚
β”‚  β”‚  setIsOpen(open => !open)               β”‚    β”‚
β”‚  β”‚  setIsOpen(o => !o)                     β”‚    β”‚
β”‚  β”‚  setIsOpen(current => !current)          β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                 β”‚
β”‚  GOLDEN RULE:                                    β”‚
β”‚  Use the FIRST LETTER of your state variable!   β”‚
β”‚  count β†’ c, isOpen β†’ o, step β†’ s, name β†’ n      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸŽ“ Practice Exercises

Exercise 1: Fix the Counter

This counter should increment by 2, but it doesn't. Fix it:

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

  function handleClick() {
    // TODO: Fix this to actually add 2
    setCount(count + 1);
    setCount(count + 1);
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>+2</button>
    </div>
  );
}

Solution:

import { useState } from 'react';

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

  function handleClick() {
    // βœ… Use callbacks so each sees the updated value
    setCount(c => c + 1);  // First: 0 β†’ 1
    setCount(c => c + 1);  // Second: 1 β†’ 2
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>+2</button>
    </div>
  );
}

Exercise 2: Create a Safe Toggle

Create a button that toggles between "ON" and "OFF" safely:

function Toggle() {
  // TODO: Add state
  // TODO: Create toggle function with callback
  // TODO: Show ON or OFF

  return (
    <div>
      <button>{/* ON or OFF */}</button>
    </div>
  );
}

Solution:

import { useState } from 'react';

function Toggle() {
  const [isOn, setIsOn] = useState(false);

  function handleToggle() {
    // βœ… Callback ensures we always flip the CURRENT value
    setIsOn(current => !current);
  }

  return (
    <div>
      <button 
        onClick={handleToggle}
        style={{ 
          padding: '20px 40px',
          fontSize: '20px',
          backgroundColor: isOn ? '#4caf50' : '#f44336',
          color: 'white',
          border: 'none',
          borderRadius: '10px'
        }}
      >
        {isOn ? 'ON' : 'OFF'}
      </button>
    </div>
  );
}

Exercise 3: Shopping Cart Add/Remove

Fix this shopping cart to properly add and remove items:

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

  function addItem() {
    // TODO: Fix using callback
    setItems(items + 1);
  }

  function removeItem() {
    // TODO: Fix using callback, don't go below 0
    setItems(items - 1);
  }

  return (
    <div>
      <p>Items: {items}</p>
      <button onClick={addItem}>Add</button>
      <button onClick={removeItem}>Remove</button>
    </div>
  );
}

Solution:

import { useState } from 'react';

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

  function addItem() {
    // βœ… Callback: i is current items, add 1
    setItems(i => i + 1);
  }

  function removeItem() {
    // βœ… Callback: i is current items, subtract 1, min 0
    setItems(i => Math.max(0, i - 1));
  }

  return (
    <div>
      <p>Items: {items}</p>
      <button onClick={addItem}>Add</button>
      <button onClick={removeItem}>Remove</button>
    </div>
  );
}

Exercise 4: Fix the Bug

This code has a bug. Find and fix it:

function ScoreKeeper() {
  const [score, setScore] = useState(0);

  function doubleScore() {
    setScore(score * 2);
    setScore(score * 2);
  }

  return (
    <div>
      <p>Score: {score}</p>
      <button onClick={doubleScore}>Double Twice!</button>
    </div>
  );
}

Bug: score * 2 uses the stale value. Both calls see score = 0, so clicking from 0 gives 0, not the expected progression.

Solution:

import { useState } from 'react';

function ScoreKeeper() {
  const [score, setScore] = useState(0);

  function doubleScore() {
    // βœ… Each callback sees the updated value
    setScore(s => s * 2);  // First double
    setScore(s => s * 2);  // Second double
    // 1 β†’ 2 β†’ 4, or 2 β†’ 4 β†’ 8, etc.
  }

  return (
    <div>
      <p>Score: {score}</p>
      <button onClick={doubleScore}>Double Twice!</button>
    </div>
  );
}

πŸ’‘ Key Takeaways

ConceptWhat It MeansExample
Stale StateUsing old value that hasn't updated yetsetCount(count + 1) when count is 0
Callback PatternFunction that receives current statesetCount(c => c + 1)
BatchingReact groups updates togetherMultiple setState calls in one event
QueueCallbacks line up and see fresh valuesEach callback gets the result of the previous
ToggleFlip boolean with callbacksetIsOn(o => !o)
Direct ValueUse when NOT based on current statesetName("Alice")

The Callback Pattern:

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

  // ❌ WRONG: Uses stale value
  const wrong = () => {
    setCount(count + 1);
    setCount(count + 1);  // Both see 0!
  };

  // βœ… CORRECT: Uses fresh value
  const correct = () => {
    setCount(c => c + 1);  // Sees 0, sets 1
    setCount(c => c + 1);  // Sees 1, sets 2
  };

  return <button onClick={correct}>+2</button>;
}

Golden Rules:

  1. Updating based on current? β†’ Always use callback: setX(x => x + 1)
  2. Setting completely new value? β†’ Use direct: setX(newValue)
  3. Multiple updates in a row? β†’ MUST use callbacks or they'll overwrite each other
  4. Toggle pattern? β†’ setIsOpen(o => !o) is the safest way
  5. Naming? β†’ Use first letter: count β†’ c, step β†’ s, isOpen β†’ o

One Sentence Summary: > "When updating state based on its current value, always pass a callback function like setCount(c => c + 1) instead of using the state variable directly, because React batches updates and callbacks ensure each update sees the freshest valueβ€”preventing bugs where multiple rapid updates overwrite each other and produce unexpected results!"