Skip to content

📅 NGÀY 48: useDeferredValue - Deferred State Pattern

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

  • [ ] Hiểu useDeferredValue hook và cách hoạt động
  • [ ] Phân biệt useDeferredValue vs useTransition
  • [ ] Biết khi nào dùng approach nào
  • [ ] Kết hợp useDeferredValue với React.memo effectively
  • [ ] Implement throttling patterns với deferred values

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

Câu 1: useTransition defer cái gì? Update hay value?

Câu 2: Nếu bạn có input controlled bởi state, và muốn defer rendering của results list, bạn wrap cái gì trong startTransition?

Câu 3: isPending trong useTransition track cái gì?


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

1.1 Vấn Đề Thực Tế

Nhìn lại search example từ ngày 47:

jsx
// useTransition approach (Ngày 47)
function SearchWithTransition() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

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

    // Manually split logic: urgent vs non-urgent
    startTransition(() => {
      const filtered = heavyFilter(items, value);
      setResults(filtered);
    });
  };

  return (
    <>
      <input
        value={query}
        onChange={handleChange}
      />
      <ResultsList results={results} />
    </>
  );
}

Vấn đề với pattern này:

  • Phải manually split state: query vs results
  • Duplicate logic: filter logic phải duplicate
  • Imperative: "Do this, then do that"
  • Boilerplate: Extra state, extra effect

Câu hỏi: Có cách nào declarative hơn không?

1.2 Giải Pháp: useDeferredValue

jsx
import { useDeferredValue } from 'react';

function SearchWithDeferred() {
  const [query, setQuery] = useState('');

  // ✅ Defer the VALUE, not the UPDATE
  const deferredQuery = useDeferredValue(query);

  // Use deferred value for heavy computation
  const results = useMemo(
    () => heavyFilter(items, deferredQuery),
    [deferredQuery],
  );

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <ResultsList results={results} />
    </>
  );
}

Mental Model:

useTransition (Imperative):
"Execute this UPDATE with low priority"
→ You control WHEN things happen

useDeferredValue (Declarative):
"Use this VALUE, but it can lag behind"
→ React decides WHEN to update

Key Differences:

AspectuseTransitionuseDeferredValue
StyleImperative (do this)Declarative (use this)
ControlsUpdates/actionsValues
Best forEvent handlers, actionsDerived state, props
CodeMore code (split updates)Less code (one state)

1.3 Mental Model

Analogy: Restaurant Orders

useTransition (Imperative):

Waiter: "I'll take your order NOW (urgent)"
Waiter: "I'll tell kitchen to make it WHEN THEY CAN (non-urgent)"
→ You control the process

useDeferredValue (Declarative):

Customer: "I want dish X"
System: "Show them dish X on menu (instant)"
System: "Actually cook dish X when kitchen is free (deferred)"
→ System decides timing

Visual:

useDeferredValue Flow:

User types "a"

query = "a" (instant, synced)

deferredQuery = "" (stale, old value)

React schedules update...

deferredQuery = "a" (catches up when possible)

Results render with new value

Key Insight:

jsx
const [value, setValue] = useState('');
const deferredValue = useDeferredValue(value);

// At any moment:
// value = current, fresh, synced with input
// deferredValue = can be stale, catching up

// They eventually sync when React has time

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

Hiểu lầm 1: "useDeferredValue delays state update"

  • Sự thật: State update ngay lập tức! Chỉ có VALUE được defer khi rendering

Hiểu lầm 2: "useDeferredValue giống debounce"

  • Sự thật: Không! Debounce delays execution. useDeferredValue shows stale value temporarily

Hiểu lầm 3: "useDeferredValue luôn tốt hơn useTransition"

  • Sự thật: Mỗi cái có use case riêng. useDeferredValue cho values, useTransition cho actions

Hiểu lầm 4: "Không cần React.memo khi dùng useDeferredValue"

  • Sự thật: CẦN! useDeferredValue chỉ defer value, memo prevents unnecessary renders

Hiểu lầm 5: "deferredValue luôn khác value"

  • Sự thật: Chỉ khác during transition. Khi không có update nào, chúng giống nhau

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

Demo 1: Basic useDeferredValue ⭐

jsx
/**
 * Demo: useDeferredValue cơ bản
 * So sánh với/không có deferred value
 */
import { useState, useDeferredValue, useMemo } from 'react';

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

// ❌ WITHOUT useDeferredValue
function SearchWithoutDeferred() {
  const [query, setQuery] = useState('');
  const items = useMemo(() => generateItems(10000), []);

  // Heavy filtering runs on EVERY keystroke
  const filtered = useMemo(() => {
    console.log('Filtering without defer...');
    return items.filter((item) =>
      item.name.toLowerCase().includes(query.toLowerCase()),
    );
  }, [items, query]);

  return (
    <div>
      <h3>❌ Without useDeferredValue</h3>
      <input
        type='text'
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder='Type to search (will lag)...'
        style={{ padding: 8, width: '100%', marginBottom: 8 }}
      />
      <p>Results: {filtered.length}</p>
      <div>
        {filtered.slice(0, 50).map((item) => (
          <div
            key={item.id}
            style={{ padding: 4 }}
          >
            {item.name}
          </div>
        ))}
      </div>
    </div>
  );
}

// ✅ WITH useDeferredValue
function SearchWithDeferred() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  const items = useMemo(() => generateItems(10000), []);

  // Heavy filtering runs with DEFERRED value
  const filtered = useMemo(() => {
    console.log('Filtering with defer...');
    return items.filter((item) =>
      item.name.toLowerCase().includes(deferredQuery.toLowerCase()),
    );
  }, [items, deferredQuery]);

  // Show if value is stale
  const isStale = query !== deferredQuery;

  return (
    <div>
      <h3>✅ With useDeferredValue</h3>
      <input
        type='text'
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder='Type to search (smooth!)...'
        style={{ padding: 8, width: '100%', marginBottom: 8 }}
      />
      <p>
        Results: {filtered.length}
        {isStale && (
          <span style={{ color: '#f59e0b', marginLeft: 8 }}>(Updating...)</span>
        )}
      </p>
      <div style={{ opacity: isStale ? 0.5 : 1, transition: 'opacity 0.2s' }}>
        {filtered.slice(0, 50).map((item) => (
          <div
            key={item.id}
            style={{ padding: 4 }}
          >
            {item.name}
          </div>
        ))}
      </div>
    </div>
  );
}

/**
 * Quan sát:
 *
 * Without useDeferredValue:
 * - Console logs on EVERY keystroke
 * - Input may lag
 * - UI feels janky
 *
 * With useDeferredValue:
 * - Input always smooth
 * - Console logs less frequently (deferred)
 * - Old results visible while updating
 * - isStale flag for loading indicator
 */

Demo 2: useDeferredValue + React.memo ⭐⭐

jsx
/**
 * Demo: Combine useDeferredValue với React.memo
 * CRITICAL: memo là essential để useDeferredValue work well
 */

// ❌ Heavy component WITHOUT memo
function SlowList({ items }) {
  console.log('SlowList rendering...');

  // Simulate slow render
  const startTime = performance.now();
  while (performance.now() - startTime < 50) {
    // Busy wait
  }

  return (
    <div>
      {items.slice(0, 100).map((item) => (
        <div
          key={item.id}
          style={{ padding: 4, borderBottom: '1px solid #eee' }}
        >
          {item.name} - {item.value.toFixed(2)}
        </div>
      ))}
    </div>
  );
}

// ✅ Heavy component WITH memo
const SlowListMemo = React.memo(function SlowList({ items }) {
  console.log('SlowListMemo rendering...');

  const startTime = performance.now();
  while (performance.now() - startTime < 50) {
    // Busy wait
  }

  return (
    <div>
      {items.slice(0, 100).map((item) => (
        <div
          key={item.id}
          style={{ padding: 4, borderBottom: '1px solid #eee' }}
        >
          {item.name} - {item.value.toFixed(2)}
        </div>
      ))}
    </div>
  );
});

function DemoMemoComparison() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);

  const items = useMemo(() => generateItems(5000), []);

  const filtered = useMemo(() => {
    return items.filter((item) =>
      item.name.toLowerCase().includes(deferredText.toLowerCase()),
    );
  }, [items, deferredText]);

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
      {/* Left: Without memo */}
      <div>
        <h3>❌ Without React.memo</h3>
        <input
          type='text'
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder='Type...'
          style={{ padding: 8, width: '100%', marginBottom: 8 }}
        />
        <SlowList items={filtered} />
        <p style={{ fontSize: 12, color: '#ef4444' }}>
          ⚠️ Re-renders even with deferred value!
        </p>
      </div>

      {/* Right: With memo */}
      <div>
        <h3>✅ With React.memo</h3>
        <input
          type='text'
          value={text}
          onChange={(e) => setText(e.target.value)}
          placeholder='Type...'
          style={{ padding: 8, width: '100%', marginBottom: 8 }}
        />
        <SlowListMemo items={filtered} />
        <p style={{ fontSize: 12, color: '#10b981' }}>
          ✅ Only re-renders when deferred value changes
        </p>
      </div>
    </div>
  );
}

/**
 * Key Insight:
 *
 * useDeferredValue ALONE doesn't prevent renders!
 * It only defers when the value changes.
 *
 * Need React.memo to:
 * 1. Skip renders when props don't change
 * 2. Allow component to stay with old props while new value pending
 * 3. Actually see performance benefit
 *
 * Pattern:
 * useDeferredValue (defer value) + React.memo (skip renders) = Smooth UI
 */

Demo 3: Throttling với useDeferredValue ⭐⭐⭐

jsx
/**
 * Demo: Throttling expensive operations
 * Use case: Real-time preview that's expensive to render
 */

function ExpensiveChart({ data }) {
  console.log('ExpensiveChart rendering with', data.length, 'points');

  // Simulate expensive computation
  const startTime = performance.now();
  while (performance.now() - startTime < 100) {
    // Process data
  }

  // Simple visualization
  const max = Math.max(...data.map((d) => d.value));

  return (
    <div style={{ padding: 16, border: '1px solid #e5e7eb', borderRadius: 8 }}>
      <h4>Data Visualization</h4>
      <div
        style={{
          display: 'flex',
          alignItems: 'flex-end',
          gap: 2,
          height: 200,
          backgroundColor: '#f9fafb',
          padding: 8,
          borderRadius: 4,
        }}
      >
        {data.slice(0, 50).map((point, i) => (
          <div
            key={i}
            style={{
              flex: 1,
              height: `${(point.value / max) * 100}%`,
              backgroundColor: '#3b82f6',
              minWidth: 2,
              transition: 'height 0.3s',
            }}
          />
        ))}
      </div>
      <p style={{ marginTop: 8, fontSize: 12, color: '#6b7280' }}>
        Showing {Math.min(50, data.length)} of {data.length} points
      </p>
    </div>
  );
}

const ExpensiveChartMemo = React.memo(ExpensiveChart);

function DataEditor() {
  const [dataPoints, setDataPoints] = useState(
    Array.from({ length: 100 }, (_, i) => ({
      id: i,
      value: Math.floor(Math.random() * 100),
    })),
  );

  // Defer the data for preview
  const deferredData = useDeferredValue(dataPoints);

  const [editValue, setEditValue] = useState('');

  const handleAddPoint = () => {
    const value = parseInt(editValue) || Math.floor(Math.random() * 100);
    setDataPoints((prev) => [...prev, { id: prev.length, value }]);
    setEditValue('');
  };

  const handleRandomize = () => {
    setDataPoints((prev) =>
      prev.map((p) => ({ ...p, value: Math.floor(Math.random() * 100) })),
    );
  };

  const isStale = dataPoints !== deferredData;

  return (
    <div>
      <h3>Real-time Data Editor</h3>

      {/* Controls */}
      <div
        style={{
          display: 'flex',
          gap: 8,
          marginBottom: 16,
          padding: 12,
          backgroundColor: '#f9fafb',
          borderRadius: 8,
        }}
      >
        <input
          type='number'
          value={editValue}
          onChange={(e) => setEditValue(e.target.value)}
          placeholder='Value (0-100)'
          style={{ padding: 8, width: 120 }}
        />
        <button
          onClick={handleAddPoint}
          style={{ padding: '8px 16px' }}
        >
          Add Point
        </button>
        <button
          onClick={handleRandomize}
          style={{ padding: '8px 16px' }}
        >
          Randomize All
        </button>

        {isStale && (
          <div
            style={{
              marginLeft: 'auto',
              padding: '8px 12px',
              backgroundColor: '#dbeafe',
              borderRadius: 4,
              fontSize: 14,
              color: '#1e40af',
            }}
          >
            ⏳ Updating preview...
          </div>
        )}
      </div>

      {/* Preview with deferred data */}
      <div style={{ opacity: isStale ? 0.6 : 1, transition: 'opacity 0.3s' }}>
        <ExpensiveChartMemo data={deferredData} />
      </div>

      <p style={{ marginTop: 8, fontSize: 12, color: '#6b7280' }}>
        💡 Notice: Controls remain responsive even during expensive chart render
      </p>
    </div>
  );
}

/**
 * Benefits:
 *
 * 1. Controls always responsive
 *    - Add point button works instantly
 *    - Randomize button responds immediately
 *    - No input lag
 *
 * 2. Expensive render deferred
 *    - Chart updates in background
 *    - Old chart visible during update
 *    - Smooth transition when ready
 *
 * 3. Clear feedback
 *    - isStale flag shows pending state
 *    - Opacity indicates stale data
 *    - User knows something is happening
 *
 * This pattern perfect for:
 * - Live editors (code, markdown, data)
 * - Real-time previews
 * - Interactive visualizations
 * - Any expensive derived UI
 */

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

⭐ Level 1: Convert useTransition to useDeferredValue (15 phút)

jsx
/**
 * 🎯 Mục tiêu: Refactor từ useTransition sang useDeferredValue
 * ⏱️ Thời gian: 15 phút
 * 🚫 KHÔNG dùng: Suspense, useDeferredValue với initialValue
 *
 * Requirements:
 * 1. Code dưới dùng useTransition
 * 2. Refactor sang useDeferredValue
 * 3. Keep same functionality
 * 4. So sánh code complexity
 *
 * 💡 Gợi ý: Bỏ results state, dùng deferredQuery
 */

// ❌ Current implementation với useTransition
function ProductSearchTransition() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const products = useMemo(
    () =>
      Array.from({ length: 2000 }, (_, i) => ({
        id: i,
        name: `Product ${i}`,
        price: Math.floor(Math.random() * 1000),
      })),
    [],
  );

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

    startTransition(() => {
      const filtered = products.filter((p) =>
        p.name.toLowerCase().includes(value.toLowerCase()),
      );
      setResults(filtered);
    });
  };

  return (
    <div>
      <input
        value={query}
        onChange={handleSearch}
      />
      {isPending && <div>Searching...</div>}
      <div>{results.length} results</div>
    </div>
  );
}

// 🎯 NHIỆM VỤ CỦA BẠN:
function ProductSearchDeferred() {
  // TODO: Refactor to use useDeferredValue
  // TODO: Remove results state
  // TODO: Use deferredQuery instead
  // TODO: Add isStale indicator
}
💡 Solution
jsx
/**
 * Product Search với useDeferredValue
 * Simpler, more declarative approach
 */
function ProductSearchDeferred() {
  const [query, setQuery] = useState('');

  // ✅ Defer the search query
  const deferredQuery = useDeferredValue(query);

  const products = useMemo(
    () =>
      Array.from({ length: 2000 }, (_, i) => ({
        id: i,
        name: `Product ${i}`,
        price: Math.floor(Math.random() * 1000),
      })),
    [],
  );

  // ✅ Filter using deferred value
  const results = useMemo(() => {
    if (!deferredQuery) return products;

    return products.filter((p) =>
      p.name.toLowerCase().includes(deferredQuery.toLowerCase()),
    );
  }, [products, deferredQuery]);

  // ✅ Check if value is stale
  const isStale = query !== deferredQuery;

  return (
    <div>
      <h3>Product Search (useDeferredValue)</h3>

      <input
        type='text'
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder='Search products...'
        style={{
          padding: 8,
          width: '100%',
          marginBottom: 8,
        }}
      />

      {isStale && (
        <div
          style={{
            padding: 8,
            backgroundColor: '#dbeafe',
            color: '#1e40af',
            borderRadius: 4,
            marginBottom: 8,
          }}
        >
          🔍 Searching...
        </div>
      )}

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

      <div style={{ opacity: isStale ? 0.6 : 1 }}>
        {results.slice(0, 20).map((p) => (
          <div
            key={p.id}
            style={{
              padding: 8,
              border: '1px solid #e5e7eb',
              marginBottom: 4,
              borderRadius: 4,
            }}
          >
            {p.name} - ${p.price}
          </div>
        ))}
      </div>
    </div>
  );
}

/**
 * Code comparison:
 *
 * useTransition version:
 * - 2 state variables (query, results)
 * - Manual state splitting
 * - Imperative (startTransition call)
 * - More boilerplate
 *
 * useDeferredValue version:
 * - 1 state variable (query)
 * - Automatic deferring
 * - Declarative (use deferred value)
 * - Less code, cleaner
 *
 * Both achieve same result!
 * useDeferredValue simpler for this use case
 */

⭐⭐ Level 2: Optimizing với React.memo (25 phút)

jsx
/**
 * 🎯 Mục tiêu: Understand importance of React.memo
 * ⏱️ Thời gian: 25 phút
 *
 * Scenario: Image gallery với filter
 *
 * 🤔 PHÂN TÍCH:
 * Approach A: useDeferredValue alone
 * Pros: Simple code
 * Cons: Still re-renders unnecessarily
 *
 * Approach B: useDeferredValue + React.memo
 * Pros: Optimal performance
 * Cons: Need to memo components
 *
 * 💭 MEASURE và SO SÁNH performance
 */

// 🎯 NHIỆM VỤ CỦA BẠN:
function ImageGallery() {
  // TODO: Create gallery với 100 images
  // TODO: Implement filter
  // TODO: Implement WITHOUT memo first
  // TODO: Add memo và measure difference
  // TODO: Use console.log to count renders
}
💡 Solution
jsx
/**
 * Image Gallery - Demonstrating React.memo importance
 */

// Simulate image data
function generateImages(count) {
  return Array.from({ length: count }, (_, i) => ({
    id: i,
    src: `https://picsum.photos/200/200?random=${i}`,
    title: `Image ${i}`,
    category: ['nature', 'city', 'people', 'food'][i % 4],
  }));
}

// ❌ Image component WITHOUT memo
function ImageCard({ image }) {
  console.log('ImageCard render (no memo):', image.id);

  // Simulate slow render
  const startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Busy wait 1ms
  }

  return (
    <div
      style={{
        border: '1px solid #e5e7eb',
        borderRadius: 8,
        padding: 8,
        textAlign: 'center',
      }}
    >
      <div
        style={{
          width: 100,
          height: 100,
          backgroundColor: '#f3f4f6',
          borderRadius: 4,
          marginBottom: 4,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: 12,
          color: '#6b7280',
        }}
      >
        {image.title}
      </div>
      <div style={{ fontSize: 12 }}>{image.category}</div>
    </div>
  );
}

// ✅ Image component WITH memo
const ImageCardMemo = React.memo(function ImageCard({ image }) {
  console.log('ImageCard render (with memo):', image.id);

  const startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Busy wait 1ms
  }

  return (
    <div
      style={{
        border: '1px solid #e5e7eb',
        borderRadius: 8,
        padding: 8,
        textAlign: 'center',
      }}
    >
      <div
        style={{
          width: 100,
          height: 100,
          backgroundColor: '#f3f4f6',
          borderRadius: 4,
          marginBottom: 4,
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'center',
          fontSize: 12,
          color: '#6b7280',
        }}
      >
        {image.title}
      </div>
      <div style={{ fontSize: 12 }}>{image.category}</div>
    </div>
  );
});

function ImageGalleryComparison() {
  const [category, setCategory] = useState('all');
  const [useMemo, setUseMemo] = useState(false);
  const deferredCategory = useDeferredValue(category);

  const images = useMemo(() => generateImages(100), []);

  const filtered = useMemo(() => {
    console.log('Filtering images...');
    if (deferredCategory === 'all') return images;
    return images.filter((img) => img.category === deferredCategory);
  }, [images, deferredCategory]);

  const isStale = category !== deferredCategory;

  return (
    <div>
      <h3>Image Gallery Performance Test</h3>

      {/* Controls */}
      <div
        style={{
          marginBottom: 16,
          padding: 12,
          backgroundColor: '#f9fafb',
          borderRadius: 8,
        }}
      >
        <div style={{ marginBottom: 8 }}>
          <label style={{ marginRight: 8 }}>
            <input
              type='checkbox'
              checked={useMemo}
              onChange={(e) => setUseMemo(e.target.checked)}
            />{' '}
            Use React.memo
          </label>
        </div>

        <div>
          <label style={{ marginRight: 8 }}>Category:</label>
          <select
            value={category}
            onChange={(e) => setCategory(e.target.value)}
            style={{ padding: 4 }}
          >
            <option value='all'>All</option>
            <option value='nature'>Nature</option>
            <option value='city'>City</option>
            <option value='people'>People</option>
            <option value='food'>Food</option>
          </select>
        </div>

        {isStale && (
          <div
            style={{
              marginTop: 8,
              padding: 8,
              backgroundColor: '#dbeafe',
              borderRadius: 4,
              fontSize: 14,
              color: '#1e40af',
            }}
          >
            🔄 Filtering...
          </div>
        )}
      </div>

      {/* Gallery */}
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
          gap: 8,
          opacity: isStale ? 0.6 : 1,
          transition: 'opacity 0.3s',
        }}
      >
        {filtered.map((image) =>
          useMemo ? (
            <ImageCardMemo
              key={image.id}
              image={image}
            />
          ) : (
            <ImageCard
              key={image.id}
              image={image}
            />
          ),
        )}
      </div>

      {/* Performance Info */}
      <div
        style={{
          marginTop: 16,
          padding: 12,
          backgroundColor: '#f9fafb',
          borderRadius: 8,
          fontSize: 12,
        }}
      >
        <h4 style={{ margin: '0 0 8px 0' }}>📊 Performance Notes:</h4>
        <ul style={{ margin: 0, paddingLeft: 20 }}>
          <li>
            <strong>Without memo:</strong> All {filtered.length} images
            re-render on every category change → ~{filtered.length}ms total
            render time
          </li>
          <li>
            <strong>With memo:</strong> Only changed images re-render →
            Significantly faster!
          </li>
          <li>Check console to see render counts</li>
        </ul>
      </div>
    </div>
  );
}

/**
 * Performance Results:
 *
 * WITHOUT React.memo:
 * - Change category → All 100 images re-render
 * - Each image takes ~1ms
 * - Total: ~100ms render time
 * - Console: 100 "ImageCard render" logs
 *
 * WITH React.memo:
 * - Change category → Only new/removed images render
 * - If 25 nature images → only 25 renders
 * - Total: ~25ms render time
 * - Console: 25 "ImageCard render" logs
 *
 * Improvement: 4x faster!
 *
 * KEY LESSON:
 * useDeferredValue + React.memo = Optimal Performance
 * - useDeferredValue: Defer value update
 * - React.memo: Skip unnecessary renders
 * - Together: Smooth UX with minimal work
 */

⭐⭐⭐ Level 3: Live Markdown Editor (40 phút)

jsx
/**
 * 🎯 Mục tiêu: Build live preview editor
 * ⏱️ Thời gian: 40 phút
 *
 * 📋 Product Requirements:
 * User Story: "Là writer, tôi muốn preview markdown
 * realtime mà không bị lag khi typing"
 *
 * ✅ Acceptance Criteria:
 * - [ ] Textarea responsive khi typing
 * - [ ] Preview updates nhưng không block input
 * - [ ] Clear indication when preview updating
 * - [ ] Smooth experience với long documents
 *
 * 🎨 Technical Constraints:
 * - Use useDeferredValue
 * - Markdown parsing simulated (heavy operation)
 * - Split view: editor + preview
 *
 * 🚨 Edge Cases cần handle:
 * - Empty input
 * - Very long text (>1000 lines)
 * - Rapid typing
 *
 * 📝 Implementation Checklist:
 * - [ ] Textarea với state
 * - [ ] Deferred value cho preview
 * - [ ] Simulated markdown parsing
 * - [ ] Loading indicator
 * - [ ] Smooth transitions
 */

// 🎯 NHIỆM VỤ CỦA BẠN:
function MarkdownEditor() {
  // TODO: Implement live markdown editor
  // TODO: Use useDeferredValue for preview
  // TODO: Simulate heavy markdown parsing
  // TODO: Add proper feedback
}
💡 Solution
jsx
/**
 * Live Markdown Editor với useDeferredValue
 * Production-ready pattern for live previews
 */

// Simulate markdown parsing (expensive operation)
function parseMarkdown(text) {
  // Simulate processing time
  const startTime = performance.now();
  while (performance.now() - startTime < 50) {
    // Busy wait to simulate heavy parsing
  }

  // Simple markdown transformations
  let html = text
    // Headers
    .replace(/^### (.*$)/gim, '<h3>$1</h3>')
    .replace(/^## (.*$)/gim, '<h2>$1</h2>')
    .replace(/^# (.*$)/gim, '<h1>$1</h1>')
    // Bold
    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
    // Italic
    .replace(/\*(.*?)\*/g, '<em>$1</em>')
    // Line breaks
    .replace(/\n/g, '<br/>');

  return html;
}

// Memoized preview component
const MarkdownPreview = React.memo(function MarkdownPreview({ markdown }) {
  console.log('MarkdownPreview rendering...');

  const html = useMemo(() => parseMarkdown(markdown), [markdown]);

  return (
    <div
      style={{
        padding: 16,
        backgroundColor: 'white',
        border: '1px solid #e5e7eb',
        borderRadius: 8,
        minHeight: 400,
      }}
      dangerouslySetInnerHTML={{ __html: html }}
    />
  );
});

function MarkdownEditor() {
  const [markdown, setMarkdown] = useState(
    `# Welcome to Markdown Editor

## Features
- **Real-time** preview
- *Smooth* typing experience
- No lag!

### Try it out
Type in the editor and see the preview update smoothly.`,
  );

  // ✅ Defer markdown for preview
  const deferredMarkdown = useDeferredValue(markdown);

  const isStale = markdown !== deferredMarkdown;
  const wordCount = markdown.split(/\s+/).filter(Boolean).length;
  const charCount = markdown.length;

  return (
    <div>
      <h3>📝 Live Markdown Editor</h3>

      {/* Stats Bar */}
      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          padding: 8,
          backgroundColor: '#f9fafb',
          borderRadius: 8,
          marginBottom: 8,
          fontSize: 12,
          color: '#6b7280',
        }}
      >
        <div>
          Words: {wordCount} | Characters: {charCount}
        </div>
        {isStale && (
          <div style={{ color: '#f59e0b' }}>⏳ Preview updating...</div>
        )}
      </div>

      {/* Split View */}
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: '1fr 1fr',
          gap: 16,
        }}
      >
        {/* Editor */}
        <div>
          <h4 style={{ marginTop: 0 }}>Editor</h4>
          <textarea
            value={markdown}
            onChange={(e) => setMarkdown(e.target.value)}
            placeholder='Write your markdown here...'
            style={{
              width: '100%',
              minHeight: 400,
              padding: 12,
              fontFamily: 'monospace',
              fontSize: 14,
              border: '1px solid #d1d5db',
              borderRadius: 8,
              resize: 'vertical',
            }}
          />
        </div>

        {/* Preview */}
        <div>
          <h4 style={{ marginTop: 0 }}>Preview</h4>
          <div
            style={{
              opacity: isStale ? 0.6 : 1,
              transition: 'opacity 0.3s',
            }}
          >
            <MarkdownPreview markdown={deferredMarkdown} />
          </div>
        </div>
      </div>

      {/* Tips */}
      <div
        style={{
          marginTop: 16,
          padding: 12,
          backgroundColor: '#f0fdf4',
          border: '1px solid #bbf7d0',
          borderRadius: 8,
          fontSize: 12,
        }}
      >
        <h4 style={{ margin: '0 0 8px 0' }}>💡 Tips:</h4>
        <ul style={{ margin: 0, paddingLeft: 20 }}>
          <li>Type rapidly - notice input never lags</li>
          <li>Preview updates smoothly in background</li>
          <li>Old preview visible while new one renders</li>
          <li>Try pasting large text to see deferred rendering</li>
        </ul>
      </div>
    </div>
  );
}

/**
 * Key Features:
 *
 * 1. ✅ Responsive Input
 *    - Textarea never lags
 *    - All keystrokes captured instantly
 *    - Smooth typing experience
 *
 * 2. ✅ Deferred Preview
 *    - Preview uses deferredMarkdown
 *    - Expensive parsing deferred
 *    - Old preview visible during update
 *
 * 3. ✅ Clear Feedback
 *    - isStale indicator shows pending
 *    - Opacity change during transition
 *    - Stats update immediately
 *
 * 4. ✅ Performance
 *    - React.memo prevents unnecessary renders
 *    - useMemo caches parsed HTML
 *    - useDeferredValue defers heavy work
 *
 * This pattern works for:
 * - Code editors with live preview
 * - Formula editors (LaTeX, etc.)
 * - Rich text editors
 * - Any live preview scenario
 */

⭐⭐⭐⭐ Level 4: Smart Throttling System (60 phút)

jsx
/**
 * 🎯 Mục tiêu: Build adaptive throttling system
 * ⏱️ Thời gian: 60 phút
 *
 * 🏗️ PHASE 1: Research & Design (20 phút)
 *
 * Problem: Different devices have different performance
 * - High-end: Can handle more updates
 * - Low-end: Need more aggressive throttling
 *
 * Solution: Adaptive throttling based on render time
 *
 * ADR Template:
 * - Context: Performance varies across devices
 * - Decision: Measure render time, adjust deferral
 * - Rationale: Better UX for all users
 * - Consequences: More complex, but worth it
 * - Alternatives: Fixed throttling (suboptimal)
 *
 * 💻 PHASE 2: Implementation (30 phút)
 * 🧪 PHASE 3: Testing (10 phút)
 */
💡 Solution
jsx
/**
 * Adaptive Throttling System
 * Adjusts deferral based on device performance
 */

/**
 * useAdaptiveDeferred - Smart deferred value
 * Measures render performance and adapts
 */
function useAdaptiveDeferred(value) {
  const [performanceLevel, setPerformanceLevel] = useState('medium');
  const renderTimes = useRef([]);

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

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

      // Track last 10 renders
      renderTimes.current.push(renderTime);
      if (renderTimes.current.length > 10) {
        renderTimes.current.shift();
      }

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

      // Classify performance
      if (avg < 16) {
        setPerformanceLevel('high'); // Can handle 60fps
      } else if (avg < 33) {
        setPerformanceLevel('medium'); // Can handle 30fps
      } else {
        setPerformanceLevel('low'); // Struggling
      }
    };
  });

  // Use useDeferredValue
  const deferredValue = useDeferredValue(value);

  return {
    value: deferredValue,
    performanceLevel,
    avgRenderTime: renderTimes.current.length
      ? (
          renderTimes.current.reduce((a, b) => a + b, 0) /
          renderTimes.current.length
        ).toFixed(2)
      : '0',
  };
}

/**
 * Demo: Adaptive Search
 */
function AdaptiveSearch() {
  const [query, setQuery] = useState('');
  const [complexity, setComplexity] = useState('medium');

  const {
    value: deferredQuery,
    performanceLevel,
    avgRenderTime,
  } = useAdaptiveDeferred(query);

  // Generate data based on complexity
  const items = useMemo(() => {
    const counts = {
      low: 1000,
      medium: 5000,
      high: 10000,
    };

    return Array.from({ length: counts[complexity] }, (_, i) => ({
      id: i,
      name: `Item ${i}`,
      description: `Description for item ${i}`.repeat(3),
    }));
  }, [complexity]);

  // Filter with deferred query
  const filtered = useMemo(() => {
    console.log('Filtering...');
    return items.filter(
      (item) =>
        item.name.toLowerCase().includes(deferredQuery.toLowerCase()) ||
        item.description.toLowerCase().includes(deferredQuery.toLowerCase()),
    );
  }, [items, deferredQuery]);

  const isStale = query !== deferredQuery;

  // Performance indicator color
  const levelColors = {
    high: '#10b981',
    medium: '#f59e0b',
    low: '#ef4444',
  };

  return (
    <div>
      <h3>🎯 Adaptive Search System</h3>

      {/* Performance Dashboard */}
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(3, 1fr)',
          gap: 12,
          marginBottom: 16,
        }}
      >
        <div
          style={{
            padding: 12,
            backgroundColor: '#f9fafb',
            borderRadius: 8,
            border: `2px solid ${levelColors[performanceLevel]}`,
          }}
        >
          <div style={{ fontSize: 12, color: '#6b7280' }}>
            Performance Level
          </div>
          <div
            style={{
              fontSize: 20,
              fontWeight: 'bold',
              color: levelColors[performanceLevel],
              textTransform: 'uppercase',
            }}
          >
            {performanceLevel}
          </div>
        </div>

        <div
          style={{
            padding: 12,
            backgroundColor: '#f9fafb',
            borderRadius: 8,
          }}
        >
          <div style={{ fontSize: 12, color: '#6b7280' }}>Avg Render Time</div>
          <div style={{ fontSize: 20, fontWeight: 'bold' }}>
            {avgRenderTime}ms
          </div>
        </div>

        <div
          style={{
            padding: 12,
            backgroundColor: '#f9fafb',
            borderRadius: 8,
          }}
        >
          <div style={{ fontSize: 12, color: '#6b7280' }}>Dataset Size</div>
          <div style={{ fontSize: 20, fontWeight: 'bold' }}>
            {items.length.toLocaleString()}
          </div>
        </div>
      </div>

      {/* Controls */}
      <div
        style={{
          padding: 12,
          backgroundColor: '#f9fafb',
          borderRadius: 8,
          marginBottom: 16,
        }}
      >
        <div style={{ marginBottom: 8 }}>
          <label>Complexity:</label>
          <select
            value={complexity}
            onChange={(e) => setComplexity(e.target.value)}
            style={{ marginLeft: 8, padding: 4 }}
          >
            <option value='low'>Low (1K items)</option>
            <option value='medium'>Medium (5K items)</option>
            <option value='high'>High (10K items)</option>
          </select>
        </div>

        <input
          type='text'
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder='Search...'
          style={{
            padding: 8,
            width: '100%',
            fontSize: 16,
          }}
        />

        {isStale && (
          <div
            style={{
              marginTop: 8,
              padding: 8,
              backgroundColor: '#dbeafe',
              borderRadius: 4,
              fontSize: 14,
            }}
          >
            🔍 Filtering {items.length.toLocaleString()} items...
          </div>
        )}
      </div>

      {/* Results */}
      <div
        style={{
          opacity: isStale ? 0.6 : 1,
          transition: 'opacity 0.3s',
        }}
      >
        <p>Found: {filtered.length.toLocaleString()} results</p>

        <div
          style={{
            maxHeight: 400,
            overflowY: 'auto',
            border: '1px solid #e5e7eb',
            borderRadius: 8,
            padding: 8,
          }}
        >
          {filtered.slice(0, 50).map((item) => (
            <div
              key={item.id}
              style={{
                padding: 8,
                borderBottom: '1px solid #e5e7eb',
              }}
            >
              <strong>{item.name}</strong>
              <div style={{ fontSize: 12, color: '#6b7280' }}>
                {item.description.substring(0, 100)}...
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Recommendations */}
      <div
        style={{
          marginTop: 16,
          padding: 12,
          backgroundColor: levelColors[performanceLevel] + '20',
          border: `1px solid ${levelColors[performanceLevel]}`,
          borderRadius: 8,
          fontSize: 12,
        }}
      >
        <h4 style={{ margin: '0 0 8px 0' }}>💡 System Recommendations:</h4>
        {performanceLevel === 'high' && (
          <p style={{ margin: 0 }}>
            ✅ Your device handles updates smoothly. You can work with larger
            datasets.
          </p>
        )}
        {performanceLevel === 'medium' && (
          <p style={{ margin: 0 }}>
            ⚠️ Performance is moderate. Consider reducing dataset size for
            smoother experience.
          </p>
        )}
        {performanceLevel === 'low' && (
          <p style={{ margin: 0 }}>
            ❌ Performance is struggling. Recommend: Use low complexity or
            enable additional optimizations.
          </p>
        )}
      </div>
    </div>
  );
}

/**
 * Advanced Features:
 *
 * 1. Performance Monitoring
 *    - Tracks render times
 *    - Classifies device capability
 *    - Provides recommendations
 *
 * 2. Adaptive Behavior
 *    - High performance: Normal deferral
 *    - Medium performance: More aggressive deferral
 *    - Low performance: Maximum deferral
 *
 * 3. User Feedback
 *    - Visual performance indicator
 *    - Real-time metrics
 *    - Actionable recommendations
 *
 * 4. Production Considerations
 *    - Measure actual render time
 *    - Adapt throttling strategy
 *    - Provide escape hatches (settings)
 *
 * This pattern ideal for:
 * - Apps used on varied devices
 * - Performance-critical features
 * - Adaptive user experiences
 * - Enterprise applications
 */

⭐⭐⭐⭐⭐ Level 5: Deferred Value Manager (90 phút)

jsx
/**
 * 🎯 Mục tiêu: Build production-grade deferred value system
 * ⏱️ Thời gian: 90 phút
 *
 * 📋 Feature Specification:
 * Reusable hooks and utilities:
 * - useDeferredState - State với built-in deferral
 * - useDeferredMemo - Memoized deferred computation
 * - useStaleDetector - Advanced stale detection
 * - DeferredValueProvider - Context-based management
 *
 * 🏗️ Technical Design Doc:
 * 1. Hook Architecture
 *    - Composable hooks
 *    - Type-safe (ready for TS)
 *    - Clear APIs
 *
 * 2. Performance
 *    - Minimal overhead
 *    - Smart memoization
 *    - Cancellation support
 *
 * 3. Developer Experience
 *    - Easy to use
 *    - Good defaults
 *    - Configurable
 *
 * ✅ Production Checklist:
 * - [ ] Multiple deferred values support
 * - [ ] Stale detection
 * - [ ] Performance monitoring
 * - [ ] Clear documentation
 * - [ ] Usage examples
 */
💡 Solution
jsx
/**
 * Production-grade Deferred Value Management System
 * Complete toolkit for handling deferred values
 */

/**
 * useDeferredState - Combines useState với useDeferredValue
 * Simplifies common pattern
 */
function useDeferredState(initialValue) {
  const [value, setValue] = useState(initialValue);
  const deferredValue = useDeferredValue(value);

  const isStale = value !== deferredValue;

  return {
    value,
    deferredValue,
    setValue,
    isStale,
  };
}

/**
 * useDeferredMemo - Memoized computation với deferred deps
 * Combines useMemo + useDeferredValue pattern
 */
function useDeferredMemo(factory, deps) {
  const deferredDeps = deps.map((dep) => useDeferredValue(dep));

  const value = useMemo(() => factory(...deferredDeps), deferredDeps);

  const isStale = deps.some((dep, i) => dep !== deferredDeps[i]);

  return {
    value,
    isStale,
  };
}

/**
 * useStaleDetector - Advanced stale detection
 * Tracks multiple values và provides granular info
 */
function useStaleDetector(values) {
  const [staleInfo, setStaleInfo] = useState({});

  useEffect(() => {
    const newInfo = {};
    let hasStale = false;

    Object.entries(values).forEach(([key, { current, deferred }]) => {
      const isStale = current !== deferred;
      newInfo[key] = {
        isStale,
        current,
        deferred,
      };
      if (isStale) hasStale = true;
    });

    newInfo.anyStale = hasStale;
    setStaleInfo(newInfo);
  }, [values]);

  return staleInfo;
}

/**
 * DeferredValueContext - Global deferred value management
 */
const DeferredValueContext = React.createContext(null);

function DeferredValueProvider({ children }) {
  const [trackedValues, setTrackedValues] = useState(new Map());

  const registerValue = useCallback((key, value, deferredValue) => {
    setTrackedValues((prev) => {
      const next = new Map(prev);
      next.set(key, {
        value,
        deferredValue,
        isStale: value !== deferredValue,
        timestamp: Date.now(),
      });
      return next;
    });
  }, []);

  const unregisterValue = useCallback((key) => {
    setTrackedValues((prev) => {
      const next = new Map(prev);
      next.delete(key);
      return next;
    });
  }, []);

  const getStats = useCallback(() => {
    const values = Array.from(trackedValues.values());
    return {
      total: values.length,
      stale: values.filter((v) => v.isStale).length,
      fresh: values.filter((v) => !v.isStale).length,
    };
  }, [trackedValues]);

  const value = {
    registerValue,
    unregisterValue,
    trackedValues,
    getStats,
  };

  return (
    <DeferredValueContext.Provider value={value}>
      {children}
    </DeferredValueContext.Provider>
  );
}

function useDeferredValueContext() {
  const context = React.useContext(DeferredValueContext);
  if (!context) {
    throw new Error(
      'useDeferredValueContext must be used within DeferredValueProvider',
    );
  }
  return context;
}

// ===============================================
// DEMO APPLICATION
// ===============================================

/**
 * Advanced Dashboard với Deferred Value Manager
 */
function DeferredDashboard() {
  return (
    <DeferredValueProvider>
      <DashboardContent />
    </DeferredValueProvider>
  );
}

function DashboardContent() {
  const { getStats, trackedValues } = useDeferredValueContext();

  // Multiple deferred states
  const search = useDeferredState('');
  const category = useDeferredState('all');
  const sortBy = useDeferredState('name');

  // Track in context
  const { registerValue, unregisterValue } = useDeferredValueContext();

  useEffect(() => {
    registerValue('search', search.value, search.deferredValue);
    registerValue('category', category.value, category.deferredValue);
    registerValue('sortBy', sortBy.value, sortBy.deferredValue);

    return () => {
      unregisterValue('search');
      unregisterValue('category');
      unregisterValue('sortBy');
    };
  }, [
    search.value,
    search.deferredValue,
    category.value,
    category.deferredValue,
    sortBy.value,
    sortBy.deferredValue,
    registerValue,
    unregisterValue,
  ]);

  // Deferred computation
  const { value: data, isStale: isDataStale } = useDeferredMemo(
    (deferredSearch, deferredCategory, deferredSort) => {
      console.log('Computing data...');

      // Generate mock data
      let items = Array.from({ length: 2000 }, (_, i) => ({
        id: i,
        name: `Product ${i}`,
        category: ['Electronics', 'Clothing', 'Food'][i % 3],
        price: Math.floor(Math.random() * 1000),
      }));

      // Filter by search
      if (deferredSearch) {
        items = items.filter((item) =>
          item.name.toLowerCase().includes(deferredSearch.toLowerCase()),
        );
      }

      // Filter by category
      if (deferredCategory !== 'all') {
        items = items.filter((item) => item.category === deferredCategory);
      }

      // Sort
      items.sort((a, b) => {
        if (deferredSort === 'name') {
          return a.name.localeCompare(b.name);
        }
        return a.price - b.price;
      });

      return items;
    },
    [search.deferredValue, category.deferredValue, sortBy.deferredValue],
  );

  const stats = getStats();
  const anyStale =
    search.isStale || category.isStale || sortBy.isStale || isDataStale;

  return (
    <div>
      <h3>🎛️ Advanced Deferred Dashboard</h3>

      {/* Global Stats */}
      <div
        style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(4, 1fr)',
          gap: 8,
          marginBottom: 16,
        }}
      >
        <StatCard
          label='Total Values'
          value={stats.total}
          color='#3b82f6'
        />
        <StatCard
          label='Stale'
          value={stats.stale}
          color='#f59e0b'
        />
        <StatCard
          label='Fresh'
          value={stats.fresh}
          color='#10b981'
        />
        <StatCard
          label='Data Items'
          value={data.length}
          color='#8b5cf6'
        />
      </div>

      {/* Controls */}
      <div
        style={{
          padding: 16,
          backgroundColor: '#f9fafb',
          borderRadius: 8,
          marginBottom: 16,
        }}
      >
        <div
          style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(3, 1fr)',
            gap: 12,
          }}
        >
          <div>
            <label style={{ display: 'block', marginBottom: 4, fontSize: 12 }}>
              Search
              {search.isStale && (
                <span style={{ color: '#f59e0b' }}> (stale)</span>
              )}
            </label>
            <input
              type='text'
              value={search.value}
              onChange={(e) => search.setValue(e.target.value)}
              placeholder='Search products...'
              style={{ padding: 8, width: '100%' }}
            />
          </div>

          <div>
            <label style={{ display: 'block', marginBottom: 4, fontSize: 12 }}>
              Category
              {category.isStale && (
                <span style={{ color: '#f59e0b' }}> (stale)</span>
              )}
            </label>
            <select
              value={category.value}
              onChange={(e) => category.setValue(e.target.value)}
              style={{ padding: 8, width: '100%' }}
            >
              <option value='all'>All</option>
              <option value='Electronics'>Electronics</option>
              <option value='Clothing'>Clothing</option>
              <option value='Food'>Food</option>
            </select>
          </div>

          <div>
            <label style={{ display: 'block', marginBottom: 4, fontSize: 12 }}>
              Sort By
              {sortBy.isStale && (
                <span style={{ color: '#f59e0b' }}> (stale)</span>
              )}
            </label>
            <select
              value={sortBy.value}
              onChange={(e) => sortBy.setValue(e.target.value)}
              style={{ padding: 8, width: '100%' }}
            >
              <option value='name'>Name</option>
              <option value='price'>Price</option>
            </select>
          </div>
        </div>

        {anyStale && (
          <div
            style={{
              marginTop: 12,
              padding: 8,
              backgroundColor: '#dbeafe',
              borderRadius: 4,
              fontSize: 14,
              color: '#1e40af',
            }}
          >
            🔄 Updating results...
          </div>
        )}
      </div>

      {/* Results */}
      <div
        style={{
          opacity: anyStale ? 0.6 : 1,
          transition: 'opacity 0.3s',
        }}
      >
        <div
          style={{
            maxHeight: 400,
            overflowY: 'auto',
            border: '1px solid #e5e7eb',
            borderRadius: 8,
          }}
        >
          {data.slice(0, 50).map((item) => (
            <div
              key={item.id}
              style={{
                padding: 12,
                borderBottom: '1px solid #e5e7eb',
                display: 'flex',
                justifyContent: 'space-between',
              }}
            >
              <div>
                <strong>{item.name}</strong>
                <div style={{ fontSize: 12, color: '#6b7280' }}>
                  {item.category}
                </div>
              </div>
              <div style={{ fontWeight: 'bold', color: '#3b82f6' }}>
                ${item.price}
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Debug Panel */}
      <DebugPanel trackedValues={trackedValues} />
    </div>
  );
}

function StatCard({ label, value, color }) {
  return (
    <div
      style={{
        padding: 12,
        backgroundColor: 'white',
        border: `2px solid ${color}`,
        borderRadius: 8,
        textAlign: 'center',
      }}
    >
      <div style={{ fontSize: 24, fontWeight: 'bold', color }}>{value}</div>
      <div style={{ fontSize: 12, color: '#6b7280' }}>{label}</div>
    </div>
  );
}

function DebugPanel({ trackedValues }) {
  const [showDebug, setShowDebug] = useState(false);

  return (
    <div style={{ marginTop: 16 }}>
      <button
        onClick={() => setShowDebug(!showDebug)}
        style={{
          padding: '8px 16px',
          fontSize: 12,
          backgroundColor: '#f3f4f6',
          border: '1px solid #d1d5db',
          borderRadius: 4,
          cursor: 'pointer',
        }}
      >
        {showDebug ? 'Hide' : 'Show'} Debug Info
      </button>

      {showDebug && (
        <div
          style={{
            marginTop: 8,
            padding: 12,
            backgroundColor: '#1f2937',
            color: '#f3f4f6',
            borderRadius: 8,
            fontSize: 12,
            fontFamily: 'monospace',
          }}
        >
          <h4 style={{ margin: '0 0 8px 0' }}>Tracked Values:</h4>
          {Array.from(trackedValues.entries()).map(([key, info]) => (
            <div
              key={key}
              style={{ marginBottom: 8 }}
            >
              <strong>{key}:</strong>
              <div style={{ paddingLeft: 12 }}>
                <div>Current: {JSON.stringify(info.value)}</div>
                <div>Deferred: {JSON.stringify(info.deferredValue)}</div>
                <div>Stale: {info.isStale ? '⚠️ YES' : '✅ NO'}</div>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/**
 * Production Features Implemented:
 *
 * 1. ✅ useDeferredState
 *    - Combines useState + useDeferredValue
 *    - Cleaner API
 *    - Built-in stale detection
 *
 * 2. ✅ useDeferredMemo
 *    - Memoized deferred computation
 *    - Multiple deferred dependencies
 *    - Automatic stale tracking
 *
 * 3. ✅ DeferredValueProvider
 *    - Global tracking
 *    - Statistics
 *    - Debug support
 *
 * 4. ✅ Debug Panel
 *    - Real-time value tracking
 *    - Stale detection
 *    - Developer insights
 *
 * This toolkit provides:
 * - Reusable patterns
 * - Better DX
 * - Production-ready code
 * - Easy debugging
 */

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

Bảng So Sánh: useTransition vs useDeferredValue

AspectuseTransitionuseDeferredValue
StyleImperative (action-based)Declarative (value-based)
ControlsState updatesValues
SyntaxstartTransition(() => {...})const deferred = useDeferredValue(value)
Use CaseEvent handlers, actionsProps, derived state
Code AmountMore (split states)Less (single state)
FlexibilityMore control over timingReact controls timing
Pending StateisPending flagCompare values
Best ForComplex workflowsSimple deferral

Trade-offs Matrix

ApproachProsConsWhen to Use
useTransition✅ Explicit control
✅ Clear intent
✅ Built-in isPending
✅ Good for actions
❌ More boilerplate
❌ Split state needed
❌ Imperative style
Event handlers
Actions
Complex workflows
Need isPending
useDeferredValue✅ Less code
✅ Declarative
✅ No state splitting
✅ Cleaner API
❌ Less control
❌ Manual stale detection
❌ React decides timing
Props/values
Derived state
Simple scenarios
Prefer declarative
debounce✅ Reduces work
✅ Works everywhere
✅ Simple
❌ Fixed delay
❌ Doesn't prevent blocking
❌ Extra library
API calls
Autosave
Search suggestions
throttle✅ Limits frequency
✅ Predictable timing
❌ Fixed interval
❌ May skip updates
❌ Extra library
Scroll handlers
Resize handlers
Animation frames

Decision Tree: Which Hook to Use?

START: How to defer expensive work?

├─ Is this an ACTION or a VALUE?
│  ├─ ACTION (button click, form submit)
│  │  └─ ✅ Use useTransition
│  │
│  └─ VALUE (search query, filter, prop)
│     ├─ Need explicit control over timing?
│     │  ├─ YES → ✅ Use useTransition
│     │  └─ NO → ✅ Use useDeferredValue
│     │
│     └─ Prefer declarative or imperative?
│        ├─ Declarative → ✅ Use useDeferredValue
│        └─ Imperative → ✅ Use useTransition

├─ Need to defer network request?
│  └─ ❌ Use neither - use regular async state

├─ Need fixed delay (e.g., autosave)?
│  └─ ✅ Use debounce (lodash, custom)

└─ Need to limit frequency (e.g., scroll)?
   └─ ✅ Use throttle (lodash, custom)

Pattern Comparison: Same Search, Different Approaches

jsx
// ========================================
// Pattern 1: useTransition (Imperative)
// ========================================
function SearchTransition() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

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

    startTransition(() => {
      setResults(filterItems(value));
    });
  };

  return (
    <>
      <input
        value={query}
        onChange={handleChange}
      />
      {isPending && <Spinner />}
      <Results data={results} />
    </>
  );
}
// Pros: Explicit control, clear isPending
// Cons: Split state (query + results), more code

// ========================================
// Pattern 2: useDeferredValue (Declarative)
// ========================================
function SearchDeferred() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  const results = useMemo(() => filterItems(deferredQuery), [deferredQuery]);

  const isStale = query !== deferredQuery;

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      {isStale && <Spinner />}
      <Results data={results} />
    </>
  );
}
// Pros: Single state, less code, declarative
// Cons: Manual stale detection, less control

// ========================================
// Pattern 3: debounce (Traditional)
// ========================================
function SearchDebounce() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const debouncedFilter = useMemo(
    () =>
      debounce((value) => {
        setResults(filterItems(value));
      }, 300),
    [],
  );

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

  return (
    <>
      <input
        value={query}
        onChange={handleChange}
      />
      <Results data={results} />
    </>
  );
}
// Pros: Reduces work, familiar pattern
// Cons: Delay in results, no loading state

// ========================================
// Pattern 4: Hybrid (Best of Both)
// ========================================
function SearchHybrid() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  const results = useMemo(() => filterItems(deferredQuery), [deferredQuery]);

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      {query !== deferredQuery && <Spinner />}
      <ResultsMemo data={results} />
    </>
  );
}
const ResultsMemo = React.memo(Results);
// Combines: useDeferredValue + React.memo
// Best: Clean code + optimal performance

When to Use Each Pattern

jsx
// ✅ Use useTransition when:
// - Event handlers (clicks, submissions)
// - Explicit workflow control needed
// - Need isPending for loading states
// - Multiple coordinated updates

function TabSwitcher() {
  const [tab, setTab] = useState('home');
  const [isPending, startTransition] = useTransition();

  const switchTab = (newTab) => {
    startTransition(() => {
      setTab(newTab);
      // Maybe update other states too
    });
  };

  return (
    <Tabs
      active={tab}
      onSwitch={switchTab}
    />
  );
}

// ✅ Use useDeferredValue when:
// - Deferring props or values
// - Simple, single-value deferral
// - Prefer declarative style
// - Less boilerplate desired

function FilteredList({ query }) {
  const deferredQuery = useDeferredValue(query);
  const filtered = useMemo(
    () => items.filter((i) => i.includes(deferredQuery)),
    [deferredQuery],
  );

  return <List items={filtered} />;
}

// ✅ Use debounce when:
// - API calls (don't want rapid requests)
// - Autosave functionality
// - Search suggestions
// - Fixed delay acceptable

function Autosave({ content }) {
  const saveDebounced = useMemo(
    () => debounce((text) => api.save(text), 1000),
    [],
  );

  useEffect(() => {
    saveDebounced(content);
  }, [content]);
}

// ✅ Use throttle when:
// - Scroll/resize handlers
// - Animation frames
// - Limit update frequency
// - Predictable timing needed

function InfiniteScroll() {
  const handleScroll = useMemo(
    () =>
      throttle(() => {
        if (atBottom()) loadMore();
      }, 200),
    [],
  );

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, []);
}

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

Bug 1: Forgetting React.memo

jsx
/**
 * ❌ BUG: useDeferredValue không improve performance
 *
 * Symptom: UI vẫn lag giống như không dùng hook
 * Root cause: Quên React.memo cho child component
 */
function BuggyList() {
  const [filter, setFilter] = useState('');
  const deferredFilter = useDeferredValue(filter);

  const items = useMemo(() => generateItems(5000), []);

  const filtered = useMemo(
    () => items.filter((i) => i.name.includes(deferredFilter)),
    [items, deferredFilter],
  );

  return (
    <>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
      />
      {/* ❌ No React.memo! */}
      <HeavyList items={filtered} />
    </>
  );
}

function HeavyList({ items }) {
  // Slow render
  const startTime = performance.now();
  while (performance.now() - startTime < 100) {}

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

// ❓ CÂU HỎI:
// 1. Tại sao useDeferredValue không help?
// 2. Vai trò của React.memo là gì?
// 3. Fix như thế nào?

💡 Giải thích:

  1. Tại sao không help:

    User types → filter updates
    
    deferredFilter still old → filtered still old
    
    But Parent re-renders → HeavyList re-renders!
    
    HeavyList renders with OLD props → Still blocks UI!

    useDeferredValue defer VALUE, nhưng không prevent renders. Component vẫn re-render mỗi khi parent renders.

  2. Vai trò của React.memo:

    • Skip render khi props không đổi
    • Cho phép component "stay behind" với old props
    • Component chỉ render khi deferred value actually changes
  3. Fix:

    jsx
    // ✅ Add React.memo
    const HeavyList = React.memo(function HeavyList({ items }) {
      const startTime = performance.now();
      while (performance.now() - startTime < 100) {}
    
      return <div>{items.length} items</div>;
    });
    
    // Now:
    // - filter updates → Parent renders
    // - deferredFilter still old → filtered still old
    // - HeavyList props unchanged → SKIP RENDER ✨
    // - When deferredFilter catches up → HeavyList renders

Golden Rule:

useDeferredValue + React.memo = Performance Win
Neither alone is enough!

Bug 2: Deferred Value Not Updating

jsx
/**
 * ❌ BUG: Deferred value "stuck" at initial value
 *
 * Symptom: Results never update
 * Root cause: Incorrect dependency trong useMemo
 */
function BuggySearch() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);

  const items = [
    /* ... */
  ];

  // ❌ Missing deferredQuery from deps!
  const filtered = useMemo(() => {
    return items.filter((i) => i.name.includes(deferredQuery));
  }, [items]); // ⚠️ deferredQuery not in deps!

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <div>{filtered.length} results</div>
    </>
  );
}

// ❓ CÂU HỎI:
// 1. Tại sao filtered không update?
// 2. ESLint warning là gì?
// 3. Correct dependencies là gì?

💡 Giải thích:

  1. Tại sao không update:

    deferredQuery changes → No problem
    But useMemo deps = [items] only
    → Memo doesn't know to recompute!
    → Filtered stays at initial value
  2. ESLint warning:

    React Hook useMemo has a missing dependency: 'deferredQuery'.
    Either include it or remove the dependency array.

    ESLint-plugin-react-hooks catches này! LUÔN LUÔN fix ESLint warnings!

  3. Fix:

    jsx
    // ✅ Include ALL dependencies
    const filtered = useMemo(() => {
      return items.filter((i) => i.name.includes(deferredQuery));
    }, [items, deferredQuery]); // ✅ Both deps

Lesson:

  • Trust ESLint exhaustive-deps rule
  • Include ALL values used inside hook
  • useDeferredValue creates NEW value → needs to be in deps

Bug 3: Using Deferred Value for Network Requests

jsx
/**
 * ❌ BUG: Dùng useDeferredValue cho API calls
 *
 * Symptom: Multiple unnecessary requests, stale results
 * Root cause: Misunderstanding useDeferredValue purpose
 */
function BuggyDataFetch() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const [results, setResults] = useState([]);

  useEffect(() => {
    // ❌ Fetching with deferred value
    fetch(`/api/search?q=${deferredQuery}`)
      .then((res) => res.json())
      .then(setResults);
  }, [deferredQuery]);

  return (
    <>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <div>{results.length} results</div>
    </>
  );
}

// ❓ CÂU HỎI:
// 1. Tại sao pattern này sai?
// 2. Nên dùng gì thay vì useDeferredValue?
// 3. Khi nào useDeferredValue appropriate?

💡 Giải thích:

  1. Tại sao sai:

    useDeferredValue is for UI WORK, not I/O work!
    
    User types "abc"
    → query = "a"
    → deferredQuery = "" → fetch("")
    → query = "ab"
    → deferredQuery = "a" → fetch("a")
    → query = "abc"
    → deferredQuery = "ab" → fetch("ab")
    → deferredQuery = "abc" → fetch("abc")
    
    Result: 4 requests instead of 1!
    Defeats the purpose!
  2. Correct approach:

    jsx
    // ✅ Use debounce for API calls
    function CorrectDataFetch() {
      const [query, setQuery] = useState('');
      const [results, setResults] = useState([]);
      const [loading, setLoading] = useState(false);
    
      useEffect(() => {
        const timer = setTimeout(async () => {
          if (!query) return;
    
          setLoading(true);
          try {
            const res = await fetch(`/api/search?q=${query}`);
            const data = await res.json();
            setResults(data);
          } finally {
            setLoading(false);
          }
        }, 300); // Debounce 300ms
    
        return () => clearTimeout(timer);
      }, [query]);
    
      return (
        <>
          <input
            value={query}
            onChange={(e) => setQuery(e.target.value)}
          />
          {loading && <Spinner />}
          <div>{results.length} results</div>
        </>
      );
    }
  3. When useDeferredValue appropriate:

    jsx
    // ✅ For heavy UI rendering
    function CorrectUsage() {
      const [query, setQuery] = useState('');
      const deferredQuery = useDeferredValue(query);
    
      // Heavy LOCAL filtering (not network)
      const filtered = useMemo(() => {
        return largeLocalDataset.filter((item) =>
          item.name.includes(deferredQuery),
        );
      }, [deferredQuery]);
    
      return <List items={filtered} />;
    }

Remember:

useDeferredValue: For heavy RENDERING
debounce: For network REQUESTS
Different tools, different jobs!

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

Knowledge Check

  • [ ] Tôi hiểu useDeferredValue defer VALUE, không phải UPDATE
  • [ ] Tôi biết khi nào dùng useDeferredValue vs useTransition
  • [ ] Tôi hiểu tại sao React.memo essential với useDeferredValue
  • [ ] Tôi biết detect stale value bằng cách compare values
  • [ ] Tôi không dùng useDeferredValue cho network requests
  • [ ] Tôi có thể so sánh declarative vs imperative approaches
  • [ ] Tôi hiểu trade-offs giữa different deferral strategies

Code Review Checklist

useDeferredValue Usage:

  • [ ] Only deferring VALUES, not actions
  • [ ] Child components have React.memo
  • [ ] Deferred values in useMemo dependencies
  • [ ] Stale detection implemented properly
  • [ ] Not used for async I/O

Performance:

  • [ ] React.memo prevents unnecessary renders
  • [ ] useMemo caches expensive computations
  • [ ] No excessive re-renders
  • [ ] Smooth UI experience

UX:

  • [ ] Clear loading indicators when stale
  • [ ] Input always responsive
  • [ ] No jarring transitions
  • [ ] Visual feedback appropriate

🏠 BÀI TẬP VỀ NHÀ

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

1. Refactoring Exercise:

  • Take một useTransition example từ Ngày 47
  • Refactor sang useDeferredValue
  • Compare code complexity
  • Document when each approach better

2. Performance Measurement:

  • Build simple search interface
  • Implement 3 versions:
    • Without optimization
    • With useDeferredValue (no memo)
    • With useDeferredValue + React.memo
  • Measure render counts
  • Document findings

Nâng cao (60 phút)

1. Custom Hook Library:

  • Create useDeferredState hook
  • Create useDeferredMemo hook
  • Add TypeScript types
  • Write usage examples
  • Test với real scenarios

2. Comparison Chart:

  • Build interactive demo comparing:
    • useTransition
    • useDeferredValue
    • debounce
    • throttle
  • Side-by-side comparison
  • Performance metrics
  • Use case recommendations

📚 TÀI LIỆU THAM KHẢO

Bắt buộc đọc

  1. useDeferredValue - React Docs

  2. Keeping Components Pure

Đọc thêm

  1. useTransition vs useDeferredValue

  2. React.memo Deep Dive


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

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

  • Ngày 47: useTransition patterns
  • Ngày 46: Concurrent rendering concepts
  • Ngày 32-34: React.memo, useMemo, useCallback
  • Ngày 11-14: useState fundamentals

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

  • Ngày 49: Suspense - Declarative loading
  • Ngày 50: Error Boundaries
  • Ngày 51: React Server Components
  • Module Next.js: Server/Client component patterns

💡 SENIOR INSIGHTS

Cân Nhắc Production

1. Choosing Between Hooks:

jsx
// Decision Matrix:
//
// Have control over the UPDATE?
// → YES: Use useTransition
// → NO: Use useDeferredValue
//
// Example: Own component, can change setState call
// → useTransition
//
// Example: Receiving props from parent
// → useDeferredValue

2. Performance Monitoring:

jsx
// Track deferred value lag
function useDefferredWithMetrics(value) {
  const deferred = useDeferredValue(value);
  const lagTime = useRef(0);

  useEffect(() => {
    if (value !== deferred) {
      lagTime.current = performance.now();
    } else if (lagTime.current) {
      const lag = performance.now() - lagTime.current;
      console.log(`Deferred value lag: ${lag}ms`);
      lagTime.current = 0;
    }
  }, [value, deferred]);

  return deferred;
}

3. Common Pitfalls:

jsx
// ❌ PITFALL 1: No memo
<HeavyComponent data={deferredData} />;
// Child re-renders anyway!

// ✅ SOLUTION:
const HeavyComponentMemo = React.memo(HeavyComponent);
<HeavyComponentMemo data={deferredData} />;

// ❌ PITFALL 2: Wrong deps
const result = useMemo(() => compute(deferred), [original]);
// Should be [deferred]!

// ❌ PITFALL 3: Using for I/O
fetch(`/api?q=${deferredQuery}`);
// Use debounce instead!

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

Junior Level:

  • Q: useDeferredValue làm gì?
  • A: Trả về version deferred của value. React có thể defer updating deferred value để prioritize urgent updates, giúp UI responsive.

Mid Level:

  • Q: Khi nào dùng useDeferredValue vs useTransition?
  • A: useDeferredValue khi defer VALUES (props, derived state), declarative style. useTransition khi defer ACTIONS (event handlers), imperative control. useDeferredValue simpler cho most cases, useTransition khi cần explicit control.

Senior Level:

  • Q: Giải thích tại sao useDeferredValue cần React.memo để effective

  • A: useDeferredValue only defers the VALUE, không prevent renders. Without memo:

    • Parent renders → Child renders with old props
    • Deferred value updates → Child renders again
    • Result: 2 renders, defeats purpose

    With memo:

    • Parent renders → Memo checks props → Skip render if same
    • Deferred value updates → Props change → Render once
    • Result: 1 render when needed ✨

Architect Level:

  • Q: Design strategy cho large dashboard với multiple deferred values

  • A: Multi-layered approach:

    1. Identify critical vs non-critical updates
    2. Use useDeferredValue cho non-critical values
    3. Memo all expensive children
    4. Group related deferred values
    5. Monitor performance metrics
    6. Provide user controls (quality settings)
    7. Fallback to simpler views on low-end devices

    Architecture:

    jsx
    <DashboardProvider>
      {' '}
      // Track all deferred values
      <Controls /> // Always responsive
      <CriticalMetrics /> // No deferral
      <DeferredChartMemo data={deferredData} /> // Deferred
      <DeferredTableMemo data={deferredData} /> // Deferred
    </DashboardProvider>

War Stories

Story 1: "The Forgotten Memo"

Scenario: Added useDeferredValue to search, no improvement

Investigation: Profiler showed child still re-rendering

Root cause: Forgot React.memo on child component

Fix: Added memo → 10x performance improvement!

Lesson: useDeferredValue + React.memo = Essential pair

Story 2: "The API Disaster"

Scenario: Used useDeferredValue for search API calls

Result:
- User types "react"
- API called 5 times: "", "r", "re", "rea", "react"
- Server overloaded!

Fix: Switched to debounce (300ms)

Result: Only 1 API call per search

Lesson: Right tool for right job
- useDeferredValue: UI rendering
- debounce: Network requests

Story 3: "The Declarative Win"

Before (useTransition):
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();

const handleChange = (e) => {
  setQuery(e.target.value);
  startTransition(() => {
    setResults(filter(e.target.value));
  });
};

After (useDeferredValue):
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const results = useMemo(() => filter(deferredQuery), [deferredQuery]);

Result:
- 50% less code
- Cleaner, more maintainable
- Same performance

Lesson: Simple solution often best

🎯 PREVIEW NGÀY MAI

Ngày 49: Suspense for Data Fetching

Tomorrow sẽ học:

  • Suspense component - declarative loading
  • Integration với data fetching
  • Error boundaries với Suspense
  • Streaming SSR patterns

Prepare:

  • Review async patterns (useEffect + fetch)
  • Think về loading states trong apps
  • Concurrent rendering concepts từ Ngày 46-48

Hint: Suspense is the missing piece - declarative loading thay vì manual loading states. Combined với useTransition/useDeferredValue = Powerful! 🚀

See you tomorrow!

Personal tech knowledge base