React Performance Lab

Interactive demonstrations of React performance patterns with side-by-side comparisons

Concept 1: Unnecessary Re-renders

Learn how improper state management can cause child components to re-render unnecessarily. Compare a bad implementation that lifts state too high with a good implementation using React.memo, state colocation, and useCallback.

Code Comparison

✗ Bad Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ❌ BAD: Parent state causes all children to re-renderspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Parent = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [count, setCount] = useState(<span class="text-cyan-400">0span>);

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <button onClick={() => setCount(count + <span class="text-cyan-400">1span>)}>
        Count: {count}
      button>
      {/* This re-renders on every count change */}
      <HeavyChild data={count} />
    div>
  );
};

<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> HeavyChild = ({ data }) => {
  console.log(<span class="text-green-<span class="text-cyan-400">400span>">'HeavyChild rendered'span>);
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> <div>Data: {data}div>;
};
✓ Good Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ✅ GOOD: Use React.<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>memospan> + state colocationspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Parent = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <Counter />
      <SeparateChild />
    div>
  );
};

<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Counter = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [count, setCount] = useState(<span class="text-cyan-400">0span>);
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <button onClick={() => setCount(count + <span class="text-cyan-400">1span>)}>
      Count: {count}
    button>
  );
};

<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> SeparateChild = React.<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>memospan>(() => {
  console.log(<span class="text-green-<span class="text-cyan-400">400span>">'SeparateChild rendered once'span>);
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> <div>This rarely re-rendersdiv>;
});

Concept 2: Expensive Computation

Understand why running expensive computations on every render hurts performance. See how useMemo with proper dependency arrays caches results and only recomputes when dependencies change.

Code Comparison

✗ Bad Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ❌ BAD: Expensive calculation runs every renderspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Calculator = ({ numbers }) => {
  <span class="text-gray-<span class="text-cyan-400">500span>">// This runs EVERY render, even <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>ifspan> numbers didn't change!span>
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> sum = numbers.reduce((a, b) => {
    <span class="text-gray-<span class="text-cyan-400">500span>">// Simulate expensive operationspan>
    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>forspan> (<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>letspan> i = <span class="text-cyan-400">0span>; i < <span class="text-cyan-400">1000000span>; i++) {}
    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> a + b;
  }, <span class="text-cyan-400">0span>);

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <input placeholder=<span class="text-green-<span class="text-cyan-400">400span>">"type something"span> />
      <p>Sum: {sum}p>
    div>
  );
};
✓ Good Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ✅ GOOD: Use <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>useMemospan> to cache computationspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Calculator = ({ numbers }) => {
  <span class="text-gray-<span class="text-cyan-400">500span>">// Only recalculates when <span class="text-green-<span class="text-cyan-400">400span>">'numbers'span> changesspan>
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> sum = <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>useMemospan>(() => {
    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> numbers.reduce((a, b) => {
      <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>forspan> (<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>letspan> i = <span class="text-cyan-400">0span>; i < <span class="text-cyan-400">1000000span>; i++) {}
      <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> a + b;
    }, <span class="text-cyan-400">0span>);
  }, [numbers]);

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <input placeholder=<span class="text-green-<span class="text-cyan-400">400span>">"type something"span> />
      <p>Sum: {sum}p>
    div>
  );
};

Concept 3: List Rendering & Filtering

When rendering large lists with filtering, improper memoization causes lag. Discover how useMemo prevents re-filtering on every parent render change, keeping your UI responsive.

Code Comparison

✗ Bad Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ❌ BAD: No memoization <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>forspan> large filtered listsspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> ListComponent = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [items] = useState(
    Array.<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>fromspan>({ length: <span class="text-cyan-400">1000span> }, (_, i) => 
      ({ id: i, name: <span class="text-green-<span class="text-cyan-400">400span>">`Item ${i}`span> })
    )
  );
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [filter, setFilter] = useState(<span class="text-green-<span class="text-cyan-400">400span>">''span>);

  <span class="text-gray-<span class="text-cyan-400">500span>">// Creates new array every render!span>
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> filteredItems = items.filter(item =>
    item.name.toLowerCase().includes(filter)
  );

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <input onChange={(e) => setFilter(e.target.value)} />
      <ul>{filteredItems.map(item => 
        <li key={item.id}>{item.name}li>
      )}ul>
    div>
  );
};
✓ Good Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ✅ GOOD: Memoize filtered list with <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>useMemospan>span>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> ListComponent = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [items] = useState(
    Array.<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>fromspan>({ length: <span class="text-cyan-400">1000span> }, (_, i) => 
      ({ id: i, name: <span class="text-green-<span class="text-cyan-400">400span>">`Item ${i}`span> })
    )
  );
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [filter, setFilter] = useState(<span class="text-green-<span class="text-cyan-400">400span>">''span>);

  <span class="text-gray-<span class="text-cyan-400">500span>">// Only recalculates when filter changesspan>
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> filteredItems = <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>useMemospan>(
    () => items.filter(item =>
      item.name.toLowerCase().includes(filter)
    ),
    [filter, items]
  );

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <input onChange={(e) => setFilter(e.target.value)} />
      <ul>{filteredItems.map(item => 
        <li key={item.id}>{item.name}li>
      )}ul>
    div>
  );
};

Concept 4: Component Lifecycle & Memory Leaks

Missing dependency arrays in useEffect create memory leaks and duplicate event listeners. Learn how proper dependencies prevent re-running effects and mounting/unmounting on every render.

Code Comparison

✗ Bad Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ❌ BAD: Missing dependency array in useEffectspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Modal = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [counter, setCounter] = useState(<span class="text-cyan-400">0span>);

  <span class="text-gray-<span class="text-cyan-400">500span>">// Runs on EVERY render - memory leak!span>
  useEffect(() => {
    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> interval = setInterval(() => {
      console.log(<span class="text-green-<span class="text-cyan-400">400span>">'interval'span>);
    }, <span class="text-cyan-400">100span>);

    window.addEventListener(<span class="text-green-<span class="text-cyan-400">400span>">'resize'span>, () => {
      console.log(<span class="text-green-<span class="text-cyan-400">400span>">'resized'span>);
    });

    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> () => clearInterval(interval);
    <span class="text-gray-<span class="text-cyan-400">500span>">// Missing dependency array!span>
  });

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <button onClick={() => setCounter(c => c + <span class="text-cyan-400">1span>)}>
        Count: {counter}
      button>
    div>
  );
};
✓ Good Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ✅ GOOD: Proper useEffect dependency arrayspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Modal = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [counter, setCounter] = useState(<span class="text-cyan-400">0span>);

  <span class="text-gray-<span class="text-cyan-400">500span>">// Runs ONLY on mount/unmountspan>
  useEffect(() => {
    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> interval = setInterval(() => {
      console.log(<span class="text-green-<span class="text-cyan-400">400span>">'interval'span>);
    }, <span class="text-cyan-400">100span>);

    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> handleResize = () => console.log(<span class="text-green-<span class="text-cyan-400">400span>">'resized'span>);
    window.addEventListener(<span class="text-green-<span class="text-cyan-400">400span>">'resize'span>, handleResize);

    <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> () => {
      clearInterval(interval);
      window.removeEventListener(<span class="text-green-<span class="text-cyan-400">400span>">'resize'span>, handleResize);
    };
  }, []); <span class="text-gray-<span class="text-cyan-400">500span>">// Empty dependency array!span>

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <button onClick={() => setCounter(c => c + <span class="text-cyan-400">1span>)}>
        Count: {counter}
      button>
    div>
  );
};

Concept 5: Progressive Loading & Batching

Loading all resources simultaneously blocks the UI. See how progressive loading with staggered timing keeps your application responsive while managing concurrent operations efficiently.

Code Comparison

✗ Bad Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ❌ BAD: Load all images at oncespan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Gallery = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [visible, setVisible] = useState(<span class="text-cyan-400">0span>);
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> images = Array.<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>fromspan>({ length: <span class="text-cyan-400">12span> }, (_, i) => 
    ({ id: i, url: <span class="text-green-<span class="text-cyan-400">400span>">`/img-${i}.jpg`span> })
  );

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <button onClick={() => setVisible(images.length)}>
        Load All
      button>
      <div className=<span class="text-green-<span class="text-cyan-400">400span>">"grid"span>>
        {images.map((img, idx) => 
          idx < visible && 
          <img key={img.id} src={img.url} />
        )}
      div>
    div>
  );
};
✓ Good Implementation
<span class="text-gray-<span class="text-cyan-400">500span>">// ✅ GOOD: Progressive lazy loadingspan>
<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> Gallery = () => {
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> [loaded, setLoaded] = useState(new Set());
  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> images = Array.<span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>fromspan>({ length: <span class="text-cyan-400">12span> }, (_, i) => 
    ({ id: i, url: <span class="text-green-<span class="text-cyan-400">400span>">`/img-${i}.jpg`span> })
  );

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>constspan> startLoading = () => {
    images.forEach((img, idx) => {
      setTimeout(() => {
        setLoaded(p => new Set(p).add(img.id));
      }, idx * <span class="text-cyan-400">100span>); <span class="text-gray-<span class="text-cyan-400">500span>">// Stagger loadingspan>
    });
  };

  <span class=<span class="text-green-<span class="text-cyan-400">400span>">"text-blue-<span class="text-cyan-400">400span>"span>>returnspan> (
    <div>
      <button onClick={startLoading}>Load Progressivelybutton>
      <div className=<span class="text-green-<span class="text-cyan-400">400span>">"grid"span>>
        {images.map(img => 
          loaded.has(img.id) && 
          <img key={img.id} src={img.url} />
        )}
      div>
    div>
  );
};