Skip to content

📅 NGÀY 46: Concurrent Rendering - Nền Tảng Hiện Đại

🎯 Mục tiêu học tập (5 phút)

  • [ ] Hiểu được Concurrent Rendering là gì và tại sao cần thiết
  • [ ] Phân biệt rõ Synchronous vs Concurrent rendering
  • [ ] Nắm vững Automatic Batching trong React 18
  • [ ] Biết cách measure và visualize render performance
  • [ ] Nhận diện use cases phù hợp với Concurrent features

🤔 Kiểm tra đầu vào (5 phút)

Câu 1: State update trong React trigger re-render như thế nào? Có đồng bộ hay bất đồng bộ?

Câu 2: Khi bạn gọi setState nhiều lần liên tiếp, React xử lý như thế nào?

Câu 3: Trong app có danh sách 10,000 items, user typing vào search box bị lag. Tại sao và bạn xử lý thế nào?


📖 PHẦN 1: GIỚI THIỆU KHÁI NIỆM (30 phút)

1.1 Vấn Đề Thực Tế

Tưởng tượng bạn đang build một search interface:

jsx
function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);

    // Heavy computation - filter 10,000 items
    const filtered = heavyFilter(allItems, value);
    setResults(filtered);
  };

  return (
    <>
      <input
        value={query}
        onChange={handleChange}
      />
      <ResultsList results={results} /> {/* Renders 10,000 items */}
    </>
  );
}

Vấn đề: Mỗi keystroke → UI freezes vài trăm ms → Trải nghiệm tệ!

Tại sao? React 17 render đồng bộ (synchronous):

  1. User types "a"
  2. React bắt đầu render từ đầu đến cuối
  3. PHẢI hoàn thành 100% trước khi handle input tiếp theo
  4. Nếu render takes 200ms → Input bị block 200ms

1.2 Giải Pháp: Concurrent Rendering

React 18 giới thiệu Concurrent Rendering - khả năng:

  • Interruptible rendering: Tạm dừng render để xử lý urgent updates
  • Prioritized updates: Updates có độ ưu tiên khác nhau
  • Automatic batching: Batch nhiều state updates thành 1 render

Mental Model:

REACT 17 (Synchronous):
User Input → [==========RENDER==========] → UI Update
              (blocked, không thể interrupt)

REACT 18 (Concurrent):
User Input → [==RENDER==] ← New Input!
              (pause render, handle input first)
             → [==RENDER URGENT==] → UI Update (fast!)
             → [==RESUME RENDER==] → Background Update

1.3 Mental Model

Hãy nghĩ về Concurrent Rendering như multitasking trên computer:

Synchronous (React 17):

Task A [=============================] Task B
       Must finish A before start B

Concurrent (React 18):

Task A [====]     [====]     [====]
Task B      [====]     [====]     [====]
       Switch between tasks, prioritize urgent ones

Analogy:

  • Synchronous: Như đọc sách từ đầu đến cuối, không được dừng
  • Concurrent: Như đọc sách nhưng có thể bookmark, xử lý điện thoại, rồi quay lại đọc tiếp

1.4 Hiểu Lầm Phổ Biến

Hiểu lầm 1: "Concurrent = Parallel (đa luồng)"

  • Sự thật: Vẫn single-threaded! Chỉ là time-slicing thông minh

Hiểu lầm 2: "React 18 tự động làm mọi thứ nhanh hơn"

  • Sự thật: Cần opt-in vào concurrent features (useTransition, useDeferredValue)

Hiểu lầm 3: "Phải rewrite toàn bộ app để dùng React 18"

  • Sự thật: Backward compatible 100%! Chỉ cần upgrade version

Hiểu lầm 4: "Automatic batching làm app chậm hơn"

  • Sự thật: Ngược lại - ít render hơn = nhanh hơn!

💻 PHẦN 2: LIVE CODING (45 phút)

Demo 1: Automatic Batching - Before vs After ⭐

React 17 (No Batching ngoài event handlers):

jsx
/**
 * Demo: React 17 batching behavior
 * Batching CHỈ hoạt động trong event handlers
 */
function Counter17() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  console.log('Render'); // Log để đếm số lần render

  // ✅ Event handler: Batched (1 render)
  const handleClick = () => {
    setCount((c) => c + 1);
    setFlag((f) => !f);
    // React 17: Batches these → 1 render
  };

  // ❌ setTimeout: NOT batched (2 renders)
  const handleAsync = () => {
    setTimeout(() => {
      setCount((c) => c + 1); // Render 1
      setFlag((f) => !f); // Render 2
      // React 17: NO batching → 2 renders!
    }, 1000);
  };

  // ❌ fetch: NOT batched (2 renders)
  const handleFetch = () => {
    fetch('/api/data').then(() => {
      setCount((c) => c + 1); // Render 1
      setFlag((f) => !f); // Render 2
      // React 17: NO batching → 2 renders!
    });
  };

  return (
    <div>
      <p>
        Count: {count}, Flag: {flag.toString()}
      </p>
      <button onClick={handleClick}>Sync Update (1 render)</button>
      <button onClick={handleAsync}>Async Update (2 renders)</button>
      <button onClick={handleFetch}>Fetch Update (2 renders)</button>
    </div>
  );
}

// Console output khi click "Async Update":
// Render
// Render
// → 2 renders! Performance issue!

React 18 (Automatic Batching everywhere):

jsx
/**
 * Demo: React 18 automatic batching
 * Batching hoạt động ở MỌI NƠI
 */
function Counter18() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  console.log('Render');

  // ✅ Event handler: Batched
  const handleClick = () => {
    setCount((c) => c + 1);
    setFlag((f) => !f);
    // React 18: Batched → 1 render
  };

  // ✅ setTimeout: Batched! (NEW in React 18)
  const handleAsync = () => {
    setTimeout(() => {
      setCount((c) => c + 1);
      setFlag((f) => !f);
      // React 18: Batched → 1 render ✨
    }, 1000);
  };

  // ✅ fetch: Batched! (NEW in React 18)
  const handleFetch = async () => {
    const data = await fetch('/api/data');
    setCount((c) => c + 1);
    setFlag((f) => !f);
    // React 18: Batched → 1 render ✨
  };

  return (
    <div>
      <p>
        Count: {count}, Flag: {flag.toString()}
      </p>
      <button onClick={handleClick}>Sync Update (1 render)</button>
      <button onClick={handleAsync}>Async Update (1 render!)</button>
      <button onClick={handleFetch}>Fetch Update (1 render!)</button>
    </div>
  );
}

// Console output khi click "Async Update":
// Render
// → Chỉ 1 render! Performance improved! ✨

⚠️ Opt-out nếu cần:

jsx
import { flushSync } from 'react-dom';

function OptOut() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  const handleClick = () => {
    flushSync(() => {
      setCount((c) => c + 1); // Render immediately
    });

    flushSync(() => {
      setFlag((f) => !f); // Render immediately
    });
    // Total: 2 renders (opted out of batching)
  };

  return <button onClick={handleClick}>Force 2 Renders</button>;
}

Demo 2: Visualizing Concurrent Rendering ⭐⭐

jsx
/**
 * Demo: Measure render performance với profiler
 * So sánh sync vs concurrent rendering impact
 */
import { Profiler } from 'react';

function PerformanceDemo() {
  const [items, setItems] = useState(generateItems(5000));
  const [query, setQuery] = useState('');

  // Callback để measure render time
  const onRenderCallback = (
    id,
    phase, // "mount" hoặc "update"
    actualDuration, // Time spent rendering
    baseDuration, // Estimated time without memoization
    startTime,
    commitTime,
    interactions,
  ) => {
    console.log(`${id} ${phase} phase:`);
    console.log(`  Actual time: ${actualDuration.toFixed(2)}ms`);
    console.log(`  Base time: ${baseDuration.toFixed(2)}ms`);
  };

  const handleChange = (e) => {
    const value = e.target.value;
    setQuery(value);

    // Heavy filtering
    const filtered = items.filter((item) =>
      item.name.toLowerCase().includes(value.toLowerCase()),
    );
    setItems(filtered);
  };

  return (
    <Profiler
      id='SearchDemo'
      onRender={onRenderCallback}
    >
      <div>
        <input
          type='text'
          value={query}
          onChange={handleChange}
          placeholder='Search...'
        />

        <div>
          {items.map((item) => (
            <div key={item.id}>{item.name}</div>
          ))}
        </div>
      </div>
    </Profiler>
  );
}

function generateItems(count) {
  return Array.from({ length: count }, (_, i) => ({
    id: i,
    name: `Item ${i}`,
  }));
}

// Console output:
// SearchDemo update phase:
//   Actual time: 245.30ms  ← Blocking UI!
//   Base time: 245.30ms

Demo 3: React DevTools Profiler ⭐⭐⭐

jsx
/**
 * Demo: Sử dụng React DevTools để identify performance issues
 *
 * Cách dùng:
 * 1. Mở React DevTools
 * 2. Tab "Profiler"
 * 3. Click record (⏺️)
 * 4. Interact với app
 * 5. Stop recording
 * 6. Analyze flame graph
 */
function ProfilerExample() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // Heavy component - intentionally slow
  const HeavyComponent = () => {
    const startTime = performance.now();
    while (performance.now() - startTime < 50) {
      // Simulate heavy computation
    }
    return <div>Heavy Component rendered</div>;
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>

      <input
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder='Type here...'
      />

      {/* This will show up red in profiler */}
      <HeavyComponent />

      {/* Render nhiều components để dễ visualize */}
      {Array.from({ length: 100 }).map((_, i) => (
        <div key={i}>Item {i}</div>
      ))}
    </div>
  );
}

/**
 * Flame Graph Analysis:
 *
 * 🔴 Red/Orange: Took long time (>12ms)
 * 🟡 Yellow: Moderate time (4-12ms)
 * 🟢 Green: Fast (<4ms)
 *
 * Ranked Chart: Shows components by render time
 * - Focus on top items first
 * - Optimize red/orange components
 */

🔨 PHẦN 3: BÀI TẬP THỰC HÀNH (60 phút)

⭐ Level 1: Kiểm Tra Automatic Batching (15 phút)

jsx
/**
 * 🎯 Mục tiêu: Verify automatic batching trong React 18
 * ⏱️ Thời gian: 15 phút
 * 🚫 KHÔNG dùng: useTransition, useDeferredValue
 *
 * Requirements:
 * 1. Tạo component với 3 state variables
 * 2. Update cả 3 states trong setTimeout
 * 3. Log render count để verify chỉ 1 render
 * 4. So sánh behavior nếu dùng React 17
 *
 * 💡 Gợi ý: Dùng useRef để track render count
 */

// ❌ Cách SAI (không track được renders):
function BadExample() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  const [c, setC] = useState(0);

  // Không có cách nào biết bao nhiêu renders!

  return <div>{a + b + c}</div>;
}

// ✅ Cách ĐÚNG (track renders properly):
function GoodExample() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);
  const [c, setC] = useState(0);

  const renderCount = useRef(0);

  useEffect(() => {
    renderCount.current += 1;
    console.log(`Render #${renderCount.current}`);
  });

  return (
    <div>
      <p>Sum: {a + b + c}</p>
      <p>Render count: {renderCount.current}</p>
    </div>
  );
}

// 🎯 NHIỆM VỤ CỦA BẠN:
function BatchingTest() {
  // TODO: Implement state variables

  // TODO: Implement render counter

  const handleAsync = () => {
    setTimeout(() => {
      // TODO: Update all 3 states here
      // Expected: Chỉ 1 render trong React 18
    }, 100);
  };

  const handlePromise = () => {
    Promise.resolve().then(() => {
      // TODO: Update all 3 states here
      // Expected: Chỉ 1 render trong React 18
    });
  };

  return (
    <div>
      {/* TODO: Display states và render count */}
      {/* TODO: Add buttons để trigger updates */}
    </div>
  );
}
💡 Solution
jsx
/**
 * Automatic Batching Test Component
 * Verifies React 18 batches updates trong async contexts
 */
function BatchingTest() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);
  const [text, setText] = useState('');

  const renderCount = useRef(0);

  useEffect(() => {
    renderCount.current += 1;
    console.log(`Render #${renderCount.current}`);
  });

  const handleAsync = () => {
    console.log('--- Async update started ---');
    setTimeout(() => {
      setCount((c) => c + 1);
      setFlag((f) => !f);
      setText('Updated via setTimeout');
      // React 18: 1 render
      // React 17: 3 renders
    }, 100);
  };

  const handlePromise = () => {
    console.log('--- Promise update started ---');
    Promise.resolve().then(() => {
      setCount((c) => c + 1);
      setFlag((f) => !f);
      setText('Updated via Promise');
      // React 18: 1 render
      // React 17: 3 renders
    });
  };

  const handleFetch = async () => {
    console.log('--- Fetch update started ---');
    // Simulate fetch
    await new Promise((resolve) => setTimeout(resolve, 100));

    setCount((c) => c + 1);
    setFlag((f) => !f);
    setText('Updated via fetch');
    // React 18: 1 render
    // React 17: 3 renders
  };

  return (
    <div>
      <h3>Automatic Batching Test</h3>
      <div>
        <p>Count: {count}</p>
        <p>Flag: {flag.toString()}</p>
        <p>Text: {text}</p>
        <p>
          <strong>Total Renders: {renderCount.current}</strong>
        </p>
      </div>

      <button onClick={handleAsync}>Update via setTimeout</button>
      <button onClick={handlePromise}>Update via Promise</button>
      <button onClick={handleFetch}>Update via Fetch</button>
    </div>
  );
}

/**
 * Expected results (React 18):
 * - Click "Update via setTimeout" → Render count +1
 * - Click "Update via Promise" → Render count +1
 * - Click "Update via Fetch" → Render count +1
 *
 * In React 17:
 * - Each click would increase render count by +3
 */

⭐⭐ Level 2: Performance Profiling (25 phút)

jsx
/**
 * 🎯 Mục tiêu: Measure và visualize render performance
 * ⏱️ Thời gian: 25 phút
 *
 * Scenario: Build một search interface với large dataset
 *
 * 🤔 PHÂN TÍCH:
 * Approach A: Filter inline trong render
 * Pros: Simple, straightforward
 * Cons: Recalculates every render
 *
 * Approach B: Filter trong event handler
 * Pros: Better performance
 * Cons: Multiple state updates
 *
 * Approach C: useMemo để cache filtered results
 * Pros: Optimal performance
 * Cons: More complex
 *
 * 💭 BẠN CHỌN GÌ VÀ TẠI SAO?
 *
 * Requirements:
 * 1. Generate 10,000 items
 * 2. Implement search với cả 3 approaches
 * 3. Dùng Profiler component để measure
 * 4. Log render times
 * 5. So sánh performance
 */

// 🎯 NHIỆM VỤ CỦA BẠN:
function SearchPerformance() {
  const allItems = useMemo(
    () =>
      Array.from({ length: 10000 }, (_, i) => ({
        id: i,
        name: `Item ${i}`,
        category: ['A', 'B', 'C'][i % 3],
      })),
    [],
  );

  // TODO: Implement 3 approaches
  // TODO: Add Profiler to measure each
  // TODO: Display render times

  return <div>{/* Your implementation */}</div>;
}
💡 Solution
jsx
/**
 * Search Performance Comparison
 * Compares different filtering approaches
 */
function SearchPerformance() {
  const allItems = useMemo(
    () =>
      Array.from({ length: 10000 }, (_, i) => ({
        id: i,
        name: `Item ${i}`,
        category: ['Electronics', 'Clothing', 'Food'][i % 3],
      })),
    [],
  );

  const [approach, setApproach] = useState('A');
  const [times, setTimes] = useState({ A: [], B: [], C: [] });

  const onRender = useCallback((id, phase, actualDuration) => {
    if (phase === 'update') {
      setTimes((prev) => ({
        ...prev,
        [id]: [...prev[id].slice(-4), actualDuration],
      }));
    }
  }, []);

  const avgTime = (arr) =>
    arr.length
      ? (arr.reduce((a, b) => a + b, 0) / arr.length).toFixed(2)
      : '0.00';

  return (
    <div>
      <h3>Performance Comparison</h3>

      <div style={{ marginBottom: 20 }}>
        <button onClick={() => setApproach('A')}>Approach A</button>
        <button onClick={() => setApproach('B')}>Approach B</button>
        <button onClick={() => setApproach('C')}>Approach C</button>
      </div>

      <div style={{ marginBottom: 20 }}>
        <p>Approach A avg: {avgTime(times.A)}ms (Filter inline)</p>
        <p>Approach B avg: {avgTime(times.B)}ms (Filter in handler)</p>
        <p>Approach C avg: {avgTime(times.C)}ms (useMemo)</p>
      </div>

      {approach === 'A' && (
        <Profiler
          id='A'
          onRender={onRender}
        >
          <ApproachA items={allItems} />
        </Profiler>
      )}

      {approach === 'B' && (
        <Profiler
          id='B'
          onRender={onRender}
        >
          <ApproachB items={allItems} />
        </Profiler>
      )}

      {approach === 'C' && (
        <Profiler
          id='C'
          onRender={onRender}
        >
          <ApproachC items={allItems} />
        </Profiler>
      )}
    </div>
  );
}

/**
 * Approach A: Filter inline (WORST)
 * Re-filters on EVERY render, even unrelated updates
 */
function ApproachA({ items }) {
  const [query, setQuery] = useState('');
  const [count, setCount] = useState(0); // Unrelated state

  // ❌ Filters on every render!
  const filtered = items.filter((item) =>
    item.name.toLowerCase().includes(query.toLowerCase()),
  );

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder='Search...'
      />
      <button onClick={() => setCount((c) => c + 1)}>
        Unrelated update: {count}
      </button>
      <p>Found: {filtered.length} items</p>
    </div>
  );
}

/**
 * Approach B: Filter in handler (BETTER)
 * Only filters when query changes
 */
function ApproachB({ items }) {
  const [query, setQuery] = useState('');
  const [filtered, setFiltered] = useState(items);
  const [count, setCount] = useState(0);

  const handleSearch = (e) => {
    const value = e.target.value;
    setQuery(value);

    // Filter and update state
    const results = items.filter((item) =>
      item.name.toLowerCase().includes(value.toLowerCase()),
    );
    setFiltered(results);
  };

  return (
    <div>
      <input
        value={query}
        onChange={handleSearch}
        placeholder='Search...'
      />
      <button onClick={() => setCount((c) => c + 1)}>
        Unrelated update: {count}
      </button>
      <p>Found: {filtered.length} items</p>
    </div>
  );
}

/**
 * Approach C: useMemo (BEST)
 * Only recalculates when dependencies change
 */
function ApproachC({ items }) {
  const [query, setQuery] = useState('');
  const [count, setCount] = useState(0);

  // ✅ Only recalculates when query or items change
  const filtered = useMemo(() => {
    console.log('Filtering...'); // Log để verify
    return items.filter((item) =>
      item.name.toLowerCase().includes(query.toLowerCase()),
    );
  }, [items, query]);

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder='Search...'
      />
      <button onClick={() => setCount((c) => c + 1)}>
        Unrelated update: {count}
      </button>
      <p>Found: {filtered.length} items</p>
    </div>
  );
}

/**
 * Performance results (typical):
 * Approach A: 15-20ms per render (includes filtering)
 * Approach B: 2-3ms per render (no filtering on unrelated updates)
 * Approach C: 2-3ms per render (memoized)
 *
 * Key insight: Approach B and C similar, but C is cleaner code
 */

⭐⭐⭐ Level 3: React DevTools Profiler Analysis (40 phút)

jsx
/**
 * 🎯 Mục tiêu: Identify và fix performance bottlenecks
 * ⏱️ Thời gian: 40 phút
 *
 * 📋 Product Requirements:
 * User Story: "Là user, tôi muốn type trong search box mượt mà
 * không bị lag, ngay cả khi có nhiều kết quả"
 *
 * ✅ Acceptance Criteria:
 * - [ ] Search không lag khi typing nhanh
 * - [ ] Unrelated updates không re-render search results
 * - [ ] Profiler shows minimal wasted renders
 *
 * 🎨 Technical Constraints:
 * - Dataset: 5000 items
 * - Target: <16ms per render (60fps)
 * - Must show loading indicator
 *
 * 🚨 Edge Cases cần handle:
 * - Empty search (show all)
 * - No results found
 * - Rapid typing (debounce?)
 *
 * 📝 Implementation Checklist:
 * - [ ] Implement buggy version first
 * - [ ] Use Profiler to identify issues
 * - [ ] Apply optimizations
 * - [ ] Measure before/after
 */

// 🎯 NHIỆM VỤ CỦA BẠN:
function ProductSearch() {
  // TODO: Implement search với intentional performance issues
  // TODO: Add Profiler
  // TODO: Identify bottlenecks
  // TODO: Fix issues
  // TODO: Document findings
}
💡 Solution
jsx
/**
 * Product Search - Performance Optimization Exercise
 * Step-by-step optimization với profiling
 */

// Step 1: Buggy version với multiple issues
function ProductSearchBuggy() {
  const [query, setQuery] = useState('');
  const [category, setCategory] = useState('all');
  const [sortBy, setSortBy] = useState('name');

  // ❌ Issue 1: Generate data on every render!
  const allProducts = Array.from({ length: 5000 }, (_, i) => ({
    id: i,
    name: `Product ${i}`,
    category: ['Electronics', 'Clothing', 'Food'][i % 3],
    price: Math.floor(Math.random() * 1000),
  }));

  // ❌ Issue 2: No memoization
  const filtered = allProducts.filter((p) => {
    const matchQuery = p.name.toLowerCase().includes(query.toLowerCase());
    const matchCategory = category === 'all' || p.category === category;
    return matchQuery && matchCategory;
  });

  // ❌ Issue 3: Sorting on every render
  const sorted = [...filtered].sort((a, b) => {
    if (sortBy === 'name') return a.name.localeCompare(b.name);
    return a.price - b.price;
  });

  return (
    <div>
      <h3>❌ Buggy Version (measure this!)</h3>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder='Search products...'
      />

      <select
        value={category}
        onChange={(e) => setCategory(e.target.value)}
      >
        <option value='all'>All Categories</option>
        <option value='Electronics'>Electronics</option>
        <option value='Clothing'>Clothing</option>
        <option value='Food'>Food</option>
      </select>

      <select
        value={sortBy}
        onChange={(e) => setSortBy(e.target.value)}
      >
        <option value='name'>Sort by Name</option>
        <option value='price'>Sort by Price</option>
      </select>

      <p>Found: {sorted.length} products</p>

      {/* ❌ Issue 4: No memo, re-renders always */}
      {sorted.slice(0, 100).map((product) => (
        <ProductCard
          key={product.id}
          product={product}
        />
      ))}
    </div>
  );
}

// Heavy component without memo
function ProductCard({ product }) {
  // Simulate heavy render
  const startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Busy wait 1ms
  }

  return (
    <div style={{ padding: 8, border: '1px solid #ddd', margin: 4 }}>
      <strong>{product.name}</strong> - ${product.price}
    </div>
  );
}

// Step 2: Optimized version
function ProductSearchOptimized() {
  const [query, setQuery] = useState('');
  const [category, setCategory] = useState('all');
  const [sortBy, setSortBy] = useState('name');
  const [renderTime, setRenderTime] = useState(0);

  // ✅ Fix 1: useMemo for data generation
  const allProducts = useMemo(
    () =>
      Array.from({ length: 5000 }, (_, i) => ({
        id: i,
        name: `Product ${i}`,
        category: ['Electronics', 'Clothing', 'Food'][i % 3],
        price: Math.floor(Math.random() * 1000),
      })),
    [],
  );

  // ✅ Fix 2: useMemo for filtering
  const filtered = useMemo(() => {
    console.log('Filtering...');
    return allProducts.filter((p) => {
      const matchQuery = p.name.toLowerCase().includes(query.toLowerCase());
      const matchCategory = category === 'all' || p.category === category;
      return matchQuery && matchCategory;
    });
  }, [allProducts, query, category]);

  // ✅ Fix 3: useMemo for sorting
  const sorted = useMemo(() => {
    console.log('Sorting...');
    return [...filtered].sort((a, b) => {
      if (sortBy === 'name') return a.name.localeCompare(b.name);
      return a.price - b.price;
    });
  }, [filtered, sortBy]);

  const onRender = useCallback((id, phase, actualDuration) => {
    if (phase === 'update') {
      setRenderTime(actualDuration);
    }
  }, []);

  return (
    <Profiler
      id='ProductSearch'
      onRender={onRender}
    >
      <div>
        <h3>✅ Optimized Version</h3>
        <p>Last render: {renderTime.toFixed(2)}ms</p>

        <input
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder='Search products...'
        />

        <select
          value={category}
          onChange={(e) => setCategory(e.target.value)}
        >
          <option value='all'>All Categories</option>
          <option value='Electronics'>Electronics</option>
          <option value='Clothing'>Clothing</option>
          <option value='Food'>Food</option>
        </select>

        <select
          value={sortBy}
          onChange={(e) => setSortBy(e.target.value)}
        >
          <option value='name'>Sort by Name</option>
          <option value='price'>Sort by Price</option>
        </select>

        <p>Found: {sorted.length} products</p>

        {/* ✅ Fix 4: Memo'd component */}
        {sorted.slice(0, 100).map((product) => (
          <ProductCardMemo
            key={product.id}
            product={product}
          />
        ))}
      </div>
    </Profiler>
  );
}

// ✅ Memoized component
const ProductCardMemo = React.memo(function ProductCard({ product }) {
  const startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Busy wait 1ms
  }

  return (
    <div style={{ padding: 8, border: '1px solid #ddd', margin: 4 }}>
      <strong>{product.name}</strong> - ${product.price}
    </div>
  );
});

/**
 * Performance comparison:
 *
 * Buggy version:
 * - Initial render: ~600ms (regenerates data + sorts)
 * - Typing in search: ~300ms per keystroke
 * - Changing category: ~300ms
 * - Total wasted renders: High
 *
 * Optimized version:
 * - Initial render: ~150ms (memoized data)
 * - Typing in search: ~50ms per keystroke
 * - Changing category: ~30ms
 * - Total wasted renders: Minimal
 *
 * Improvement: ~6x faster! ✨
 */

⭐⭐⭐⭐ Level 4: Custom Performance Monitor (60 phút)

jsx
/**
 * 🎯 Mục tiêu: Build reusable performance monitoring tool
 * ⏱️ Thời gian: 60 phút
 *
 * 🏗️ PHASE 1: Research & Design (20 phút)
 *
 * Nhiệm vụ:
 * 1. Design API cho usePerformance hook
 * 2. Decide metrics cần track
 * 3. Visualization strategy
 *
 * ADR Template:
 * - Context: Need to monitor render performance across app
 * - Decision: Custom hook + visual indicator
 * - Rationale: Reusable, non-invasive, informative
 * - Consequences: Small overhead, dev-only
 * - Alternatives: React DevTools (manual), console.log (poor UX)
 *
 * 💻 PHASE 2: Implementation (30 phút)
 * [Implement solution]
 *
 * 🧪 PHASE 3: Testing (10 phút)
 * - [ ] Test với fast component (<5ms)
 * - [ ] Test với slow component (>50ms)
 * - [ ] Verify không ảnh hưởng production
 */

// 🎯 NHIỆM VỤ CỦA BẠN:
function usePerformance(componentName) {
  // TODO: Track render count
  // TODO: Track render time
  // TODO: Track average time
  // TODO: Detect slow renders (>16ms)
  // TODO: Return metrics và controls
}

function PerformanceMonitor({ children }) {
  // TODO: Visual indicator component
  // TODO: Show metrics overlay
  // TODO: Warning for slow renders
}
💡 Solution
jsx
/**
 * Custom Performance Monitor Hook & Component
 * Tracks và visualizes component performance
 */

/**
 * usePerformance - Track component render metrics
 * @param {string} componentName - Tên component để identify
 * @returns {Object} Metrics và helper functions
 */
function usePerformance(componentName) {
  const renderCount = useRef(0);
  const renderTimes = useRef([]);
  const slowRenders = useRef([]);
  const [metrics, setMetrics] = useState({
    count: 0,
    avgTime: 0,
    lastTime: 0,
    slowCount: 0,
  });

  useEffect(() => {
    const startTime = performance.now();

    return () => {
      const endTime = performance.now();
      const renderTime = endTime - startTime;

      // Update counts
      renderCount.current += 1;
      renderTimes.current.push(renderTime);

      // Keep last 100 renders
      if (renderTimes.current.length > 100) {
        renderTimes.current.shift();
      }

      // Track slow renders (>16ms = 60fps threshold)
      if (renderTime > 16) {
        slowRenders.current.push({
          count: renderCount.current,
          time: renderTime,
          timestamp: Date.now(),
        });

        console.warn(
          `[Performance] Slow render in ${componentName}: ${renderTime.toFixed(2)}ms`,
        );
      }

      // Calculate metrics
      const avgTime =
        renderTimes.current.reduce((a, b) => a + b, 0) /
        renderTimes.current.length;

      setMetrics({
        count: renderCount.current,
        avgTime,
        lastTime: renderTime,
        slowCount: slowRenders.current.length,
      });
    };
  });

  const reset = useCallback(() => {
    renderCount.current = 0;
    renderTimes.current = [];
    slowRenders.current = [];
    setMetrics({ count: 0, avgTime: 0, lastTime: 0, slowCount: 0 });
  }, []);

  const getSlowRenders = useCallback(() => {
    return slowRenders.current;
  }, []);

  return {
    metrics,
    reset,
    getSlowRenders,
  };
}

/**
 * PerformanceMonitor - Visual performance indicator
 * Shows performance metrics in overlay
 */
function PerformanceMonitor({
  componentName,
  children,
  threshold = 16, // ms - 60fps threshold
}) {
  const { metrics, reset, getSlowRenders } = usePerformance(componentName);
  const [showDetails, setShowDetails] = useState(false);

  const status = metrics.avgTime > threshold ? 'slow' : 'fast';
  const color = status === 'slow' ? '#ef4444' : '#10b981';

  return (
    <div style={{ position: 'relative' }}>
      {/* Performance badge */}
      <div
        onClick={() => setShowDetails(!showDetails)}
        style={{
          position: 'absolute',
          top: 0,
          right: 0,
          backgroundColor: color,
          color: 'white',
          padding: '4px 8px',
          borderRadius: 4,
          fontSize: 12,
          cursor: 'pointer',
          zIndex: 1000,
          userSelect: 'none',
        }}
      >
        {metrics.lastTime.toFixed(1)}ms
      </div>

      {/* Detailed metrics panel */}
      {showDetails && (
        <div
          style={{
            position: 'absolute',
            top: 30,
            right: 0,
            backgroundColor: 'white',
            border: '1px solid #ccc',
            borderRadius: 4,
            padding: 12,
            fontSize: 12,
            zIndex: 1001,
            minWidth: 200,
            boxShadow: '0 2px 8px rgba(0,0,0,0.15)',
          }}
        >
          <h4 style={{ margin: '0 0 8px 0' }}>{componentName}</h4>

          <div style={{ marginBottom: 8 }}>
            <strong>Renders:</strong> {metrics.count}
          </div>

          <div style={{ marginBottom: 8 }}>
            <strong>Avg time:</strong> {metrics.avgTime.toFixed(2)}ms
          </div>

          <div style={{ marginBottom: 8 }}>
            <strong>Last render:</strong> {metrics.lastTime.toFixed(2)}ms
          </div>

          <div style={{ marginBottom: 8 }}>
            <strong>Slow renders:</strong> {metrics.slowCount}
          </div>

          {metrics.slowCount > 0 && (
            <div
              style={{
                marginTop: 8,
                padding: 8,
                backgroundColor: '#fef2f2',
                borderRadius: 4,
              }}
            >
              <strong>⚠️ Optimization needed!</strong>
              <div style={{ marginTop: 4, fontSize: 11 }}>
                {getSlowRenders()
                  .slice(-3)
                  .map((render, i) => (
                    <div key={i}>
                      Render #{render.count}: {render.time.toFixed(2)}ms
                    </div>
                  ))}
              </div>
            </div>
          )}

          <button
            onClick={reset}
            style={{
              marginTop: 8,
              width: '100%',
              padding: '4px 8px',
              fontSize: 12,
              cursor: 'pointer',
            }}
          >
            Reset Metrics
          </button>
        </div>
      )}

      {children}
    </div>
  );
}

// Example usage:
function DemoApp() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState(Array.from({ length: 1000 }, (_, i) => i));

  return (
    <div>
      <h2>Performance Monitor Demo</h2>

      {/* Fast component */}
      <PerformanceMonitor componentName='FastComponent'>
        <FastComponent count={count} />
      </PerformanceMonitor>

      {/* Slow component */}
      <PerformanceMonitor
        componentName='SlowComponent'
        threshold={10}
      >
        <SlowComponent items={items} />
      </PerformanceMonitor>

      <button onClick={() => setCount((c) => c + 1)}>Increment Count</button>

      <button onClick={() => setItems((items) => [...items, items.length])}>
        Add Item
      </button>
    </div>
  );
}

function FastComponent({ count }) {
  return <div>Fast component: {count}</div>;
}

function SlowComponent({ items }) {
  // Intentionally slow
  const startTime = performance.now();
  while (performance.now() - startTime < 20) {
    // Busy wait
  }

  return (
    <div>
      <p>Slow component with {items.length} items</p>
      {items.slice(0, 10).map((i) => (
        <div key={i}>Item {i}</div>
      ))}
    </div>
  );
}

/**
 * Key features:
 * 1. Visual badge showing last render time
 * 2. Color-coded (green = fast, red = slow)
 * 3. Click to show detailed metrics
 * 4. Tracks slow renders with warnings
 * 5. Reset functionality
 * 6. Configurable threshold
 *
 * Usage tips:
 * - Wrap components you want to monitor
 * - Check for red badges (slow renders)
 * - Click badge to see detailed metrics
 * - Use in development only (add process.env.NODE_ENV check)
 */

⭐⭐⭐⭐⭐ Level 5: Performance Dashboard (90 phút)

jsx
/**
 * 🎯 Mục tiêu: Build comprehensive performance monitoring dashboard
 * ⏱️ Thời gian: 90 phút
 *
 * 📋 Feature Specification:
 * Build a developer tool để monitor toàn bộ app performance
 * - Real-time render metrics
 * - Component hierarchy visualization
 * - Slow render alerts
 * - Export metrics
 *
 * 🏗️ Technical Design Doc:
 * 1. Component Architecture
 *    - PerformanceProvider (context)
 *    - usePerformanceMonitor hook
 *    - PerformanceDashboard UI
 *    - MetricsChart visualization
 *
 * 2. State Management Strategy
 *    - Context cho global metrics
 *    - Local state cho UI controls
 *
 * 3. Performance Considerations
 *    - Minimal overhead (<1ms)
 *    - Debounced updates
 *    - Memory limits (max 1000 data points)
 *
 * 4. Error Handling Strategy
 *    - Graceful degradation nếu không support
 *    - Try-catch cho measurement APIs
 *
 * ✅ Production Checklist:
 * - [ ] DEV-only (check NODE_ENV)
 * - [ ] No production bundle impact
 * - [ ] Keyboard shortcut (Cmd/Ctrl + Shift + P)
 * - [ ] Draggable window
 * - [ ] Local storage persistence
 * - [ ] Export CSV functionality
 * - [ ] Clear metrics function
 */

// 🎯 NHIỆM VỤ CỦA BẠN:
// Implement full performance monitoring solution
💡 Solution
jsx
/**
 * Production-ready Performance Dashboard
 * Comprehensive monitoring tool cho React apps
 */

// Context for global performance tracking
const PerformanceContext = React.createContext(null);

/**
 * PerformanceProvider - Wrap app để track metrics
 */
function PerformanceProvider({ children }) {
  const [components, setComponents] = useState(new Map());
  const [showDashboard, setShowDashboard] = useState(false);

  // Keyboard shortcut: Cmd/Ctrl + Shift + P
  useEffect(() => {
    const handleKeyPress = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === 'P') {
        e.preventDefault();
        setShowDashboard((prev) => !prev);
      }
    };

    window.addEventListener('keydown', handleKeyPress);
    return () => window.removeEventListener('keydown', handleKeyPress);
  }, []);

  const registerComponent = useCallback((name, metrics) => {
    setComponents((prev) => {
      const newMap = new Map(prev);
      newMap.set(name, {
        ...metrics,
        timestamp: Date.now(),
      });
      return newMap;
    });
  }, []);

  const clearMetrics = useCallback(() => {
    setComponents(new Map());
  }, []);

  const exportMetrics = useCallback(() => {
    const data = Array.from(components.entries()).map(([name, metrics]) => ({
      component: name,
      ...metrics,
    }));

    const csv = [
      'Component,Renders,AvgTime,LastTime,SlowRenders',
      ...data.map(
        (d) =>
          `${d.component},${d.count},${d.avgTime.toFixed(2)},${d.lastTime.toFixed(2)},${d.slowCount}`,
      ),
    ].join('\n');

    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `performance-${Date.now()}.csv`;
    a.click();
  }, [components]);

  const value = {
    components,
    registerComponent,
    clearMetrics,
    exportMetrics,
  };

  return (
    <PerformanceContext.Provider value={value}>
      {children}
      {showDashboard && (
        <PerformanceDashboard onClose={() => setShowDashboard(false)} />
      )}
    </PerformanceContext.Provider>
  );
}

/**
 * useComponentPerformance - Hook để track individual component
 */
function useComponentPerformance(componentName) {
  const context = React.useContext(PerformanceContext);
  const renderCount = useRef(0);
  const renderTimes = useRef([]);
  const slowRenders = useRef(0);

  useEffect(() => {
    const startTime = performance.now();

    return () => {
      const renderTime = performance.now() - startTime;
      renderCount.current += 1;
      renderTimes.current.push(renderTime);

      // Keep last 100
      if (renderTimes.current.length > 100) {
        renderTimes.current.shift();
      }

      if (renderTime > 16) {
        slowRenders.current += 1;
      }

      const avgTime =
        renderTimes.current.reduce((a, b) => a + b, 0) /
        renderTimes.current.length;

      context?.registerComponent(componentName, {
        count: renderCount.current,
        avgTime,
        lastTime: renderTime,
        slowCount: slowRenders.current,
      });
    };
  });
}

/**
 * PerformanceDashboard - Main dashboard UI
 */
function PerformanceDashboard({ onClose }) {
  const { components, clearMetrics, exportMetrics } =
    React.useContext(PerformanceContext);
  const [position, setPosition] = useState({ x: 20, y: 20 });
  const [isDragging, setIsDragging] = useState(false);
  const dragStart = useRef({ x: 0, y: 0 });

  const componentsArray = Array.from(components.entries())
    .map(([name, metrics]) => ({ name, ...metrics }))
    .sort((a, b) => b.avgTime - a.avgTime);

  const totalRenders = componentsArray.reduce((sum, c) => sum + c.count, 0);
  const slowComponents = componentsArray.filter((c) => c.avgTime > 16).length;

  // Dragging logic
  const handleMouseDown = (e) => {
    if (e.target.closest('.dashboard-header')) {
      setIsDragging(true);
      dragStart.current = {
        x: e.clientX - position.x,
        y: e.clientY - position.y,
      };
    }
  };

  useEffect(() => {
    if (!isDragging) return;

    const handleMouseMove = (e) => {
      setPosition({
        x: e.clientX - dragStart.current.x,
        y: e.clientY - dragStart.current.y,
      });
    };

    const handleMouseUp = () => setIsDragging(false);

    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseup', handleMouseUp);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseup', handleMouseUp);
    };
  }, [isDragging]);

  return (
    <div
      style={{
        position: 'fixed',
        left: position.x,
        top: position.y,
        width: 600,
        maxHeight: '80vh',
        backgroundColor: 'white',
        border: '1px solid #ccc',
        borderRadius: 8,
        boxShadow: '0 4px 20px rgba(0,0,0,0.2)',
        zIndex: 10000,
        overflow: 'hidden',
        fontFamily: 'monospace',
        fontSize: 12,
        cursor: isDragging ? 'grabbing' : 'default',
      }}
      onMouseDown={handleMouseDown}
    >
      {/* Header */}
      <div
        className='dashboard-header'
        style={{
          padding: 12,
          backgroundColor: '#3b82f6',
          color: 'white',
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center',
          cursor: 'grab',
        }}
      >
        <h3 style={{ margin: 0 }}>⚡ Performance Dashboard</h3>
        <button
          onClick={onClose}
          style={{
            background: 'none',
            border: 'none',
            color: 'white',
            cursor: 'pointer',
            fontSize: 18,
          }}
        >
          ×
        </button>
      </div>

      {/* Summary */}
      <div style={{ padding: 12, borderBottom: '1px solid #e5e7eb' }}>
        <div
          style={{
            display: 'grid',
            gridTemplateColumns: '1fr 1fr 1fr',
            gap: 12,
          }}
        >
          <div>
            <div style={{ color: '#6b7280' }}>Components</div>
            <div style={{ fontSize: 20, fontWeight: 'bold' }}>
              {components.size}
            </div>
          </div>
          <div>
            <div style={{ color: '#6b7280' }}>Total Renders</div>
            <div style={{ fontSize: 20, fontWeight: 'bold' }}>
              {totalRenders}
            </div>
          </div>
          <div>
            <div style={{ color: '#6b7280' }}>Slow Components</div>
            <div
              style={{
                fontSize: 20,
                fontWeight: 'bold',
                color: slowComponents > 0 ? '#ef4444' : '#10b981',
              }}
            >
              {slowComponents}
            </div>
          </div>
        </div>
      </div>

      {/* Actions */}
      <div
        style={{
          padding: 12,
          borderBottom: '1px solid #e5e7eb',
          display: 'flex',
          gap: 8,
        }}
      >
        <button
          onClick={clearMetrics}
          style={{ flex: 1, padding: 6 }}
        >
          Clear Metrics
        </button>
        <button
          onClick={exportMetrics}
          style={{ flex: 1, padding: 6 }}
        >
          Export CSV
        </button>
      </div>

      {/* Component list */}
      <div
        style={{
          maxHeight: 400,
          overflowY: 'auto',
          padding: 12,
        }}
      >
        {componentsArray.length === 0 ? (
          <div style={{ textAlign: 'center', padding: 20, color: '#6b7280' }}>
            No components tracked yet
          </div>
        ) : (
          componentsArray.map(
            ({ name, count, avgTime, lastTime, slowCount }) => (
              <div
                key={name}
                style={{
                  padding: 8,
                  marginBottom: 8,
                  backgroundColor: avgTime > 16 ? '#fef2f2' : '#f9fafb',
                  borderLeft: `3px solid ${avgTime > 16 ? '#ef4444' : '#10b981'}`,
                  borderRadius: 4,
                }}
              >
                <div
                  style={{
                    display: 'flex',
                    justifyContent: 'space-between',
                    marginBottom: 4,
                  }}
                >
                  <strong>{name}</strong>
                  <span
                    style={{
                      color: avgTime > 16 ? '#ef4444' : '#10b981',
                      fontWeight: 'bold',
                    }}
                  >
                    {avgTime.toFixed(2)}ms avg
                  </span>
                </div>
                <div
                  style={{
                    display: 'flex',
                    gap: 16,
                    color: '#6b7280',
                    fontSize: 11,
                  }}
                >
                  <span>Renders: {count}</span>
                  <span>Last: {lastTime.toFixed(2)}ms</span>
                  {slowCount > 0 && (
                    <span style={{ color: '#ef4444' }}>Slow: {slowCount}</span>
                  )}
                </div>
              </div>
            ),
          )
        )}
      </div>

      {/* Footer */}
      <div
        style={{
          padding: 8,
          borderTop: '1px solid #e5e7eb',
          backgroundColor: '#f9fafb',
          fontSize: 10,
          color: '#6b7280',
          textAlign: 'center',
        }}
      >
        Press Cmd/Ctrl + Shift + P to toggle • Drag to move
      </div>
    </div>
  );
}

// Example: Wrap your app
function App() {
  return (
    <PerformanceProvider>
      <MyApp />
    </PerformanceProvider>
  );
}

// Example: Track a component
function MyComponent() {
  useComponentPerformance('MyComponent');

  return <div>My Component</div>;
}

/**
 * Features implemented:
 * ✅ Global performance tracking via Context
 * ✅ Keyboard shortcut (Cmd/Ctrl + Shift + P)
 * ✅ Draggable window
 * ✅ Real-time metrics
 * ✅ Color-coded slow components
 * ✅ Export to CSV
 * ✅ Clear metrics
 * ✅ Summary statistics
 * ✅ Sorted by avg time
 *
 * Production considerations:
 * - Wrap with process.env.NODE_ENV === 'development' check
 * - Tree-shake in production build
 * - Add memory limits (max components tracked)
 * - Debounce updates for better performance
 */

📊 PHẦN 4: SO SÁNH PATTERNS (30 phút)

Bảng So Sánh: React 17 vs React 18

FeatureReact 17React 18Impact
BatchingChỉ trong event handlersMọi nơi (automatic)⬆️ Performance
RenderingSynchronousConcurrent (opt-in)⬆️ Responsiveness
APIsLegacyModern (useTransition, etc)⬆️ Developer Experience
SSRClient hydration blockingStreaming SSR⬆️ TTI (Time to Interactive)
SuspenseLimitedFull support⬆️ Loading UX

Trade-offs Matrix

ApproachProsConsWhen to Use
React 17✅ Stable
✅ Well-tested
✅ Simple model
❌ No automatic batching
❌ Blocking renders
❌ No concurrent features
Legacy apps
Simple UIs
Low interactivity
React 18✅ Better performance
✅ Concurrent rendering
✅ Automatic batching
✅ Modern features
❌ Learning curve
❌ Migration effort
❌ Some breaking changes
New projects
High interactivity
Performance-critical

Decision Tree: Upgrade to React 18?

START: Should I upgrade to React 18?

├─ App cần Concurrent features? (useTransition, Suspense)
│  ├─ YES → ✅ Upgrade
│  └─ NO → Continue

├─ Performance issues với frequent state updates?
│  ├─ YES → ✅ Upgrade (automatic batching helps)
│  └─ NO → Continue

├─ Using Suspense for data fetching?
│  ├─ YES → ✅ Upgrade (better support)
│  └─ NO → Continue

├─ App is stable, no issues?
│  ├─ YES → ⚠️ Optional (low risk, minor benefits)
│  └─ NO → Continue

└─ Legacy codebase, high risk?
   └─ NO → ⚠️ Test thoroughly before upgrade

Batching Comparison

jsx
// SCENARIO: Multiple state updates trong async callback

// ❌ React 17
setTimeout(() => {
  setA(1); // Render 1
  setB(2); // Render 2
  setC(3); // Render 3
}, 100);
// Total: 3 renders → Performance issue!

// ✅ React 18
setTimeout(() => {
  setA(1);
  setB(2);
  setC(3);
}, 100);
// Total: 1 render → Optimized! ✨

// WHEN TO OPT-OUT (rare cases):
import { flushSync } from 'react-dom';

setTimeout(() => {
  flushSync(() => setA(1)); // Render immediately
  flushSync(() => setB(2)); // Render immediately
  setC(3); // Batched with next update
}, 100);
// Use case: Need immediate DOM update for measurements

🧪 PHẦN 5: DEBUG LAB (20 phút)

Bug 1: Expecting Automatic Performance Improvements

jsx
/**
 * ❌ BUG: Upgraded to React 18 nhưng vẫn chậm
 *
 * Symptom: UI vẫn lag khi typing, không có improvement
 * Root cause: Concurrent features are OPT-IN, không tự động
 */
function SlowSearch() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = (e) => {
    const value = e.target.value;
    setQuery(value);

    // ❌ Heavy sync operation vẫn block UI
    const filtered = heavyFilter(allData, value);
    setResults(filtered);
  };

  return (
    <input
      value={query}
      onChange={handleSearch}
    />
  );
}

// ❓ CÂU HỎI:
// 1. Tại sao React 18 không tự động làm nhanh hơn?
// 2. Solution là gì?
// 3. Khi nào dùng useTransition? (sẽ học ngày sau)

💡 Giải thích:

  1. Tại sao không tự động nhanh hơn?

    • Concurrent rendering là OPT-IN
    • Cần dùng useTransition hoặc useDeferredValue
    • Heavy computation vẫn cần optimize (memoization, code splitting)
  2. Solutions:

    jsx
    // Solution 1: useMemo để tránh re-compute
    const filtered = useMemo(() => heavyFilter(allData, query), [query]);
    
    // Solution 2: useTransition (sẽ học Ngày 47)
    // const [isPending, startTransition] = useTransition();
    // startTransition(() => setResults(filtered));
    
    // Solution 3: Web Worker (advanced)
    // Offload to background thread
  3. Khi nào dùng useTransition:

    • Update không urgent (search results, filtering)
    • User cần feedback ngay (input field)
    • Heavy computation có thể defer

Bug 2: Infinite Renders sau Upgrade

jsx
/**
 * ❌ BUG: Component re-render vô hạn sau upgrade React 18
 *
 * Symptom: Browser freeze, console đầy "Render"
 * Root cause: useEffect dependency issue amplified bởi automatic batching
 */
function BuggyComponent() {
  const [data, setData] = useState([]);
  const [filter, setFilter] = useState('all');

  console.log('Render');

  useEffect(() => {
    // ❌ Creates new array reference every time
    const filtered = data.filter(
      (item) => filter === 'all' || item.type === filter,
    );
    setData(filtered); // This triggers re-render!
  }, [data, filter]); // data changes → effect runs → setData → data changes...

  return <div>{data.length} items</div>;
}

// ❓ CÂU HỎI:
// 1. Tại sao infinite loop xảy ra?
// 2. React 17 có bug này không? Tại sao React 18 amplify?
// 3. Fix như thế nào?

💡 Giải thích:

  1. Tại sao infinite loop:

    • useEffect depends on data
    • Effect runs → setData(filtered)data changes
    • data changes → effect runs again → infinite loop!
  2. React 17 vs 18:

    • React 17: Bug vẫn có nhưng có thể ít obvious hơn
    • React 18: Automatic batching làm loop nhanh hơn → dễ phát hiện
  3. Fix:

    jsx
    // ✅ Fix 1: Remove data từ dependencies
    useEffect(() => {
      setData((prev) =>
        prev.filter((item) => filter === 'all' || item.type === filter),
      );
    }, [filter]); // Chỉ depend on filter
    
    // ✅ Fix 2: useMemo thay vì useEffect
    const filteredData = useMemo(
      () =>
        originalData.filter((item) => filter === 'all' || item.type === filter),
      [filter],
    );
    
    // ✅ Fix 3: Separate filtered state
    const [rawData, setRawData] = useState([]);
    const [filteredData, setFilteredData] = useState([]);
    
    useEffect(() => {
      setFilteredData(
        rawData.filter((item) => filter === 'all' || item.type === filter),
      );
    }, [rawData, filter]); // Safe - rawData không update trong effect

Bug 3: flushSync Misuse

jsx
/**
 * ❌ BUG: Overusing flushSync causing performance regression
 *
 * Symptom: React 18 chậm hơn React 17!
 * Root cause: Opt-out khỏi batching unnecessarily
 */
import { flushSync } from 'react-dom';

function OverFlushSync() {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(false);

  const handleLoad = async () => {
    // ❌ Unnecessary flushSync
    flushSync(() => {
      setLoading(true);
    });

    const data = await fetchData();

    // ❌ Another unnecessary flushSync
    flushSync(() => {
      setItems(data);
    });

    // ❌ And another!
    flushSync(() => {
      setLoading(false);
    });
  };

  return (
    <button onClick={handleLoad}>{loading ? 'Loading...' : 'Load Data'}</button>
  );
}

// ❓ CÂU HỎI:
// 1. Tại sao code này slow?
// 2. Khi nào NÊN dùng flushSync?
// 3. Fix như thế nào?

💡 Giải thích:

  1. Tại sao slow:

    • flushSync force synchronous render
    • Mỗi flushSync call = 1 render ngay lập tức
    • Total: 3 synchronous renders thay vì 1 batched render
    • Mất đi benefit của automatic batching!
  2. Khi NÊN dùng flushSync:

    jsx
    // ✅ Use case 1: Cần DOM measurement ngay
    flushSync(() => {
      setHeight(100);
    });
    const actualHeight = ref.current.offsetHeight; // Accurate!
    
    // ✅ Use case 2: Third-party lib cần sync DOM
    flushSync(() => {
      setOpen(true);
    });
    thirdPartyLib.focus(ref.current); // DOM đã update!
    
    // ✅ Use case 3: Scroll position restore
    flushSync(() => {
      setItems(newItems);
    });
    scrollToPosition(savedPosition);
  3. Fix:

    jsx
    // ✅ Let React batch automatically
    const handleLoad = async () => {
      setLoading(true);
      const data = await fetchData();
    
      // React 18 tự động batch cả 2 updates này!
      setItems(data);
      setLoading(false);
      // Total: 1 render ✨
    };
    
    // Hoặc nếu muốn explicit:
    const handleLoad = async () => {
      setLoading(true);
      const data = await fetchData();
    
      // Batched update
      React.startTransition(() => {
        setItems(data);
        setLoading(false);
      });
    };

✅ PHẦN 6: TỰ ĐÁNH GIÁ (15 phút)

Knowledge Check

  • [ ] Tôi hiểu sự khác biệt giữa Synchronous và Concurrent rendering
  • [ ] Tôi biết Automatic Batching hoạt động như thế nào trong React 18
  • [ ] Tôi có thể dùng Profiler component để measure performance
  • [ ] Tôi hiểu khi nào nên upgrade lên React 18
  • [ ] Tôi biết khi nào dùng flushSync và khi nào KHÔNG nên dùng
  • [ ] Tôi có thể identify performance bottlenecks bằng React DevTools
  • [ ] Tôi hiểu Concurrent features là opt-in, không automatic

Code Review Checklist

React 18 Migration:

  • [ ] Đã test automatic batching trong async callbacks
  • [ ] Verify không có breaking changes trong codebase
  • [ ] useEffect dependencies vẫn correct sau upgrade
  • [ ] Performance không regression (measure before/after)

Performance Monitoring:

  • [ ] Profiler component được dùng đúng cách
  • [ ] Metrics tracking không ảnh hưởng production
  • [ ] React DevTools được dùng để identify bottlenecks
  • [ ] Slow renders được document và track

Best Practices:

  • [ ] Không overuse flushSync
  • [ ] useMemo/useCallback được dùng khi cần
  • [ ] Component không re-render unnecessarily
  • [ ] Performance monitoring chỉ trong development

🏠 BÀI TẬP VỀ NHÀ

Bắt buộc (30 phút)

1. Batching Verification Lab:

  • Tạo component test automatic batching
  • So sánh behavior trong different contexts:
    • Event handlers
    • setTimeout
    • Promises
    • fetch callbacks
  • Document findings với screenshots

2. Profiler Integration:

  • Add Profiler component vào 1 existing project
  • Identify top 3 slowest components
  • Document render times và potential optimizations

Nâng cao (60 phút)

1. Migration Guide:

  • Document step-by-step guide để upgrade project từ React 17 → 18
  • Include:
    • Breaking changes checklist
    • Testing strategy
    • Rollback plan
    • Performance comparison

2. Performance Audit:

  • Chọn 1 component trong project
  • Measure với React DevTools Profiler
  • Identify performance issues
  • Implement optimizations
  • Document before/after metrics

📚 TÀI LIỆU THAM KHẢO

Bắt buộc đọc

  1. React 18 Release Notes

  2. New in React 18

Đọc thêm

  1. Concurrent Rendering Deep Dive

  2. Performance Profiling Guide


🔗 KẾT NỐI KIẾN THỨC

Kiến thức nền (cần biết)

  • Ngày 11-14: useState và state updates
  • Ngày 16-20: useEffect và side effects
  • Ngày 31-34: Performance optimization (memo, useMemo, useCallback)
  • Profiler API: Đã biết từ demo trước

Hướng tới (sẽ dùng)

  • Ngày 47: useTransition - Non-urgent updates
  • Ngày 48: useDeferredValue - Deferred values
  • Ngày 49: Suspense for Data Fetching
  • Ngày 50: Error Boundaries
  • Ngày 51: React Server Components

💡 SENIOR INSIGHTS

Cân Nhắc Production

1. Migration Strategy:

PHASED APPROACH:
Week 1: Upgrade dependencies, test builds
Week 2: Test critical paths, fix breaking changes
Week 3: Performance monitoring
Week 4: Gradual rollout (10% → 50% → 100% users)

2. Monitoring:

  • Track render times before/after upgrade
  • Watch for performance regressions
  • Monitor error rates
  • Set up alerts cho slow renders

3. Rollback Plan:

  • Keep React 17 build ready
  • Feature flags để toggle concurrent features
  • Incremental adoption strategy

Câu Hỏi Phỏng Vấn

Junior Level:

  • Q: React 18 có gì mới so với React 17?
  • A: Automatic batching everywhere, concurrent rendering opt-in, new APIs (useTransition, useDeferredValue), Suspense improvements

Mid Level:

  • Q: Giải thích Automatic Batching và tại sao nó improve performance?
  • A: React 18 batches multiple state updates thành 1 render, ngay cả trong async callbacks (setTimeout, promises). Ít renders hơn = better performance. React 17 chỉ batch trong event handlers.

Senior Level:

  • Q: Khi nào nên migrate lên React 18? Trade-offs là gì?
  • A:
    • Migrate khi: Cần concurrent features, performance issues với frequent updates, modern SSR
    • Trade-offs: Learning curve, migration effort, potential breaking changes
    • Strategy: Test thoroughly, gradual rollout, monitor metrics, rollback plan ready

Architect Level:

  • Q: Design strategy để migrate large legacy app lên React 18 without downtime
  • A:
    1. Audit codebase (breaking changes, third-party deps)
    2. Phased migration (dev → staging → prod)
    3. Feature flags để toggle concurrent features
    4. Comprehensive testing (unit, integration, E2E, perf)
    5. Monitoring setup (render times, error rates)
    6. Gradual rollout (percentage-based)
    7. Rollback plan (React 17 build ready)
    8. Post-migration optimization (adopt new patterns)

War Stories

Story 1: "Automatic Batching Surprise"

Scenario: Upgraded to React 18, performance đột nhiên WORSE!

Root cause: Code rely on synchronous renders để trigger effects
useEffect(() => {
  // Expects setA to trigger render before setB
}, [a]);

setA(1);  // React 17: Render now
setB(2);  // React 17: Render now

setA(1);  // React 18: Batched
setB(2);  // React 18: Batched → 1 render → effect timing changed!

Fix: Review useEffect dependencies, use flushSync nếu cần sync behavior

Story 2: "The flushSync Trap"

Scenario: Developer đọc về flushSync, nghĩ nó "optimize" performance

Reality: Wrapped mọi setState trong flushSync
→ Opt-out khỏi batching completely
→ React 18 chậm hơn React 17!

Lesson: flushSync là escape hatch cho edge cases, không phải default

Story 3: "Profiler in Production"

Mistake: Ship Profiler component to production

Impact:
- Bundle size increase
- Runtime overhead (callbacks firing constantly)
- Memory leaks (storing metrics)

Fix:
if (process.env.NODE_ENV === 'development') {
  return <Profiler onRender={callback}>{children}</Profiler>;
}
return children;

🎯 PREVIEW NGÀY MAI

Ngày 47: useTransition - Non-urgent Updates

Tomorrow sẽ học:

  • useTransition hook để mark non-urgent updates
  • isPending state để show feedback
  • Pattern: Keep UI responsive during heavy updates
  • Use case: Search, filtering, sorting large datasets

Prepare:

  • Review concurrent rendering concepts hôm nay
  • Nghĩ về scenarios trong app của bạn cần "urgent vs non-urgent" updates
  • Cài đặt React DevTools mới nhất

See you tomorrow! 🚀

Personal tech knowledge base