Dev Logs
/React/ useRef: Refs vs Variables Deep Dive
Chapters
  • 01Props & Children: Component Communication Mastery
  • 02useState & Functional State: Making Components Interactive
  • 03Rendering Logic, Lists, and Keys: Dynamic Content Mastery
  • 04Handling DOM Events: User Interactions Mastery
  • 05Controlled vs Uncontrolled Inputs: Form Mastery
  • 06useEffect: Dependencies & Cleanup Mastery
  • 07useRef: Refs vs Variables Deep Dive
    • What You'll Learn
    • Understanding useRef Fundamentals
    • Refs vs State vs Variables
    • DOM Refs and Element Access
    • Mutable Values and Performance
    • Using Refs for Mutable Values
    • Performance Optimization with Refs
    • Ref Forwarding and Imperative APIs
    • Ref Forwarding Patterns
    • Custom Hooks with Refs
    • Common Mistakes & Anti-Patterns
    • . Using Refs Instead of State
    • . Accessing Refs During Render
    • . Not Handling Null Refs
    • . Ref Callback Mistakes
    • Mini Challenges
    • Challenge 1: Focus Management System
    • Challenge 2: Virtual Scrolling with Refs
    • When and Why: useRef Decision Framework
    • Quick Decision Tree
    • Interview Insights
    • Common Interview Questions
    • Code Review Red Flags
    • Key Takeaways
    • Mental Model
    • Best Practices Summary
    • What You'll Learn
    • Understanding useMemo Fundamentals
    • What is useMemo?
    • When React Re-calculates
    • Expensive Calculations
    • Identifying Expensive Operations
    • Reference Stability
    • Object and Array Reference Stability
    • Child Component Re-render Prevention
    • Dependency Array Best Practices
    • Dependency Array Patterns
    • Common Mistakes & Anti-Patterns
    • . Premature Optimization
    • . Missing Dependencies
    • . Object/Array Dependencies
    • . Expensive Dependency Calculations
    • Mini Challenges
    • Challenge 1: Data Dashboard Optimization
    • Challenge 2: Smart Shopping Cart
    • Performance Measurement
    • Measuring useMemo Impact
    • When and Why: useMemo Decision Framework
    • Quick Decision Tree
    • Performance Guidelines
    • Interview Insights
    • Common Interview Questions
    • Code Review Red Flags
    • Key Takeaways
    • Mental Model
    • Best Practices Summary
    • Production Tips
  • 08useCallback: Function Memoization
  • 09useContext: Global State Management
  • 10useReducer: Complex State Management
  • 11. Custom Hooks: Reusable Logic 🎣
  • 12. React.memo & Optimization 🚀
  • 13. Advanced Patterns 🎨
  • 14. Error Boundaries & Error Handling 🛡️
  • 15. Testing React Applications 🧪
  • 16. Performance Optimization ⚡
  • 17. Deployment & Production 🚀
All chapters

🎯 useRef: Refs vs Variables Deep Dive

Master React's escape hatch: Direct DOM access, mutable values, and when refs are better than state

🎯 What You'll Learn

  • Understanding refs vs regular variables vs state
  • Direct DOM manipulation and focus management
  • Storing mutable values that don't trigger re-renders
  • Ref forwarding and imperative APIs
  • Common ref patterns and anti-patterns
  • Performance optimizations with refs
  • Real-world scenarios and best practices

🎯 Understanding useRef Fundamentals

Refs vs State vs Variables

jsx
function RefVsStateVsVariables() {
  // 🎯 State: Triggers re-render when changed
  const [stateValue, setStateValue] = useState(0);

  // 🎯 Ref: Persists between renders, doesn't trigger re-render
  const refValue = useRef(0);

  // ❌ Regular variable: Gets reset on every render
  let regularVariable = 0;

  // 🎯 Ref for tracking render count
  const renderCount = useRef(0);

  // Increment render count on every render
  renderCount.current += 1;

  const handleStateIncrement = () => {
    setStateValue((prev) => prev + 1); // ✅ Triggers re-render
  };

  const handleRefIncrement = () => {
    refValue.current += 1; // ✅ Persists but no re-render
    console.log("Ref value:", refValue.current);
  };

  const handleVariableIncrement = () => {
    regularVariable += 1; // ❌ Will be reset on next render
    console.log("Variable value:", regularVariable);
  };

  const forceRerender = () => {
    setStateValue((prev) => prev); // Force re-render without changing state
  };

  return (
    <div className="ref-comparison">
      <h3>Refs vs State vs Variables</h3>

      <div className="render-info">
        <p>
          <strong>Render count:</strong> {renderCount.current}
        </p>
      </div>

      <div className="value-display">
        <div className="state-section">
          <h4>State Value (triggers re-render)</h4>
          <p>Current value: {stateValue}</p>
          <button onClick={handleStateIncrement}>Increment State</button>
        </div>

        <div className="ref-section">
          <h4>Ref Value (persists, no re-render)</h4>
          <p>Current value: {refValue.current}</p>
          <button onClick={handleRefIncrement}>Increment Ref</button>
          <p>
            <em>Check console for actual value</em>
          </p>
        </div>

        <div className="variable-section">
          <h4>Regular Variable (resets each render)</h4>
          <p>Current value: {regularVariable}</p>
          <button onClick={handleVariableIncrement}>Increment Variable</button>
          <p>
            <em>Always resets to 0</em>
          </p>
        </div>
      </div>

      <div className="controls">
        <button onClick={forceRerender}>Force Re-render</button>
        <button
          onClick={() => {
            console.log("Current values:");
            console.log("State:", stateValue);
            console.log("Ref:", refValue.current);
            console.log("Variable:", regularVariable);
          }}
        >
          Log All Values
        </button>
      </div>

      <div className="explanation">
        <h4>Key Differences:</h4>
        <ul>
          <li>
            <strong>State:</strong> Triggers re-render, UI updates automatically
          </li>
          <li>
            <strong>Ref:</strong> Persists between renders, no re-render, manual
            UI updates
          </li>
          <li>
            <strong>Variable:</strong> Resets on every render, loses value
          </li>
        </ul>
      </div>
    </div>
  );
}

DOM Refs and Element Access

jsx
function DOMRefExamples() {
  // 🎯 Refs for DOM elements
  const inputRef = useRef(null);
  const textareaRef = useRef(null);
  const divRef = useRef(null);
  const videoRef = useRef(null);

  // 🎯 Refs for storing DOM measurements
  const measurementsRef = useRef({ width: 0, height: 0 });

  const [measurements, setMeasurements] = useState({ width: 0, height: 0 });
  const [inputValue, setInputValue] = useState("");
  const [isPlaying, setIsPlaying] = useState(false);

  // 🎯 Focus management
  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select(); // Select all text
    }
  };

  const clearAndFocus = () => {
    setInputValue("");
    // Focus after state update
    setTimeout(() => {
      if (inputRef.current) {
        inputRef.current.focus();
      }
    }, 0);
  };

  // 🎯 Scroll management
  const scrollToTop = () => {
    if (divRef.current) {
      divRef.current.scrollTop = 0;
    }
  };

  const scrollToBottom = () => {
    if (divRef.current) {
      divRef.current.scrollTop = divRef.current.scrollHeight;
    }
  };

  // 🎯 Measurements
  const measureElement = () => {
    if (divRef.current) {
      const rect = divRef.current.getBoundingClientRect();
      const newMeasurements = {
        width: rect.width,
        height: rect.height,
        top: rect.top,
        left: rect.left,
      };

      // Store in ref (doesn't trigger re-render)
      measurementsRef.current = newMeasurements;

      // Update state to show in UI
      setMeasurements(newMeasurements);
    }
  };

  // 🎯 Video control
  const toggleVideo = () => {
    if (videoRef.current) {
      if (isPlaying) {
        videoRef.current.pause();
      } else {
        videoRef.current.play();
      }
      setIsPlaying(!isPlaying);
    }
  };

  const setVideoTime = (seconds) => {
    if (videoRef.current) {
      videoRef.current.currentTime = seconds;
    }
  };

  // 🎯 Text manipulation
  const insertTextAtCursor = (text) => {
    if (textareaRef.current) {
      const textarea = textareaRef.current;
      const start = textarea.selectionStart;
      const end = textarea.selectionEnd;
      const currentValue = textarea.value;

      const newValue =
        currentValue.substring(0, start) + text + currentValue.substring(end);
      textarea.value = newValue;

      // Set cursor position after inserted text
      const newCursorPos = start + text.length;
      textarea.setSelectionRange(newCursorPos, newCursorPos);
      textarea.focus();
    }
  };

  return (
    <div className="dom-ref-examples">
      <h3>DOM Refs and Element Access</h3>

      {/* Input Focus Management */}
      <div className="input-section">
        <h4>Input Focus Management</h4>
        <input
          ref={inputRef}
          type="text"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="Type something..."
        />
        <div className="input-controls">
          <button onClick={focusInput}>Focus & Select</button>
          <button onClick={clearAndFocus}>Clear & Focus</button>
          <button
            onClick={() => {
              if (inputRef.current) {
                inputRef.current.blur();
              }
            }}
          >
            Blur
          </button>
        </div>
      </div>

      {/* Textarea with Text Insertion */}
      <div className="textarea-section">
        <h4>Textarea Manipulation</h4>
        <textarea
          ref={textareaRef}
          rows={4}
          cols={50}
          placeholder="Click buttons to insert text at cursor..."
        />
        <div className="textarea-controls">
          <button onClick={() => insertTextAtCursor("Hello ")}>
            Insert "Hello "
          </button>
          <button onClick={() => insertTextAtCursor("World!")}>
            Insert "World!"
          </button>
          <button onClick={() => insertTextAtCursor("\n")}>
            Insert New Line
          </button>
          <button
            onClick={() => {
              if (textareaRef.current) {
                textareaRef.current.value = "";
                textareaRef.current.focus();
              }
            }}
          >
            Clear
          </button>
        </div>
      </div>

      {/* Scrollable Div */}
      <div className="scroll-section">
        <h4>Scroll Management</h4>
        <div
          ref={divRef}
          className="scrollable-div"
          style={{
            height: "150px",
            overflow: "auto",
            border: "1px solid #ccc",
            padding: "10px",
          }}
        >
          {Array.from({ length: 50 }, (_, i) => (
            <p key={i}>Line {i + 1} - This is some scrollable content</p>
          ))}
        </div>
        <div className="scroll-controls">
          <button onClick={scrollToTop}>Scroll to Top</button>
          <button onClick={scrollToBottom}>Scroll to Bottom</button>
          <button onClick={measureElement}>Measure Element</button>
        </div>
      </div>

      {/* Measurements Display */}
      <div className="measurements-section">
        <h4>Element Measurements</h4>
        <pre>{JSON.stringify(measurements, null, 2)}</pre>
      </div>

      {/* Video Control */}
      <div className="video-section">
        <h4>Video Control</h4>
        <video
          ref={videoRef}
          width="300"
          height="200"
          controls={false}
          style={{ border: "1px solid #ccc" }}
        >
          <source src="/sample-video.mp4" type="video/mp4" />
          Your browser does not support the video tag.
        </video>
        <div className="video-controls">
          <button onClick={toggleVideo}>{isPlaying ? "Pause" : "Play"}</button>
          <button onClick={() => setVideoTime(0)}>Reset</button>
          <button onClick={() => setVideoTime(10)}>Skip to 10s</button>
        </div>
      </div>
    </div>
  );
}

🔄 Mutable Values and Performance

Using Refs for Mutable Values

jsx
function MutableValueExamples() {
  const [count, setCount] = useState(0);
  const [messages, setMessages] = useState([]);

  // 🎯 Refs for values that don't need to trigger re-renders
  const timerIdRef = useRef(null);
  const previousCountRef = useRef(0);
  const callbackCountRef = useRef(0);
  const isComponentMountedRef = useRef(true);

  // 🎯 Ref for storing expensive calculations
  const expensiveValueRef = useRef(null);
  const lastCalculationInputRef = useRef(null);

  // 🎯 Track previous value
  useEffect(() => {
    previousCountRef.current = count;
  });

  // 🎯 Cleanup on unmount
  useEffect(() => {
    return () => {
      isComponentMountedRef.current = false;
      if (timerIdRef.current) {
        clearInterval(timerIdRef.current);
      }
    };
  }, []);

  const startTimer = () => {
    if (timerIdRef.current) {
      clearInterval(timerIdRef.current);
    }

    timerIdRef.current = setInterval(() => {
      // ✅ Check if component is still mounted
      if (isComponentMountedRef.current) {
        setCount((prevCount) => prevCount + 1);
      }
    }, 1000);
  };

  const stopTimer = () => {
    if (timerIdRef.current) {
      clearInterval(timerIdRef.current);
      timerIdRef.current = null;
    }
  };

  // 🎯 Expensive calculation with caching
  const getExpensiveValue = (input) => {
    // Only recalculate if input changed
    if (lastCalculationInputRef.current !== input) {
      console.log("Performing expensive calculation...");

      // Simulate expensive operation
      let result = 0;
      for (let i = 0; i < input * 1000000; i++) {
        result += Math.random();
      }

      expensiveValueRef.current = result;
      lastCalculationInputRef.current = input;
    }

    return expensiveValueRef.current;
  };

  // 🎯 Callback with ref to avoid stale closures
  const handleAsyncOperation = useCallback(() => {
    callbackCountRef.current += 1;
    const currentCallCount = callbackCountRef.current;

    console.log(`Starting async operation #${currentCallCount}`);

    setTimeout(() => {
      // ✅ Check if this is still the latest call
      if (
        callbackCountRef.current === currentCallCount &&
        isComponentMountedRef.current
      ) {
        setMessages((prev) => [
          ...prev,
          {
            id: Date.now(),
            text: `Async operation #${currentCallCount} completed`,
            timestamp: new Date().toLocaleTimeString(),
          },
        ]);
      } else {
        console.log(`Async operation #${currentCallCount} was superseded`);
      }
    }, 2000);
  }, []);

  const clearMessages = () => {
    setMessages([]);
  };

  const expensiveValue = getExpensiveValue(count);

  return (
    <div className="mutable-value-examples">
      <h3>Mutable Values with Refs</h3>

      <div className="counter-section">
        <h4>Timer with Ref Management</h4>
        <p>Current count: {count}</p>
        <p>Previous count: {previousCountRef.current}</p>
        <p>Change: {count - previousCountRef.current}</p>

        <div className="timer-controls">
          <button onClick={startTimer}>Start Timer</button>
          <button onClick={stopTimer}>Stop Timer</button>
          <button onClick={() => setCount(0)}>Reset Count</button>
        </div>
      </div>

      <div className="expensive-calculation">
        <h4>Cached Expensive Calculation</h4>
        <p>Input: {count}</p>
        <p>
          Expensive result: {expensiveValue?.toFixed(2) || "Not calculated"}
        </p>
        <p>
          <em>Check console - calculation only runs when count changes</em>
        </p>
      </div>

      <div className="async-section">
        <h4>Async Operations with Race Condition Prevention</h4>
        <button onClick={handleAsyncOperation}>
          Start Async Operation (2s delay)
        </button>
        <p>Callback count: {callbackCountRef.current}</p>

        <div className="messages">
          <h5>Messages:</h5>
          {messages.length === 0 ? (
            <p>No messages yet</p>
          ) : (
            <ul>
              {messages.map((message) => (
                <li key={message.id}>
                  <strong>{message.timestamp}:</strong> {message.text}
                </li>
              ))}
            </ul>
          )}
          {messages.length > 0 && (
            <button onClick={clearMessages}>Clear Messages</button>
          )}
        </div>
      </div>

      <div className="ref-info">
        <h4>Ref Usage Summary:</h4>
        <ul>
          <li>
            <code>timerIdRef</code>: Stores timer ID for cleanup
          </li>
          <li>
            <code>previousCountRef</code>: Tracks previous state value
          </li>
          <li>
            <code>callbackCountRef</code>: Prevents race conditions
          </li>
          <li>
            <code>isComponentMountedRef</code>: Prevents state updates after
            unmount
          </li>
          <li>
            <code>expensiveValueRef</code>: Caches expensive calculations
          </li>
        </ul>
      </div>
    </div>
  );
}

Performance Optimization with Refs

jsx
function PerformanceOptimizationWithRefs() {
  const [items, setItems] = useState(
    Array.from({ length: 1000 }, (_, i) => ({
      id: i,
      name: `Item ${i}`,
      selected: false,
    }))
  );
  const [filter, setFilter] = useState("");

  // 🎯 Refs for performance optimization
  const selectedItemsRef = useRef(new Set());
  const lastFilterRef = useRef("");
  const filteredItemsRef = useRef([]);
  const renderCountRef = useRef(0);

  renderCountRef.current += 1;

  // 🎯 Memoized filtered items with ref caching
  const filteredItems = useMemo(() => {
    // Only recalculate if filter actually changed
    if (
      lastFilterRef.current === filter &&
      filteredItemsRef.current.length > 0
    ) {
      return filteredItemsRef.current;
    }

    console.log("Filtering items...");
    const result = items.filter((item) =>
      item.name.toLowerCase().includes(filter.toLowerCase())
    );

    lastFilterRef.current = filter;
    filteredItemsRef.current = result;

    return result;
  }, [items, filter]);

  // 🎯 Optimized selection handling
  const toggleItemSelection = useCallback((itemId) => {
    const selectedItems = selectedItemsRef.current;

    if (selectedItems.has(itemId)) {
      selectedItems.delete(itemId);
    } else {
      selectedItems.add(itemId);
    }

    // Update items state
    setItems((prevItems) =>
      prevItems.map((item) =>
        item.id === itemId
          ? { ...item, selected: selectedItems.has(itemId) }
          : item
      )
    );
  }, []);

  const selectAll = () => {
    const selectedItems = selectedItemsRef.current;
    selectedItems.clear();

    filteredItems.forEach((item) => {
      selectedItems.add(item.id);
    });

    setItems((prevItems) =>
      prevItems.map((item) => ({
        ...item,
        selected: selectedItems.has(item.id),
      }))
    );
  };

  const clearSelection = () => {
    selectedItemsRef.current.clear();

    setItems((prevItems) =>
      prevItems.map((item) => ({ ...item, selected: false }))
    );
  };

  const getSelectionCount = () => {
    return selectedItemsRef.current.size;
  };

  return (
    <div className="performance-optimization">
      <h3>Performance Optimization with Refs</h3>

      <div className="stats">
        <p>Render count: {renderCountRef.current}</p>
        <p>Total items: {items.length}</p>
        <p>Filtered items: {filteredItems.length}</p>
        <p>Selected items: {getSelectionCount()}</p>
      </div>

      <div className="controls">
        <input
          type="text"
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
          placeholder="Filter items..."
        />

        <div className="selection-controls">
          <button onClick={selectAll}>Select All Filtered</button>
          <button onClick={clearSelection}>Clear Selection</button>
        </div>
      </div>

      <div className="items-list" style={{ height: "300px", overflow: "auto" }}>
        {filteredItems.map((item) => (
          <div
            key={item.id}
            className={`item ${item.selected ? "selected" : ""}`}
            onClick={() => toggleItemSelection(item.id)}
            style={{
              padding: "5px",
              cursor: "pointer",
              backgroundColor: item.selected ? "#e3f2fd" : "transparent",
            }}
          >
            <input
              type="checkbox"
              checked={item.selected}
              onChange={() => {}} // Handled by parent click
            />
            {item.name}
          </div>
        ))}
      </div>

      <div className="optimization-notes">
        <h4>Optimization Techniques Used:</h4>
        <ul>
          <li>
            <strong>selectedItemsRef:</strong> Tracks selection without
            triggering re-renders
          </li>
          <li>
            <strong>filteredItemsRef:</strong> Caches filtered results
          </li>
          <li>
            <strong>lastFilterRef:</strong> Prevents unnecessary filtering
          </li>
          <li>
            <strong>useMemo:</strong> Memoizes expensive filtering operation
          </li>
          <li>
            <strong>useCallback:</strong> Prevents function recreation
          </li>
        </ul>
      </div>
    </div>
  );
}

🔄 Ref Forwarding and Imperative APIs

Ref Forwarding Patterns

jsx
// 🎯 Basic ref forwarding
const CustomInput = forwardRef((props, ref) => {
  const { label, error, ...inputProps } = props;

  return (
    <div className="custom-input">
      {label && <label>{label}</label>}
      <input ref={ref} {...inputProps} />
      {error && <span className="error">{error}</span>}
    </div>
  );
});

// 🎯 Advanced ref forwarding with imperative API
const AdvancedInput = forwardRef((props, ref) => {
  const { onValueChange, ...otherProps } = props;
  const inputRef = useRef(null);
  const [value, setValue] = useState("");

  // 🎯 Expose imperative API
  useImperativeHandle(
    ref,
    () => ({
      focus: () => {
        inputRef.current?.focus();
      },
      blur: () => {
        inputRef.current?.blur();
      },
      clear: () => {
        setValue("");
        inputRef.current?.focus();
      },
      getValue: () => value,
      setValue: (newValue) => {
        setValue(newValue);
      },
      selectAll: () => {
        inputRef.current?.select();
      },
      insertAtCursor: (text) => {
        const input = inputRef.current;
        if (input) {
          const start = input.selectionStart;
          const end = input.selectionEnd;
          const newValue =
            value.substring(0, start) + text + value.substring(end);
          setValue(newValue);

          // Set cursor position after inserted text
          setTimeout(() => {
            const newPos = start + text.length;
            input.setSelectionRange(newPos, newPos);
          }, 0);
        }
      },
    }),
    [value]
  );

  const handleChange = (e) => {
    const newValue = e.target.value;
    setValue(newValue);
    onValueChange?.(newValue);
  };

  return (
    <input
      ref={inputRef}
      value={value}
      onChange={handleChange}
      {...otherProps}
    />
  );
});

// 🎯 Modal component with ref forwarding
const Modal = forwardRef(({ children, title, onClose }, ref) => {
  const [isOpen, setIsOpen] = useState(false);
  const modalRef = useRef(null);
  const previousFocusRef = useRef(null);

  useImperativeHandle(
    ref,
    () => ({
      open: () => {
        previousFocusRef.current = document.activeElement;
        setIsOpen(true);
      },
      close: () => {
        setIsOpen(false);
        previousFocusRef.current?.focus();
      },
      isOpen: () => isOpen,
    }),
    [isOpen]
  );

  // 🎯 Focus management
  useEffect(() => {
    if (isOpen && modalRef.current) {
      modalRef.current.focus();
    }
  }, [isOpen]);

  // 🎯 Escape key handling
  useEffect(() => {
    const handleEscape = (e) => {
      if (e.key === "Escape" && isOpen) {
        setIsOpen(false);
        onClose?.();
      }
    };

    document.addEventListener("keydown", handleEscape);
    return () => document.removeEventListener("keydown", handleEscape);
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return (
    <div
      className="modal-overlay"
      onClick={() => {
        setIsOpen(false);
        onClose?.();
      }}
    >
      <div
        ref={modalRef}
        className="modal-content"
        onClick={(e) => e.stopPropagation()}
        tabIndex={-1}
      >
        <div className="modal-header">
          <h3>{title}</h3>
          <button
            onClick={() => {
              setIsOpen(false);
              onClose?.();
            }}
          >
            ×
          </button>
        </div>
        <div className="modal-body">{children}</div>
      </div>
    </div>
  );
});

// Usage examples
function RefForwardingExamples() {
  const customInputRef = useRef(null);
  const advancedInputRef = useRef(null);
  const modalRef = useRef(null);

  const [inputValue, setInputValue] = useState("");

  return (
    <div className="ref-forwarding-examples">
      <h3>Ref Forwarding Examples</h3>

      {/* Basic ref forwarding */}
      <div className="basic-forwarding">
        <h4>Basic Ref Forwarding</h4>
        <CustomInput
          ref={customInputRef}
          label="Custom Input"
          placeholder="Type something..."
        />
        <button onClick={() => customInputRef.current?.focus()}>
          Focus Custom Input
        </button>
      </div>

      {/* Advanced ref forwarding with imperative API */}
      <div className="advanced-forwarding">
        <h4>Advanced Ref Forwarding (Imperative API)</h4>
        <AdvancedInput
          ref={advancedInputRef}
          placeholder="Advanced input..."
          onValueChange={setInputValue}
        />
        <p>Current value: {inputValue}</p>

        <div className="advanced-controls">
          <button onClick={() => advancedInputRef.current?.focus()}>
            Focus
          </button>
          <button onClick={() => advancedInputRef.current?.clear()}>
            Clear
          </button>
          <button onClick={() => advancedInputRef.current?.selectAll()}>
            Select All
          </button>
          <button
            onClick={() =>
              advancedInputRef.current?.insertAtCursor(" [INSERTED] ")
            }
          >
            Insert Text
          </button>
          <button
            onClick={() => {
              const value = advancedInputRef.current?.getValue();
              alert(`Current value: ${value}`);
            }}
          >
            Get Value
          </button>
        </div>
      </div>

      {/* Modal with ref forwarding */}
      <div className="modal-forwarding">
        <h4>Modal with Ref Forwarding</h4>
        <button onClick={() => modalRef.current?.open()}>Open Modal</button>

        <Modal
          ref={modalRef}
          title="Example Modal"
          onClose={() => console.log("Modal closed")}
        >
          <p>This is modal content.</p>
          <p>Press Escape or click outside to close.</p>
          <input type="text" placeholder="Focus is managed automatically" />
        </Modal>
      </div>
    </div>
  );
}

Custom Hooks with Refs

jsx
// 🎯 Custom hook for element visibility
function useIntersectionObserver(options = {}) {
  const [isIntersecting, setIsIntersecting] = useState(false);
  const [entry, setEntry] = useState(null);
  const elementRef = useRef(null);

  useEffect(() => {
    const element = elementRef.current;
    if (!element) return;

    const observer = new IntersectionObserver(([entry]) => {
      setIsIntersecting(entry.isIntersecting);
      setEntry(entry);
    }, options);

    observer.observe(element);

    return () => {
      observer.unobserve(element);
    };
  }, [options]);

  return [elementRef, isIntersecting, entry];
}

// 🎯 Custom hook for click outside
function useClickOutside(callback) {
  const ref = useRef(null);

  useEffect(() => {
    const handleClick = (event) => {
      if (ref.current && !ref.current.contains(event.target)) {
        callback();
      }
    };

    document.addEventListener("mousedown", handleClick);
    return () => document.removeEventListener("mousedown", handleClick);
  }, [callback]);

  return ref;
}

// 🎯 Custom hook for hover
function useHover() {
  const [isHovered, setIsHovered] = useState(false);
  const ref = useRef(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const handleMouseEnter = () => setIsHovered(true);
    const handleMouseLeave = () => setIsHovered(false);

    element.addEventListener("mouseenter", handleMouseEnter);
    element.addEventListener("mouseleave", handleMouseLeave);

    return () => {
      element.removeEventListener("mouseenter", handleMouseEnter);
      element.removeEventListener("mouseleave", handleMouseLeave);
    };
  }, []);

  return [ref, isHovered];
}

// 🎯 Custom hook for element size
function useElementSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });
  const ref = useRef(null);

  useEffect(() => {
    const element = ref.current;
    if (!element) return;

    const resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) {
        const { width, height } = entry.contentRect;
        setSize({ width, height });
      }
    });

    resizeObserver.observe(element);

    return () => {
      resizeObserver.unobserve(element);
    };
  }, []);

  return [ref, size];
}

// Usage examples
function CustomHookExamples() {
  const [visibilityRef, isVisible] = useIntersectionObserver({
    threshold: 0.5,
  });

  const [hoverRef, isHovered] = useHover();
  const [sizeRef, size] = useElementSize();

  const [showDropdown, setShowDropdown] = useState(false);
  const dropdownRef = useClickOutside(() => setShowDropdown(false));

  return (
    <div className="custom-hook-examples">
      <h3>Custom Hooks with Refs</h3>

      {/* Intersection Observer */}
      <div
        style={{ height: "200px", overflow: "auto", border: "1px solid #ccc" }}
      >
        <div style={{ height: "300px", padding: "20px" }}>
          <p>Scroll down to see the observed element...</p>
        </div>
        <div
          ref={visibilityRef}
          style={{
            padding: "20px",
            backgroundColor: isVisible ? "lightgreen" : "lightcoral",
            transition: "background-color 0.3s",
          }}
        >
          <p>This element is {isVisible ? "visible" : "not visible"}</p>
        </div>
        <div style={{ height: "300px", padding: "20px" }}>
          <p>More content below...</p>
        </div>
      </div>

      {/* Hover Detection */}
      <div
        ref={hoverRef}
        style={{
          padding: "20px",
          margin: "20px 0",
          backgroundColor: isHovered ? "lightblue" : "lightgray",
          transition: "background-color 0.3s",
          cursor: "pointer",
        }}
      >
        <p>Hover over me! Currently {isHovered ? "hovered" : "not hovered"}</p>
      </div>

      {/* Element Size */}
      <div
        ref={sizeRef}
        style={{
          padding: "20px",
          margin: "20px 0",
          border: "2px solid #333",
          resize: "both",
          overflow: "auto",
          minWidth: "200px",
          minHeight: "100px",
        }}
      >
        <p>Resize me!</p>
        <p>Width: {size.width.toFixed(0)}px</p>
        <p>Height: {size.height.toFixed(0)}px</p>
      </div>

      {/* Click Outside */}
      <div className="dropdown-container">
        <button onClick={() => setShowDropdown(!showDropdown)}>
          Toggle Dropdown
        </button>

        {showDropdown && (
          <div
            ref={dropdownRef}
            style={{
              position: "absolute",
              top: "100%",
              left: 0,
              backgroundColor: "white",
              border: "1px solid #ccc",
              padding: "10px",
              zIndex: 1000,
            }}
          >
            <p>Click outside to close</p>
            <button onClick={() => alert("Button clicked!")}>Action</button>
          </div>
        )}
      </div>
    </div>
  );
}

⚠️ Common Mistakes & Anti-Patterns

1. Using Refs Instead of State

jsx
// ❌ WRONG: Using ref for UI state
function WrongRefUsage() {
  const countRef = useRef(0);

  const increment = () => {
    countRef.current += 1;
    // ❌ UI won't update because no re-render is triggered
  };

  return (
    <div>
      <p>Count: {countRef.current}</p> {/* ❌ Won't update */}
      <button onClick={increment}>Increment</button>
    </div>
  );
}

// ✅ CORRECT: Use state for UI values
function CorrectStateUsage() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount((prev) => prev + 1); // ✅ Triggers re-render
  };

  return (
    <div>
      <p>Count: {count}</p> {/* ✅ Updates correctly */}
      <button onClick={increment}>Increment</button>
    </div>
  );
}

2. Accessing Refs During Render

jsx
// ❌ WRONG: Accessing ref.current during render
function WrongRefAccess() {
  const inputRef = useRef(null);

  // ❌ Don't access ref.current during render
  const inputValue = inputRef.current?.value || "";

  return (
    <div>
      <input ref={inputRef} />
      <p>Value: {inputValue}</p> {/* ❌ May be stale or null */}
    </div>
  );
}

// ✅ CORRECT: Access refs in effects or event handlers
function CorrectRefAccess() {
  const inputRef = useRef(null);
  const [inputValue, setInputValue] = useState("");

  const handleGetValue = () => {
    // ✅ Access ref in event handler
    const value = inputRef.current?.value || "";
    setInputValue(value);
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={handleGetValue}>Get Value</button>
      <p>Value: {inputValue}</p>
    </div>
  );
}

3. Not Handling Null Refs

jsx
// ❌ WRONG: Not checking for null
function UnsafeRefAccess() {
  const elementRef = useRef(null);

  const handleClick = () => {
    elementRef.current.focus(); // ❌ May throw error if null
  };

  return (
    <div>
      <input ref={elementRef} />
      <button onClick={handleClick}>Focus</button>
    </div>
  );
}

// ✅ CORRECT: Always check for null
function SafeRefAccess() {
  const elementRef = useRef(null);

  const handleClick = () => {
    if (elementRef.current) {
      // ✅ Safe null check
      elementRef.current.focus();
    }

    // Or use optional chaining
    elementRef.current?.focus(); // ✅ Also safe
  };

  return (
    <div>
      <input ref={elementRef} />
      <button onClick={handleClick}>Focus</button>
    </div>
  );
}

4. Ref Callback Mistakes

jsx
// ❌ WRONG: Inline ref callback
function InlineRefCallback() {
  const [elements, setElements] = useState([]);

  return (
    <div>
      {[1, 2, 3].map((num) => (
        <input
          key={num}
          ref={(el) => {
            // ❌ Creates new function on every render
            if (el) {
              setElements((prev) => [...prev, el]);
            }
          }}
        />
      ))}
    </div>
  );
}

// ✅ CORRECT: Stable ref callback
function StableRefCallback() {
  const [elements, setElements] = useState([]);
  const elementsRef = useRef([]);

  const setElementRef = useCallback(
    (index) => (el) => {
      if (el) {
        elementsRef.current[index] = el;
      }
    },
    []
  );

  return (
    <div>
      {[1, 2, 3].map((num, index) => (
        <input
          key={num}
          ref={setElementRef(index)} // ✅ Stable callback
        />
      ))}
    </div>
  );
}

🧪 Mini Challenges

Challenge 1: Focus Management System

Build a focus management system that:

  • Tracks focus history
  • Provides focus navigation (next/previous)
  • Handles focus trapping in modals
  • Restores focus when components unmount
💡 Solution
jsx
function useFocusManager() {
  const focusHistoryRef = useRef([]);
  const currentFocusIndexRef = useRef(-1);
  const trapContainerRef = useRef(null);
  const isTrapActiveRef = useRef(false);

  const addToHistory = useCallback((element) => {
    if (
      element &&
      element !== focusHistoryRef.current[currentFocusIndexRef.current]
    ) {
      focusHistoryRef.current.push(element);
      currentFocusIndexRef.current = focusHistoryRef.current.length - 1;
    }
  }, []);

  const focusPrevious = useCallback(() => {
    if (currentFocusIndexRef.current > 0) {
      currentFocusIndexRef.current -= 1;
      const element = focusHistoryRef.current[currentFocusIndexRef.current];
      if (element && document.contains(element)) {
        element.focus();
      }
    }
  }, []);

  const focusNext = useCallback(() => {
    if (currentFocusIndexRef.current < focusHistoryRef.current.length - 1) {
      currentFocusIndexRef.current += 1;
      const element = focusHistoryRef.current[currentFocusIndexRef.current];
      if (element && document.contains(element)) {
        element.focus();
      }
    }
  }, []);

  const enableFocusTrap = useCallback((container) => {
    trapContainerRef.current = container;
    isTrapActiveRef.current = true;

    const handleKeyDown = (e) => {
      if (
        e.key === "Tab" &&
        isTrapActiveRef.current &&
        trapContainerRef.current
      ) {
        const focusableElements = trapContainerRef.current.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );

        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];

        if (e.shiftKey) {
          if (document.activeElement === firstElement) {
            e.preventDefault();
            lastElement.focus();
          }
        } else {
          if (document.activeElement === lastElement) {
            e.preventDefault();
            firstElement.focus();
          }
        }
      }
    };

    document.addEventListener("keydown", handleKeyDown);

    return () => {
      document.removeEventListener("keydown", handleKeyDown);
      isTrapActiveRef.current = false;
    };
  }, []);

  const disableFocusTrap = useCallback(() => {
    isTrapActiveRef.current = false;
    trapContainerRef.current = null;
  }, []);

  useEffect(() => {
    const handleFocus = (e) => {
      if (!isTrapActiveRef.current) {
        addToHistory(e.target);
      }
    };

    document.addEventListener("focusin", handleFocus);

    return () => {
      document.removeEventListener("focusin", handleFocus);
    };
  }, [addToHistory]);

  return {
    focusPrevious,
    focusNext,
    enableFocusTrap,
    disableFocusTrap,
    getFocusHistory: () => focusHistoryRef.current,
  };
}

function FocusManagementDemo() {
  const { focusPrevious, focusNext, enableFocusTrap, disableFocusTrap } =
    useFocusManager();
  const [showModal, setShowModal] = useState(false);
  const modalRef = useRef(null);

  useEffect(() => {
    if (showModal && modalRef.current) {
      const cleanup = enableFocusTrap(modalRef.current);
      return cleanup;
    } else {
      disableFocusTrap();
    }
  }, [showModal, enableFocusTrap, disableFocusTrap]);

  return (
    <div className="focus-management-demo">
      <h3>Focus Management System</h3>

      <div className="controls">
        <button onClick={focusPrevious}>Focus Previous</button>
        <button onClick={focusNext}>Focus Next</button>
        <button onClick={() => setShowModal(true)}>Open Modal</button>
      </div>

      <div className="form">
        <input placeholder="Input 1" />
        <input placeholder="Input 2" />
        <button>Button 1</button>
        <textarea placeholder="Textarea"></textarea>
        <button>Button 2</button>
      </div>

      {showModal && (
        <div className="modal-overlay">
          <div ref={modalRef} className="modal" tabIndex={-1}>
            <h4>Modal with Focus Trap</h4>
            <input placeholder="Modal input 1" />
            <input placeholder="Modal input 2" />
            <button onClick={() => setShowModal(false)}>Close</button>
            <button>Another Button</button>
          </div>
        </div>
      )}
    </div>
  );
}

Challenge 2: Virtual Scrolling with Refs

Create a virtual scrolling component that:

  • Only renders visible items
  • Uses refs for scroll position tracking
  • Handles dynamic item heights
  • Maintains scroll position during updates
💡 Solution
jsx
function useVirtualScroll({
  items,
  itemHeight,
  containerHeight,
  overscan = 5,
}) {
  const scrollElementRef = useRef(null);
  const [scrollTop, setScrollTop] = useState(0);
  const [isScrolling, setIsScrolling] = useState(false);
  const scrollTimeoutRef = useRef(null);

  const startIndex = Math.max(0, Math.floor(scrollTop / itemHeight) - overscan);
  const endIndex = Math.min(
    items.length - 1,
    Math.ceil((scrollTop + containerHeight) / itemHeight) + overscan
  );

  const visibleItems = items
    .slice(startIndex, endIndex + 1)
    .map((item, index) => ({
      ...item,
      index: startIndex + index,
    }));

  const totalHeight = items.length * itemHeight;
  const offsetY = startIndex * itemHeight;

  const handleScroll = useCallback((e) => {
    setScrollTop(e.target.scrollTop);
    setIsScrolling(true);

    if (scrollTimeoutRef.current) {
      clearTimeout(scrollTimeoutRef.current);
    }

    scrollTimeoutRef.current = setTimeout(() => {
      setIsScrolling(false);
    }, 150);
  }, []);

  const scrollToIndex = useCallback(
    (index) => {
      if (scrollElementRef.current) {
        const scrollTop = index * itemHeight;
        scrollElementRef.current.scrollTop = scrollTop;
        setScrollTop(scrollTop);
      }
    },
    [itemHeight]
  );

  const scrollToTop = useCallback(() => {
    scrollToIndex(0);
  }, [scrollToIndex]);

  const scrollToBottom = useCallback(() => {
    scrollToIndex(items.length - 1);
  }, [scrollToIndex, items.length]);

  return {
    scrollElementRef,
    visibleItems,
    totalHeight,
    offsetY,
    handleScroll,
    scrollToIndex,
    scrollToTop,
    scrollToBottom,
    isScrolling,
  };
}

function VirtualScrollDemo() {
  const items = useMemo(
    () =>
      Array.from({ length: 10000 }, (_, i) => ({
        id: i,
        name: `Item ${i}`,
        description: `Description for item ${i}`,
      })),
    []
  );

  const {
    scrollElementRef,
    visibleItems,
    totalHeight,
    offsetY,
    handleScroll,
    scrollToIndex,
    scrollToTop,
    scrollToBottom,
    isScrolling,
  } = useVirtualScroll({
    items,
    itemHeight: 50,
    containerHeight: 400,
  });

  const [jumpToIndex, setJumpToIndex] = useState("");

  const handleJumpTo = () => {
    const index = parseInt(jumpToIndex);
    if (!isNaN(index) && index >= 0 && index < items.length) {
      scrollToIndex(index);
    }
  };

  return (
    <div className="virtual-scroll-demo">
      <h3>Virtual Scrolling with Refs</h3>

      <div className="controls">
        <button onClick={scrollToTop}>Scroll to Top</button>
        <button onClick={scrollToBottom}>Scroll to Bottom</button>

        <div className="jump-controls">
          <input
            type="number"
            value={jumpToIndex}
            onChange={(e) => setJumpToIndex(e.target.value)}
            placeholder="Jump to index"
            min="0"
            max={items.length - 1}
          />
          <button onClick={handleJumpTo}>Jump</button>
        </div>
      </div>

      <div className="scroll-info">
        <p>Total items: {items.length}</p>
        <p>Visible items: {visibleItems.length}</p>
        <p>Scrolling: {isScrolling ? "Yes" : "No"}</p>
      </div>

      <div
        ref={scrollElementRef}
        className="virtual-scroll-container"
        style={{
          height: "400px",
          overflow: "auto",
          border: "1px solid #ccc",
        }}
        onScroll={handleScroll}
      >
        <div style={{ height: totalHeight, position: "relative" }}>
          <div
            style={{
              transform: `translateY(${offsetY}px)`,
              position: "absolute",
              top: 0,
              left: 0,
              right: 0,
            }}
          >
            {visibleItems.map((item) => (
              <div
                key={item.id}
                style={{
                  height: "50px",
                  padding: "10px",
                  borderBottom: "1px solid #eee",
                  display: "flex",
                  alignItems: "center",
                }}
              >
                <div>
                  <strong>{item.name}</strong>
                  <br />
                  <small>{item.description}</small>
                </div>
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

🎯 When and Why: useRef Decision Framework

Quick Decision Tree

🤔 Should I use useRef?

├── Do I need to access DOM elements directly?
│   ├── Yes → useRef for DOM reference ✅
│   └── No → Continue...
│
├── Do I need a value that persists but doesn't trigger re-renders?
│   ├── Yes → useRef for mutable value ✅
│   └── No → Continue...
│
├── Do I need to store timer IDs, intervals, or subscriptions?
│   ├── Yes → useRef for cleanup references ✅
│   └── No → Continue...
│
├── Do I need to track previous values?
│   ├── Yes → useRef for previous state ✅
│   └── No → Continue...
│
├── Do I need to prevent stale closures in effects?
│   ├── Yes → useRef for current values ✅
│   └── No → Use useState instead
│
└── Is this for UI state that should trigger re-renders?
    └── Yes → Use useState, not useRef ❌

🎤 Interview Insights

Common Interview Questions

  1. "What's the difference between useRef and useState?"

    • useRef: Mutable, doesn't trigger re-renders, persists between renders
    • useState: Immutable updates, triggers re-renders, for UI state
    • Show examples of when to use each
  2. "How do you access DOM elements in React?"

    • Use useRef to create a ref
    • Attach ref to JSX element
    • Access via ref.current in effects or event handlers
    • Show focus management example
  3. "What is ref forwarding and when do you use it?"

    • Passing refs through components to child elements
    • Use forwardRef and useImperativeHandle
    • Common in reusable component libraries
    • Show custom input component example
  4. "How do you prevent memory leaks with refs?"

    • Store cleanup functions (timers, subscriptions)
    • Check if component is mounted before state updates
    • Clean up in useEffect return function
    • Show timer cleanup example

Code Review Red Flags

jsx
// 🚨 Red Flags in Interviews:

// ❌ Using ref for UI state
const countRef = useRef(0);
return <p>{countRef.current}</p>; // Won't update

// ❌ Accessing ref during render
const value = inputRef.current?.value; // May be null/stale

// ❌ Not checking for null
elementRef.current.focus(); // May throw error

// ❌ Inline ref callbacks
ref={(el) => { /* new function every render */ }}

// ❌ Forgetting cleanup
const timer = setInterval(...);
// No cleanup in useEffect return

🎯 Key Takeaways

Mental Model

jsx
// 🧠 Think of useRef as:

// "A box that holds a value that:
//  - Persists between renders
//  - Doesn't trigger re-renders when changed
//  - Can hold any mutable value
//  - Is perfect for DOM access and cleanup"

const ref = useRef(initialValue);
// ref.current = the actual value
// ref itself never changes

Best Practices Summary

  1. Use refs for DOM access - focus, scroll, measurements
  2. Use refs for mutable values - timers, counters, flags
  3. Always check for null - refs may be null initially
  4. Don't access refs during render - use effects or event handlers
  5. Use ref forwarding for reusable components
  6. Store cleanup references - prevent memory leaks
  7. Prefer useState for UI state - don't use refs for visible data

Next up: useMemo: What, When, and Why - Master performance optimization and expensive calculation caching.

Previous: useEffect Dependencies & Cleanup


💡 Pro tip: In interviews, demonstrate understanding of when NOT to use refs. Show that you know the difference between mutable refs and immutable state, and always explain your choice.# 🚀 useMemo: What, When, and Why

Master React's performance optimization: Expensive calculations, object stability, and when memoization actually helps

🎯 What You'll Learn

  • Understanding useMemo fundamentals and when it's needed
  • Expensive calculation optimization patterns
  • Object and array reference stability
  • Dependency array best practices
  • Performance measurement and profiling
  • Common memoization mistakes and anti-patterns
  • Real-world optimization scenarios
  • Interview insights and best practices

🎯 Understanding useMemo Fundamentals

What is useMemo?

jsx
// 🎯 useMemo caches the result of expensive calculations
const memoizedValue = useMemo(() => {
  // Expensive calculation here
  return expensiveCalculation(dependencies);
}, [dependencies]);

// 🎯 Basic syntax breakdown:
// useMemo(calculateValue, dependencies)
// - calculateValue: Function that returns the value to cache
// - dependencies: Array of values that trigger recalculation

When React Re-calculates

jsx
function MemoBasicsDemo() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("John");
  const [items, setItems] = useState([1, 2, 3, 4, 5]);

  // 🎯 Without useMemo - runs on every render
  const expensiveCalculationWithoutMemo = () => {
    console.log("🔴 Expensive calculation running (no memo)");
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += Math.random();
    }
    return result;
  };

  // 🎯 With useMemo - only runs when dependencies change
  const expensiveCalculationWithMemo = useMemo(() => {
    console.log("🟢 Expensive calculation running (with memo)");
    let result = 0;
    for (let i = 0; i < 1000000; i++) {
      result += Math.random();
    }
    return result;
  }, [count]); // Only recalculates when count changes

  // 🎯 Array processing with memo
  const processedItems = useMemo(() => {
    console.log("🟡 Processing items array");
    return items.map((item) => ({
      id: item,
      value: item * 2,
      label: `Item ${item}`,
      isEven: item % 2 === 0,
    }));
  }, [items]); // Only recalculates when items array changes

  // 🎯 Complex object creation
  const userProfile = useMemo(() => {
    console.log("🔵 Creating user profile object");
    return {
      name,
      displayName: name.toUpperCase(),
      initials: name
        .split(" ")
        .map((n) => n[0])
        .join(""),
      metadata: {
        createdAt: new Date().toISOString(),
        count,
        hasItems: items.length > 0,
      },
    };
  }, [name, count, items.length]); // Recalculates when any dependency changes

  const renderCount = useRef(0);
  renderCount.current += 1;

  return (
    <div className="memo-basics-demo">
      <h3>useMemo Basics Demo</h3>

      <div className="render-info">
        <p>
          <strong>Render count:</strong> {renderCount.current}
        </p>
        <p>
          <em>Check console to see when calculations run</em>
        </p>
      </div>

      <div className="controls">
        <div className="control-group">
          <label>Count (affects memoized calculation):</label>
          <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
        </div>

        <div className="control-group">
          <label>Name (affects user profile):</label>
          <input
            value={name}
            onChange={(e) => setName(e.target.value)}
            placeholder="Enter name"
          />
        </div>

        <div className="control-group">
          <label>Items (affects processed items):</label>
          <button
            onClick={() => setItems((prev) => [...prev, prev.length + 1])}
          >
            Add Item
          </button>
          <button onClick={() => setItems((prev) => prev.slice(0, -1))}>
            Remove Item
          </button>
        </div>

        <div className="control-group">
          <label>Force re-render (no state change):</label>
          <button onClick={() => setCount(count)}>Force Re-render</button>
        </div>
      </div>

      <div className="results">
        <div className="result-section">
          <h4>Without Memo (runs every render)</h4>
          <p>Result: {expensiveCalculationWithoutMemo().toFixed(2)}</p>
        </div>

        <div className="result-section">
          <h4>With Memo (runs only when count changes)</h4>
          <p>Result: {expensiveCalculationWithMemo.toFixed(2)}</p>
        </div>

        <div className="result-section">
          <h4>Processed Items</h4>
          <div className="items-grid">
            {processedItems.map((item) => (
              <div
                key={item.id}
                className={`item ${item.isEven ? "even" : "odd"}`}
              >
                {item.label}: {item.value}
              </div>
            ))}
          </div>
        </div>

        <div className="result-section">
          <h4>User Profile</h4>
          <pre>{JSON.stringify(userProfile, null, 2)}</pre>
        </div>
      </div>

      <div className="explanation">
        <h4>What's Happening:</h4>
        <ul>
          <li>
            <strong>Without memo:</strong> Calculation runs on every render
            (expensive!)
          </li>
          <li>
            <strong>With memo:</strong> Calculation only runs when dependencies
            change
          </li>
          <li>
            <strong>Processed items:</strong> Only recalculates when items array
            changes
          </li>
          <li>
            <strong>User profile:</strong> Only recalculates when name, count,
            or items.length changes
          </li>
        </ul>
      </div>
    </div>
  );
}

💰 Expensive Calculations

Identifying Expensive Operations

jsx
function ExpensiveCalculationsDemo() {
  const [data, setData] = useState(
    Array.from({ length: 10000 }, (_, i) => ({
      id: i,
      value: Math.random() * 100,
      category: ["A", "B", "C"][Math.floor(Math.random() * 3)],
    }))
  );

  const [filterCategory, setFilterCategory] = useState("all");
  const [sortBy, setSortBy] = useState("id");
  const [searchTerm, setSearchTerm] = useState("");
  const [threshold, setThreshold] = useState(50);

  // 🎯 Expensive filtering and sorting
  const processedData = useMemo(() => {
    console.time("Data processing");
    console.log("🔄 Processing data...");

    let result = data;

    // Filter by category
    if (filterCategory !== "all") {
      result = result.filter((item) => item.category === filterCategory);
    }

    // Filter by search term (expensive string operations)
    if (searchTerm) {
      result = result.filter(
        (item) =>
          item.id.toString().includes(searchTerm) ||
          item.category.toLowerCase().includes(searchTerm.toLowerCase())
      );
    }

    // Filter by threshold
    result = result.filter((item) => item.value >= threshold);

    // Sort (expensive for large arrays)
    result = result.sort((a, b) => {
      switch (sortBy) {
        case "value":
          return b.value - a.value;
        case "category":
          return a.category.localeCompare(b.category);
        default:
          return a.id - b.id;
      }
    });

    console.timeEnd("Data processing");
    return result;
  }, [data, filterCategory, searchTerm, threshold, sortBy]);

  // 🎯 Expensive statistical calculations
  const statistics = useMemo(() => {
    console.time("Statistics calculation");
    console.log("📊 Calculating statistics...");

    if (processedData.length === 0) {
      return {
        count: 0,
        average: 0,
        min: 0,
        max: 0,
        median: 0,
        standardDeviation: 0,
      };
    }

    const values = processedData.map((item) => item.value);
    const sum = values.reduce((acc, val) => acc + val, 0);
    const average = sum / values.length;

    const sortedValues = [...values].sort((a, b) => a - b);
    const median =
      sortedValues.length % 2 === 0
        ? (sortedValues[sortedValues.length / 2 - 1] +
            sortedValues[sortedValues.length / 2]) /
          2
        : sortedValues[Math.floor(sortedValues.length / 2)];

    const variance =
      values.reduce((acc, val) => acc + Math.pow(val - average, 2), 0) /
      values.length;
    const standardDeviation = Math.sqrt(variance);

    console.timeEnd("Statistics calculation");

    return {
      count: processedData.length,
      average: average.toFixed(2),
      min: Math.min(...values).toFixed(2),
      max: Math.max(...values).toFixed(2),
      median: median.toFixed(2),
      standardDeviation: standardDeviation.toFixed(2),
    };
  }, [processedData]);

  // 🎯 Expensive chart data preparation
  const chartData = useMemo(() => {
    console.time("Chart data preparation");
    console.log("📈 Preparing chart data...");

    // Group by category and calculate averages
    const categoryGroups = processedData.reduce((acc, item) => {
      if (!acc[item.category]) {
        acc[item.category] = [];
      }
      acc[item.category].push(item.value);
      return acc;
    }, {});

    const chartData = Object.entries(categoryGroups).map(
      ([category, values]) => ({
        category,
        average: values.reduce((sum, val) => sum + val, 0) / values.length,
        count: values.length,
        min: Math.min(...values),
        max: Math.max(...values),
      })
    );

    console.timeEnd("Chart data preparation");
    return chartData;
  }, [processedData]);

  const addRandomData = () => {
    const newItems = Array.from({ length: 1000 }, (_, i) => ({
      id: data.length + i,
      value: Math.random() * 100,
      category: ["A", "B", "C"][Math.floor(Math.random() * 3)],
    }));
    setData((prev) => [...prev, ...newItems]);
  };

  const resetData = () => {
    setData(
      Array.from({ length: 10000 }, (_, i) => ({
        id: i,
        value: Math.random() * 100,
        category: ["A", "B", "C"][Math.floor(Math.random() * 3)],
      }))
    );
  };

  return (
    <div className="expensive-calculations-demo">
      <h3>Expensive Calculations Demo</h3>

      <div className="performance-note">
        <p>
          <strong>📊 Performance Note:</strong> Open DevTools Console to see
          timing information
        </p>
      </div>

      <div className="controls">
        <div className="control-row">
          <div className="control-group">
            <label>Filter Category:</label>
            <select
              value={filterCategory}
              onChange={(e) => setFilterCategory(e.target.value)}
            >
              <option value="all">All Categories</option>
              <option value="A">Category A</option>
              <option value="B">Category B</option>
              <option value="C">Category C</option>
            </select>
          </div>

          <div className="control-group">
            <label>Sort By:</label>
            <select value={sortBy} onChange={(e) => setSortBy(e.target.value)}>
              <option value="id">ID</option>
              <option value="value">Value (High to Low)</option>
              <option value="category">Category</option>
            </select>
          </div>
        </div>

        <div className="control-row">
          <div className="control-group">
            <label>Search:</label>
            <input
              type="text"
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              placeholder="Search by ID or category"
            />
          </div>

          <div className="control-group">
            <label>Minimum Value: {threshold}</label>
            <input
              type="range"
              min="0"
              max="100"
              value={threshold}
              onChange={(e) => setThreshold(Number(e.target.value))}
            />
          </div>
        </div>

        <div className="control-row">
          <button onClick={addRandomData}>Add 1000 Random Items</button>
          <button onClick={resetData}>Reset Data</button>
        </div>
      </div>

      <div className="results">
        <div className="result-section">
          <h4>Data Summary</h4>
          <p>Total items: {data.length}</p>
          <p>Filtered items: {processedData.length}</p>
          <p>Processing time: Check console</p>
        </div>

        <div className="result-section">
          <h4>Statistics</h4>
          <div className="stats-grid">
            <div className="stat">
              <strong>Count:</strong> {statistics.count}
            </div>
            <div className="stat">
              <strong>Average:</strong> {statistics.average}
            </div>
            <div className="stat">
              <strong>Min:</strong> {statistics.min}
            </div>
            <div className="stat">
              <strong>Max:</strong> {statistics.max}
            </div>
            <div className="stat">
              <strong>Median:</strong> {statistics.median}
            </div>
            <div className="stat">
              <strong>Std Dev:</strong> {statistics.standardDeviation}
            </div>
          </div>
        </div>

        <div className="result-section">
          <h4>Chart Data by Category</h4>
          <div className="chart-data">
            {chartData.map((item) => (
              <div key={item.category} className="chart-item">
                <h5>Category {item.category}</h5>
                <p>Count: {item.count}</p>
                <p>Average: {item.average.toFixed(2)}</p>
                <p>
                  Range: {item.min.toFixed(2)} - {item.max.toFixed(2)}
                </p>
              </div>
            ))}
          </div>
        </div>

        <div className="result-section">
          <h4>Sample Data (First 10 items)</h4>
          <div className="data-preview">
            {processedData.slice(0, 10).map((item) => (
              <div key={item.id} className="data-item">
                ID: {item.id}, Value: {item.value.toFixed(2)}, Category:{" "}
                {item.category}
              </div>
            ))}
            {processedData.length > 10 && (
              <p>... and {processedData.length - 10} more items</p>
            )}
          </div>
        </div>
      </div>

      <div className="optimization-notes">
        <h4>Optimization Techniques Used:</h4>
        <ul>
          <li>
            <strong>Data Processing:</strong> Memoized filtering, searching, and
            sorting
          </li>
          <li>
            <strong>Statistics:</strong> Memoized complex mathematical
            calculations
          </li>
          <li>
            <strong>Chart Data:</strong> Memoized data transformation for
            visualization
          </li>
          <li>
            <strong>Dependency Arrays:</strong> Only recalculate when relevant
            inputs change
          </li>
        </ul>
      </div>
    </div>
  );
}

🔗 Reference Stability

Object and Array Reference Stability

jsx
function ReferenceStabilityDemo() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("John");
  const [items, setItems] = useState(["apple", "banana", "cherry"]);

  // 🎯 Without useMemo - new object/array on every render
  const unstableConfig = {
    theme: "dark",
    count,
    features: ["feature1", "feature2"],
  };

  const unstableArray = items.map((item) => item.toUpperCase());

  // 🎯 With useMemo - stable references
  const stableConfig = useMemo(
    () => ({
      theme: "dark",
      count,
      features: ["feature1", "feature2"],
    }),
    [count]
  ); // Only creates new object when count changes

  const stableArray = useMemo(
    () => items.map((item) => item.toUpperCase()),
    [items]
  ); // Only creates new array when items change

  // 🎯 Complex object with nested properties
  const complexConfig = useMemo(
    () => ({
      user: {
        name,
        preferences: {
          theme: "dark",
          notifications: true,
          layout: "grid",
        },
      },
      app: {
        version: "1.0.0",
        features: {
          search: true,
          export: count > 5,
          advanced: count > 10,
        },
      },
      data: {
        items: items.length,
        processed: items.map((item) => ({
          original: item,
          processed: item.toUpperCase(),
          length: item.length,
        })),
      },
    }),
    [name, count, items]
  );

  // 🎯 Filtered and sorted data
  const processedItems = useMemo(() => {
    return items
      .filter((item) => item.length > 3)
      .sort()
      .map((item) => ({
        id: item,
        display: item.charAt(0).toUpperCase() + item.slice(1),
        metadata: {
          length: item.length,
          vowels: (item.match(/[aeiou]/gi) || []).length,
        },
      }));
  }, [items]);

  // 🎯 Reference tracking
  const previousRefsRef = useRef({});
  const renderCountRef = useRef(0);
  renderCountRef.current += 1;

  const checkReferenceStability = (name, current) => {
    const previous = previousRefsRef.current[name];
    const isStable = previous === current;
    previousRefsRef.current[name] = current;
    return isStable;
  };

  const addItem = () => {
    const newItem = `item${items.length + 1}`;
    setItems((prev) => [...prev, newItem]);
  };

  const removeItem = () => {
    setItems((prev) => prev.slice(0, -1));
  };

  const updateName = (newName) => {
    setName(newName);
  };

  return (
    <div className="reference-stability-demo">
      <h3>Reference Stability Demo</h3>

      <div className="render-info">
        <p>
          <strong>Render #{renderCountRef.current}</strong>
        </p>
        <p>
          <em>Watch reference stability indicators below</em>
        </p>
      </div>

      <div className="controls">
        <div className="control-group">
          <button onClick={() => setCount((c) => c + 1)}>Count: {count}</button>
          <button onClick={() => setCount(count)}>
            Force Re-render (same count)
          </button>
        </div>

        <div className="control-group">
          <input
            value={name}
            onChange={(e) => updateName(e.target.value)}
            placeholder="Enter name"
          />
        </div>

        <div className="control-group">
          <button onClick={addItem}>Add Item</button>
          <button onClick={removeItem}>Remove Item</button>
          <span>Items: {items.join(", ")}</span>
        </div>
      </div>

      <div className="reference-tracking">
        <h4>Reference Stability Tracking</h4>

        <div className="stability-grid">
          <div className="stability-item">
            <h5>Unstable Config Object</h5>
            <div
              className={`stability-indicator ${
                checkReferenceStability("unstableConfig", unstableConfig)
                  ? "stable"
                  : "unstable"
              }`}
            >
              {checkReferenceStability("unstableConfig", unstableConfig)
                ? "✅ Stable"
                : "❌ New Reference"}
            </div>
            <pre>{JSON.stringify(unstableConfig, null, 2)}</pre>
          </div>

          <div className="stability-item">
            <h5>Stable Config Object (useMemo)</h5>
            <div
              className={`stability-indicator ${
                checkReferenceStability("stableConfig", stableConfig)
                  ? "stable"
                  : "unstable"
              }`}
            >
              {checkReferenceStability("stableConfig", stableConfig)
                ? "✅ Stable"
                : "🔄 Updated"}
            </div>
            <pre>{JSON.stringify(stableConfig, null, 2)}</pre>
          </div>

          <div className="stability-item">
            <h5>Unstable Array</h5>
            <div
              className={`stability-indicator ${
                checkReferenceStability("unstableArray", unstableArray)
                  ? "stable"
                  : "unstable"
              }`}
            >
              {checkReferenceStability("unstableArray", unstableArray)
                ? "✅ Stable"
                : "❌ New Reference"}
            </div>
            <div>Array: [{unstableArray.join(", ")}]</div>
          </div>

          <div className="stability-item">
            <h5>Stable Array (useMemo)</h5>
            <div
              className={`stability-indicator ${
                checkReferenceStability("stableArray", stableArray)
                  ? "stable"
                  : "unstable"
              }`}
            >
              {checkReferenceStability("stableArray", stableArray)
                ? "✅ Stable"
                : "🔄 Updated"}
            </div>
            <div>Array: [{stableArray.join(", ")}]</div>
          </div>
        </div>

        <div className="complex-object">
          <h5>Complex Configuration Object</h5>
          <div
            className={`stability-indicator ${
              checkReferenceStability("complexConfig", complexConfig)
                ? "stable"
                : "unstable"
            }`}
          >
            {checkReferenceStability("complexConfig", complexConfig)
              ? "✅ Stable"
              : "🔄 Updated"}
          </div>
          <details>
            <summary>View Complex Config</summary>
            <pre>{JSON.stringify(complexConfig, null, 2)}</pre>
          </details>
        </div>

        <div className="processed-items">
          <h5>Processed Items</h5>
          <div
            className={`stability-indicator ${
              checkReferenceStability("processedItems", processedItems)
                ? "stable"
                : "unstable"
            }`}
          >
            {checkReferenceStability("processedItems", processedItems)
              ? "✅ Stable"
              : "🔄 Updated"}
          </div>
          <div className="items-display">
            {processedItems.map((item) => (
              <div key={item.id} className="processed-item">
                <strong>{item.display}</strong>
                <br />
                <small>
                  Length: {item.metadata.length}, Vowels: {item.metadata.vowels}
                </small>
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="explanation">
        <h4>Reference Stability Impact:</h4>
        <ul>
          <li>
            <strong>❌ Unstable references:</strong> Create new objects/arrays
            every render
          </li>
          <li>
            <strong>✅ Stable references:</strong> Only create new when
            dependencies change
          </li>
          <li>
            <strong>🔄 Updated references:</strong> New reference due to
            dependency change
          </li>
          <li>
            <strong>Performance:</strong> Stable references prevent unnecessary
            child re-renders
          </li>
          <li>
            <strong>useEffect:</strong> Stable references prevent infinite loops
          </li>
        </ul>
      </div>
    </div>
  );
}

Child Component Re-render Prevention

jsx
// 🎯 Child component that shows re-render behavior
const ExpensiveChild = React.memo(({ config, onAction }) => {
  const renderCount = useRef(0);
  renderCount.current += 1;

  console.log(`ExpensiveChild rendered ${renderCount.current} times`);

  // Simulate expensive rendering
  const expensiveValue = useMemo(() => {
    console.log("ExpensiveChild: Performing expensive calculation");
    let result = 0;
    for (let i = 0; i < 100000; i++) {
      result += Math.random();
    }
    return result;
  }, [config.seed]);

  return (
    <div className="expensive-child">
      <h4>Expensive Child Component</h4>
      <p>Render count: {renderCount.current}</p>
      <p>Config theme: {config.theme}</p>
      <p>Config seed: {config.seed}</p>
      <p>Expensive value: {expensiveValue.toFixed(2)}</p>
      <button onClick={() => onAction("child-action")}>Child Action</button>
    </div>
  );
});

function ChildReRenderDemo() {
  const [parentCount, setParentCount] = useState(0);
  const [childSeed, setChildSeed] = useState(1);
  const [theme, setTheme] = useState("light");

  // 🎯 Unstable config - causes unnecessary re-renders
  const unstableConfig = {
    theme,
    seed: childSeed,
    timestamp: Date.now(), // ❌ Always different!
  };

  // 🎯 Stable config - only changes when dependencies change
  const stableConfig = useMemo(
    () => ({
      theme,
      seed: childSeed,
      // timestamp: Date.now() // ❌ Don't include changing values
    }),
    [theme, childSeed]
  );

  // 🎯 Unstable callback - new function every render
  const unstableCallback = (action) => {
    console.log("Action:", action, "Parent count:", parentCount);
  };

  // 🎯 Stable callback - memoized function
  const stableCallback = useCallback(
    (action) => {
      console.log("Action:", action, "Parent count:", parentCount);
    },
    [parentCount]
  );

  const parentRenderCount = useRef(0);
  parentRenderCount.current += 1;

  return (
    <div className="child-rerender-demo">
      <h3>Child Re-render Prevention Demo</h3>

      <div className="parent-info">
        <p>
          <strong>Parent render count:</strong> {parentRenderCount.current}
        </p>
        <p>
          <em>Check console for child render logs</em>
        </p>
      </div>

      <div className="controls">
        <div className="control-group">
          <button onClick={() => setParentCount((c) => c + 1)}>
            Parent Count: {parentCount}
          </button>
          <p>
            <em>This should NOT cause child re-renders with stable props</em>
          </p>
        </div>

        <div className="control-group">
          <button onClick={() => setChildSeed((s) => s + 1)}>
            Child Seed: {childSeed}
          </button>
          <p>
            <em>This SHOULD cause child re-renders</em>
          </p>
        </div>

        <div className="control-group">
          <button
            onClick={() => setTheme((t) => (t === "light" ? "dark" : "light"))}
          >
            Theme: {theme}
          </button>
          <p>
            <em>This SHOULD cause child re-renders</em>
          </p>
        </div>
      </div>

      <div className="children-comparison">
        <div className="child-section">
          <h4>❌ With Unstable Props</h4>
          <p>
            <em>Re-renders on every parent render</em>
          </p>
          <ExpensiveChild config={unstableConfig} onAction={unstableCallback} />
        </div>

        <div className="child-section">
          <h4>✅ With Stable Props (useMemo + useCallback)</h4>
          <p>
            <em>Only re-renders when props actually change</em>
          </p>
          <ExpensiveChild config={stableConfig} onAction={stableCallback} />
        </div>
      </div>

      <div className="explanation">
        <h4>Optimization Techniques:</h4>
        <ul>
          <li>
            <strong>React.memo:</strong> Prevents re-renders when props haven't
            changed
          </li>
          <li>
            <strong>useMemo:</strong> Stabilizes object/array references
          </li>
          <li>
            <strong>useCallback:</strong> Stabilizes function references
          </li>
          <li>
            <strong>Dependency arrays:</strong> Control when memoization updates
          </li>
        </ul>
      </div>
    </div>
  );
}

🎯 Dependency Array Best Practices

Dependency Array Patterns

jsx
function DependencyArrayDemo() {
  const [user, setUser] = useState({
    name: "John",
    age: 30,
    email: "john@example.com",
  });
  const [settings, setSettings] = useState({
    theme: "light",
    notifications: true,
  });
  const [items, setItems] = useState([1, 2, 3, 4, 5]);
  const [filter, setFilter] = useState("");

  // 🎯 Primitive dependencies
  const userDisplayName = useMemo(() => {
    console.log("Computing user display name");
    return `${user.name} (${user.age} years old)`;
  }, [user.name, user.age]); // ✅ Only specific properties

  // ❌ WRONG: Entire object as dependency
  const wrongUserDisplay = useMemo(() => {
    console.log("Computing wrong user display (will run too often)");
    return `${user.name} (${user.age} years old)`;
  }, [user]); // ❌ Runs when ANY user property changes

  // 🎯 Array length as dependency
  const itemsInfo = useMemo(() => {
    console.log("Computing items info");
    return {
      count: items.length,
      isEmpty: items.length === 0,
      hasMany: items.length > 10,
    };
  }, [items.length]); // ✅ Only when length changes

  // 🎯 Computed dependencies
  const hasNotifications = settings.notifications;
  const isDarkTheme = settings.theme === "dark";

  const uiConfig = useMemo(() => {
    console.log("Computing UI config");
    return {
      showNotificationBadge: hasNotifications,
      darkMode: isDarkTheme,
      className: `theme-${settings.theme} ${
        hasNotifications ? "with-notifications" : ""
      }`,
      styles: {
        backgroundColor: isDarkTheme ? "#333" : "#fff",
        color: isDarkTheme ? "#fff" : "#333",
      },
    };
  }, [hasNotifications, isDarkTheme, settings.theme]); // ✅ Specific computed values

  // 🎯 Complex filtering with multiple dependencies
  const filteredItems = useMemo(() => {
    console.log("Filtering items");

    if (!filter) return items;

    return items.filter((item) => {
      const itemStr = item.toString();
      return itemStr.includes(filter);
    });
  }, [items, filter]); // ✅ Both items and filter

  // 🎯 Expensive calculation with conditional dependencies
  const expensiveCalculation = useMemo(() => {
    console.log("Performing expensive calculation");

    // Only calculate if we have items and user is adult
    if (items.length === 0 || user.age < 18) {
      return { result: 0, message: "No calculation needed" };
    }

    let result = 0;
    for (let i = 0; i < items.length * 1000; i++) {
      result += Math.random() * user.age;
    }

    return {
      result: result.toFixed(2),
      message: `Calculated for ${items.length} items and age ${user.age}`,
    };
  }, [items.length, user.age]); // ✅ Only the values we actually use

  // 🎯 Object with stable keys
  const stableObjectKeys = useMemo(() => Object.keys(user).sort(), [user]);

  const userMetadata = useMemo(() => {
    console.log("Computing user metadata");
    return {
      fieldCount: stableObjectKeys.length,
      hasEmail: stableObjectKeys.includes("email"),
      hasAge: stableObjectKeys.includes("age"),
      summary: `User has ${
        stableObjectKeys.length
      } fields: ${stableObjectKeys.join(", ")}`,
    };
  }, [stableObjectKeys]); // ✅ Depends on stable key array

  // 🎯 Functions in dependencies (should be memoized)
  const processItem = useCallback(
    (item) => {
      return {
        original: item,
        doubled: item * 2,
        userAgeMultiplied: item * user.age,
      };
    },
    [user.age]
  );

  const processedItems = useMemo(() => {
    console.log("Processing items with function");
    return filteredItems.map(processItem);
  }, [filteredItems, processItem]); // ✅ processItem is memoized

  const updateUser = (field, value) => {
    setUser((prev) => ({ ...prev, [field]: value }));
  };

  const updateSettings = (field, value) => {
    setSettings((prev) => ({ ...prev, [field]: value }));
  };

  const addItem = () => {
    setItems((prev) => [...prev, Math.max(...prev) + 1]);
  };

  const removeItem = () => {
    setItems((prev) => prev.slice(0, -1));
  };

  return (
    <div className="dependency-array-demo">
      <h3>Dependency Array Best Practices</h3>

      <div className="controls">
        <div className="control-section">
          <h4>User Controls</h4>
          <div className="control-group">
            <label>Name:</label>
            <input
              value={user.name}
              onChange={(e) => updateUser("name", e.target.value)}
            />
          </div>
          <div className="control-group">
            <label>Age:</label>
            <input
              type="number"
              value={user.age}
              onChange={(e) => updateUser("age", Number(e.target.value))}
            />
          </div>
          <div className="control-group">
            <label>Email:</label>
            <input
              value={user.email}
              onChange={(e) => updateUser("email", e.target.value)}
            />
          </div>
        </div>

        <div className="control-section">
          <h4>Settings Controls</h4>
          <div className="control-group">
            <label>Theme:</label>
            <select
              value={settings.theme}
              onChange={(e) => updateSettings("theme", e.target.value)}
            >
              <option value="light">Light</option>
              <option value="dark">Dark</option>
            </select>
          </div>
          <div className="control-group">
            <label>
              <input
                type="checkbox"
                checked={settings.notifications}
                onChange={(e) =>
                  updateSettings("notifications", e.target.checked)
                }
              />
              Notifications
            </label>
          </div>
        </div>

        <div className="control-section">
          <h4>Items Controls</h4>
          <div className="control-group">
            <button onClick={addItem}>Add Item</button>
            <button onClick={removeItem}>Remove Item</button>
            <span>Items: [{items.join(", ")}]</span>
          </div>
          <div className="control-group">
            <label>Filter:</label>
            <input
              value={filter}
              onChange={(e) => setFilter(e.target.value)}
              placeholder="Filter items"
            />
          </div>
        </div>
      </div>

      <div className="results">
        <div className="result-section">
          <h4>User Display Names</h4>
          <p>
            <strong>Optimized (name + age only):</strong> {userDisplayName}
          </p>
          <p>
            <strong>Unoptimized (entire user object):</strong>{" "}
            {wrongUserDisplay}
          </p>
          <p>
            <em>Check console - unoptimized version runs more often</em>
          </p>
        </div>

        <div className="result-section">
          <h4>Items Information</h4>
          <pre>{JSON.stringify(itemsInfo, null, 2)}</pre>
        </div>

        <div className="result-section">
          <h4>UI Configuration</h4>
          <div style={uiConfig.styles} className={uiConfig.className}>
            <p>Theme: {settings.theme}</p>
            <p>
              Notifications: {settings.notifications ? "Enabled" : "Disabled"}
            </p>
            <p>Dark mode: {uiConfig.darkMode ? "Yes" : "No"}</p>
          </div>
        </div>

        <div className="result-section">
          <h4>Filtered Items</h4>
          <p>Filter: "{filter}"</p>
          <p>Results: [{filteredItems.join(", ")}]</p>
        </div>

        <div className="result-section">
          <h4>Expensive Calculation</h4>
          <p>Result: {expensiveCalculation.result}</p>
          <p>Message: {expensiveCalculation.message}</p>
        </div>

        <div className="result-section">
          <h4>User Metadata</h4>
          <pre>{JSON.stringify(userMetadata, null, 2)}</pre>
        </div>

        <div className="result-section">
          <h4>Processed Items</h4>
          <div className="processed-items">
            {processedItems.map((item, index) => (
              <div key={index} className="processed-item">
                Original: {item.original}, Doubled: {item.doubled}, Age
                Multiplied: {item.userAgeMultiplied}
              </div>
            ))}
          </div>
        </div>
      </div>

      <div className="best-practices">
        <h4>Dependency Array Best Practices:</h4>
        <ul>
          <li>
            <strong>✅ Use specific properties:</strong> [user.name, user.age]
            instead of [user]
          </li>
          <li>
            <strong>✅ Use computed values:</strong> [items.length] instead of
            [items]
          </li>
          <li>
            <strong>✅ Memoize functions:</strong> Use useCallback for function
            dependencies
          </li>
          <li>
            <strong>✅ Stable references:</strong> Extract stable values outside
            useMemo
          </li>
          <li>
            <strong>❌ Avoid objects/arrays:</strong> Unless you need the entire
            reference
          </li>
          <li>
            <strong>❌ Don't omit dependencies:</strong> Include all values used
            inside
          </li>
        </ul>
      </div>
    </div>
  );
}

⚠️ Common Mistakes & Anti-Patterns

1. Premature Optimization

jsx
// ❌ WRONG: Memoizing everything unnecessarily
function OverMemoizedComponent() {
  const [count, setCount] = useState(0);

  // ❌ Unnecessary - simple calculation
  const doubledCount = useMemo(() => count * 2, [count]);

  // ❌ Unnecessary - primitive value
  const isEven = useMemo(() => count % 2 === 0, [count]);

  // ❌ Unnecessary - simple string
  const message = useMemo(() => `Count is ${count}`, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubledCount}</p>
      <p>Is Even: {isEven ? "Yes" : "No"}</p>
      <p>{message}</p>
    </div>
  );
}

// ✅ CORRECT: Only memoize when necessary
function OptimallyMemoizedComponent() {
  const [count, setCount] = useState(0);

  // ✅ Simple calculations - no memo needed
  const doubledCount = count * 2;
  const isEven = count % 2 === 0;
  const message = `Count is ${count}`;

  // ✅ Only memoize expensive operations
  const expensiveCalculation = useMemo(() => {
    let result = 0;
    for (let i = 0; i < count * 100000; i++) {
      result += Math.random();
    }
    return result;
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Doubled: {doubledCount}</p>
      <p>Is Even: {isEven ? "Yes" : "No"}</p>
      <p>{message}</p>
      <p>Expensive: {expensiveCalculation.toFixed(2)}</p>
    </div>
  );
}

2. Missing Dependencies

jsx
// ❌ WRONG: Missing dependencies
function MissingDependenciesExample() {
  const [count, setCount] = useState(0);
  const [multiplier, setMultiplier] = useState(2);

  // ❌ Missing 'multiplier' in dependencies
  const calculation = useMemo(() => {
    return count * multiplier; // Uses multiplier but not in deps
  }, [count]); // ❌ Stale closure!

  return (
    <div>
      <p>Count: {count}</p>
      <p>Multiplier: {multiplier}</p>
      <p>Result: {calculation}</p>
    </div>
  );
}

// ✅ CORRECT: All dependencies included
function CorrectDependenciesExample() {
  const [count, setCount] = useState(0);
  const [multiplier, setMultiplier] = useState(2);

  // ✅ All dependencies included
  const calculation = useMemo(() => {
    return count * multiplier;
  }, [count, multiplier]); // ✅ Complete dependency array

  return (
    <div>
      <p>Count: {count}</p>
      <p>Multiplier: {multiplier}</p>
      <p>Result: {calculation}</p>
    </div>
  );
}

3. Object/Array Dependencies

jsx
// ❌ WRONG: Using entire objects as dependencies
function WrongObjectDependencies() {
  const [user, setUser] = useState({
    name: "John",
    age: 30,
    email: "john@example.com",
  });

  // ❌ Will run whenever ANY user property changes
  const userDisplayName = useMemo(() => {
    return `${user.name} (${user.age})`; // Only uses name and age
  }, [user]); // ❌ Depends on entire user object

  return <p>{userDisplayName}</p>;
}

// ✅ CORRECT: Use specific properties
function CorrectObjectDependencies() {
  const [user, setUser] = useState({
    name: "John",
    age: 30,
    email: "john@example.com",
  });

  // ✅ Only runs when name or age changes
  const userDisplayName = useMemo(() => {
    return `${user.name} (${user.age})`;
  }, [user.name, user.age]); // ✅ Only specific properties

  return <p>{userDisplayName}</p>;
}

4. Expensive Dependency Calculations

jsx
// ❌ WRONG: Expensive calculations in dependency array
function ExpensiveDependencyCalculation() {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);

  // ❌ Expensive operation in dependency array
  const processedItems = useMemo(() => {
    return items.map((item) => item * 2);
  }, [items.map((item) => item.toString())]); // ❌ Creates new array every render!

  return (
    <div>
      {processedItems.map((item) => (
        <div key={item}>{item}</div>
      ))}
    </div>
  );
}

// ✅ CORRECT: Simple dependencies
function SimpleDependencyCalculation() {
  const [items, setItems] = useState([1, 2, 3, 4, 5]);

  // ✅ Simple dependency
  const processedItems = useMemo(() => {
    return items.map((item) => ({
      original: item,
      doubled: item * 2,
      stringified: item.toString(),
    }));
  }, [items]); // ✅ Simple array reference

  return (
    <div>
      {processedItems.map((item) => (
        <div key={item.original}>
          {item.original} → {item.doubled} ({item.stringified})
        </div>
      ))}
    </div>
  );
}

🧪 Mini Challenges

Challenge 1: Data Dashboard Optimization

Build a data dashboard that:

  • Processes large datasets efficiently
  • Calculates multiple statistics
  • Filters and sorts data
  • Only recalculates when necessary
💡 Solution
jsx
function DataDashboard() {
  const [rawData] = useState(
    Array.from({ length: 50000 }, (_, i) => ({
      id: i,
      value: Math.random() * 1000,
      category: ["A", "B", "C", "D"][Math.floor(Math.random() * 4)],
      date: new Date(
        2023,
        Math.floor(Math.random() * 12),
        Math.floor(Math.random() * 28) + 1
      ),
      status: ["active", "inactive", "pending"][Math.floor(Math.random() * 3)],
    }))
  );

  const [filters, setFilters] = useState({
    category: "all",
    status: "all",
    minValue: 0,
    maxValue: 1000,
    dateRange: "all",
  });

  const [sortConfig, setSortConfig] = useState({
    field: "id",
    direction: "asc",
  });

  // 🎯 Filtered data
  const filteredData = useMemo(() => {
    console.time("Data filtering");

    let result = rawData;

    if (filters.category !== "all") {
      result = result.filter((item) => item.category === filters.category);
    }

    if (filters.status !== "all") {
      result = result.filter((item) => item.status === filters.status);
    }

    result = result.filter(
      (item) => item.value >= filters.minValue && item.value <= filters.maxValue
    );

    if (filters.dateRange !== "all") {
      const now = new Date();
      const cutoff = new Date();

      switch (filters.dateRange) {
        case "last30":
          cutoff.setDate(now.getDate() - 30);
          break;
        case "last90":
          cutoff.setDate(now.getDate() - 90);
          break;
        case "thisYear":
          cutoff.setFullYear(now.getFullYear(), 0, 1);
          break;
      }

      result = result.filter((item) => item.date >= cutoff);
    }

    console.timeEnd("Data filtering");
    return result;
  }, [rawData, filters]);

  // 🎯 Sorted data
  const sortedData = useMemo(() => {
    console.time("Data sorting");

    const result = [...filteredData].sort((a, b) => {
      const aVal = a[sortConfig.field];
      const bVal = b[sortConfig.field];

      let comparison = 0;
      if (aVal > bVal) comparison = 1;
      if (aVal < bVal) comparison = -1;

      return sortConfig.direction === "desc" ? -comparison : comparison;
    });

    console.timeEnd("Data sorting");
    return result;
  }, [filteredData, sortConfig]);

  // 🎯 Statistics
  const statistics = useMemo(() => {
    console.time("Statistics calculation");

    if (filteredData.length === 0) {
      return {
        count: 0,
        sum: 0,
        average: 0,
        min: 0,
        max: 0,
        median: 0,
        categoryBreakdown: {},
        statusBreakdown: {},
      };
    }

    const values = filteredData.map((item) => item.value);
    const sum = values.reduce((acc, val) => acc + val, 0);
    const sortedValues = [...values].sort((a, b) => a - b);
    const median =
      sortedValues.length % 2 === 0
        ? (sortedValues[sortedValues.length / 2 - 1] +
            sortedValues[sortedValues.length / 2]) /
          2
        : sortedValues[Math.floor(sortedValues.length / 2)];

    const categoryBreakdown = filteredData.reduce((acc, item) => {
      acc[item.category] = (acc[item.category] || 0) + 1;
      return acc;
    }, {});

    const statusBreakdown = filteredData.reduce((acc, item) => {
      acc[item.status] = (acc[item.status] || 0) + 1;
      return acc;
    }, {});

    console.timeEnd("Statistics calculation");

    return {
      count: filteredData.length,
      sum: sum.toFixed(2),
      average: (sum / filteredData.length).toFixed(2),
      min: Math.min(...values).toFixed(2),
      max: Math.max(...values).toFixed(2),
      median: median.toFixed(2),
      categoryBreakdown,
      statusBreakdown,
    };
  }, [filteredData]);

  // 🎯 Chart data
  const chartData = useMemo(() => {
    console.time("Chart data preparation");

    const categoryData = Object.entries(statistics.categoryBreakdown).map(
      ([category, count]) => ({
        category,
        count,
        percentage: ((count / statistics.count) * 100).toFixed(1),
      })
    );

    const statusData = Object.entries(statistics.statusBreakdown).map(
      ([status, count]) => ({
        status,
        count,
        percentage: ((count / statistics.count) * 100).toFixed(1),
      })
    );

    console.timeEnd("Chart data preparation");

    return { categoryData, statusData };
  }, [statistics]);

  const updateFilter = (key, value) => {
    setFilters((prev) => ({ ...prev, [key]: value }));
  };

  const updateSort = (field) => {
    setSortConfig((prev) => ({
      field,
      direction:
        prev.field === field && prev.direction === "asc" ? "desc" : "asc",
    }));
  };

  return (
    <div className="data-dashboard">
      <h3>Optimized Data Dashboard</h3>

      {/* Filters */}
      <div className="filters">
        <div className="filter-group">
          <label>Category:</label>
          <select
            value={filters.category}
            onChange={(e) => updateFilter("category", e.target.value)}
          >
            <option value="all">All</option>
            <option value="A">A</option>
            <option value="B">B</option>
            <option value="C">C</option>
            <option value="D">D</option>
          </select>
        </div>

        <div className="filter-group">
          <label>Status:</label>
          <select
            value={filters.status}
            onChange={(e) => updateFilter("status", e.target.value)}
          >
            <option value="all">All</option>
            <option value="active">Active</option>
            <option value="inactive">Inactive</option>
            <option value="pending">Pending</option>
          </select>
        </div>

        <div className="filter-group">
          <label>Value Range:</label>
          <input
            type="range"
            min="0"
            max="1000"
            value={filters.minValue}
            onChange={(e) => updateFilter("minValue", Number(e.target.value))}
          />
          <span>
            {filters.minValue} - {filters.maxValue}
          </span>
          <input
            type="range"
            min="0"
            max="1000"
            value={filters.maxValue}
            onChange={(e) => updateFilter("maxValue", Number(e.target.value))}
          />
        </div>
      </div>

      {/* Statistics */}
      <div className="statistics">
        <h4>Statistics</h4>
        <div className="stats-grid">
          <div className="stat">Count: {statistics.count}</div>
          <div className="stat">Sum: {statistics.sum}</div>
          <div className="stat">Average: {statistics.average}</div>
          <div className="stat">Min: {statistics.min}</div>
          <div className="stat">Max: {statistics.max}</div>
          <div className="stat">Median: {statistics.median}</div>
        </div>
      </div>

      {/* Charts */}
      <div className="charts">
        <div className="chart">
          <h5>Category Breakdown</h5>
          {chartData.categoryData.map((item) => (
            <div key={item.category} className="chart-bar">
              {item.category}: {item.count} ({item.percentage}%)
            </div>
          ))}
        </div>

        <div className="chart">
          <h5>Status Breakdown</h5>
          {chartData.statusData.map((item) => (
            <div key={item.status} className="chart-bar">
              {item.status}: {item.count} ({item.percentage}%)
            </div>
          ))}
        </div>
      </div>

      {/* Data Table */}
      <div className="data-table">
        <h4>Data (First 100 rows)</h4>
        <table>
          <thead>
            <tr>
              <th onClick={() => updateSort("id")}>ID</th>
              <th onClick={() => updateSort("value")}>Value</th>
              <th onClick={() => updateSort("category")}>Category</th>
              <th onClick={() => updateSort("status")}>Status</th>
              <th onClick={() => updateSort("date")}>Date</th>
            </tr>
          </thead>
          <tbody>
            {sortedData.slice(0, 100).map((item) => (
              <tr key={item.id}>
                <td>{item.id}</td>
                <td>{item.value.toFixed(2)}</td>
                <td>{item.category}</td>
                <td>{item.status}</td>
                <td>{item.date.toLocaleDateString()}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

Challenge 2: Smart Shopping Cart

Create a shopping cart that:

  • Calculates totals, taxes, and discounts efficiently
  • Handles complex pricing rules
  • Updates only when necessary
  • Provides real-time validation
💡 Solution
jsx
function SmartShoppingCart() {
  const [items, setItems] = useState([
    {
      id: 1,
      name: "Laptop",
      price: 999.99,
      quantity: 1,
      category: "electronics",
      taxable: true,
    },
    {
      id: 2,
      name: "Book",
      price: 29.99,
      quantity: 2,
      category: "books",
      taxable: false,
    },
    {
      id: 3,
      name: "Headphones",
      price: 199.99,
      quantity: 1,
      category: "electronics",
      taxable: true,
    },
  ]);

  const [discountCode, setDiscountCode] = useState("");
  const [shippingMethod, setShippingMethod] = useState("standard");
  const [customerType, setCustomerType] = useState("regular");

  // 🎯 Discount rules
  const discountRules = useMemo(
    () => ({
      SAVE10: { type: "percentage", value: 0.1, minAmount: 50 },
      ELECTRONICS20: {
        type: "percentage",
        value: 0.2,
        category: "electronics",
      },
      FREESHIP: { type: "shipping", value: 0 },
      STUDENT15: { type: "percentage", value: 0.15, customerType: "student" },
    }),
    []
  );

  // 🎯 Shipping rates
  const shippingRates = useMemo(
    () => ({
      standard: 9.99,
      express: 19.99,
      overnight: 39.99,
      free: 0,
    }),
    []
  );

  // 🎯 Tax rates by category
  const taxRates = useMemo(
    () => ({
      electronics: 0.08,
      books: 0,
      clothing: 0.06,
      food: 0.03,
    }),
    []
  );

  // 🎯 Calculate subtotal
  const subtotal = useMemo(() => {
    console.log("Calculating subtotal");
    return items.reduce((total, item) => total + item.price * item.quantity, 0);
  }, [items]);

  // 🎯 Calculate item-level discounts
  const itemDiscounts = useMemo(() => {
    console.log("Calculating item discounts");

    const discount = discountRules[discountCode];
    if (!discount) return {};

    const discounts = {};

    items.forEach((item) => {
      let itemDiscount = 0;

      if (discount.type === "percentage") {
        // Category-specific discount
        if (discount.category && item.category === discount.category) {
          itemDiscount = item.price * item.quantity * discount.value;
        }
        // Customer type discount
        else if (
          discount.customerType &&
          customerType === discount.customerType
        ) {
          itemDiscount = item.price * item.quantity * discount.value;
        }
        // General percentage discount
        else if (!discount.category && !discount.customerType) {
          itemDiscount = item.price * item.quantity * discount.value;
        }
      }

      if (itemDiscount > 0) {
        discounts[item.id] = itemDiscount;
      }
    });

    return discounts;
  }, [items, discountCode, customerType, discountRules]);

  // 🎯 Calculate total discount
  const totalDiscount = useMemo(() => {
    console.log("Calculating total discount");

    const itemDiscountTotal = Object.values(itemDiscounts).reduce(
      (sum, discount) => sum + discount,
      0
    );
    const discount = discountRules[discountCode];

    // Check minimum amount requirement
    if (discount && discount.minAmount && subtotal < discount.minAmount) {
      return 0;
    }

    return itemDiscountTotal;
  }, [itemDiscounts, discountRules, discountCode, subtotal]);

  // 🎯 Calculate taxes
  const taxes = useMemo(() => {
    console.log("Calculating taxes");

    const discountedSubtotal = subtotal - totalDiscount;

    return items.reduce((totalTax, item) => {
      if (!item.taxable) return totalTax;

      const taxRate = taxRates[item.category] || 0;
      const itemTotal = item.price * item.quantity;
      const itemDiscountRatio = (itemDiscounts[item.id] || 0) / itemTotal;
      const discountedItemTotal = itemTotal * (1 - itemDiscountRatio);

      return totalTax + discountedItemTotal * taxRate;
    }, 0);
  }, [items, subtotal, totalDiscount, itemDiscounts, taxRates]);

  // 🎯 Calculate shipping
  const shipping = useMemo(() => {
    console.log("Calculating shipping");

    const discount = discountRules[discountCode];
    if (discount && discount.type === "shipping") {
      return discount.value;
    }

    // Free shipping for orders over $100
    if (subtotal - totalDiscount > 100) {
      return 0;
    }

    return shippingRates[shippingMethod] || 0;
  }, [
    discountCode,
    discountRules,
    subtotal,
    totalDiscount,
    shippingMethod,
    shippingRates,
  ]);

  // 🎯 Calculate final total
  const total = useMemo(() => {
    console.log("Calculating final total");
    return subtotal - totalDiscount + taxes + shipping;
  }, [subtotal, totalDiscount, taxes, shipping]);

  // 🎯 Validation messages
  const validationMessages = useMemo(() => {
    console.log("Calculating validation messages");

    const messages = [];
    const discount = discountRules[discountCode];

    if (discountCode && !discount) {
      messages.push({ type: "error", text: "Invalid discount code" });
    }

    if (discount && discount.minAmount && subtotal < discount.minAmount) {
      messages.push({
        type: "warning",
        text: `Add $${(discount.minAmount - subtotal).toFixed(
          2
        )} more to qualify for discount`,
      });
    }

    if (
      discount &&
      discount.customerType &&
      customerType !== discount.customerType
    ) {
      messages.push({
        type: "error",
        text: `This discount is only for ${discount.customerType} customers`,
      });
    }

    if (subtotal - totalDiscount > 100 && shipping > 0) {
      messages.push({
        type: "info",
        text: "You qualify for free shipping!",
      });
    }

    return messages;
  }, [
    discountCode,
    discountRules,
    subtotal,
    totalDiscount,
    customerType,
    shipping,
  ]);

  const updateQuantity = (id, quantity) => {
    setItems((prev) =>
      prev.map((item) =>
        item.id === id ? { ...item, quantity: Math.max(0, quantity) } : item
      )
    );
  };

  const removeItem = (id) => {
    setItems((prev) => prev.filter((item) => item.id !== id));
  };

  return (
    <div className="smart-shopping-cart">
      <h3>Smart Shopping Cart</h3>

      {/* Controls */}
      <div className="controls">
        <div className="control-group">
          <label>Customer Type:</label>
          <select
            value={customerType}
            onChange={(e) => setCustomerType(e.target.value)}
          >
            <option value="regular">Regular</option>
            <option value="student">Student</option>
            <option value="premium">Premium</option>
          </select>
        </div>

        <div className="control-group">
          <label>Discount Code:</label>
          <input
            value={discountCode}
            onChange={(e) => setDiscountCode(e.target.value.toUpperCase())}
            placeholder="Enter discount code"
          />
        </div>

        <div className="control-group">
          <label>Shipping:</label>
          <select
            value={shippingMethod}
            onChange={(e) => setShippingMethod(e.target.value)}
          >
            <option value="standard">Standard ($9.99)</option>
            <option value="express">Express ($19.99)</option>
            <option value="overnight">Overnight ($39.99)</option>
          </select>
        </div>
      </div>

      {/* Validation Messages */}
      {validationMessages.length > 0 && (
        <div className="validation-messages">
          {validationMessages.map((message, index) => (
            <div key={index} className={`message ${message.type}`}>
              {message.text}
            </div>
          ))}
        </div>
      )}

      {/* Cart Items */}
      <div className="cart-items">
        <h4>Cart Items</h4>
        {items.map((item) => (
          <div key={item.id} className="cart-item">
            <div className="item-info">
              <h5>{item.name}</h5>
              <p>Category: {item.category}</p>
              <p>Price: ${item.price}</p>
              <p>Taxable: {item.taxable ? "Yes" : "No"}</p>
            </div>

            <div className="item-controls">
              <button
                onClick={() => updateQuantity(item.id, item.quantity - 1)}
              >
                -
              </button>
              <span>Qty: {item.quantity}</span>
              <button
                onClick={() => updateQuantity(item.id, item.quantity + 1)}
              >
                +
              </button>
              <button onClick={() => removeItem(item.id)}>Remove</button>
            </div>

            <div className="item-totals">
              <p>Subtotal: ${(item.price * item.quantity).toFixed(2)}</p>
              {itemDiscounts[item.id] && (
                <p>Discount: -${itemDiscounts[item.id].toFixed(2)}</p>
              )}
            </div>
          </div>
        ))}
      </div>

      {/* Cart Summary */}
      <div className="cart-summary">
        <h4>Order Summary</h4>
        <div className="summary-line">
          <span>Subtotal:</span>
          <span>${subtotal.toFixed(2)}</span>
        </div>
        {totalDiscount > 0 && (
          <div className="summary-line discount">
            <span>Discount ({discountCode}):</span>
            <span>-${totalDiscount.toFixed(2)}</span>
          </div>
        )}
        <div className="summary-line">
          <span>Taxes:</span>
          <span>${taxes.toFixed(2)}</span>
        </div>
        <div className="summary-line">
          <span>Shipping:</span>
          <span>${shipping.toFixed(2)}</span>
        </div>
        <div className="summary-line total">
          <span>
            <strong>Total:</strong>
          </span>
          <span>
            <strong>${total.toFixed(2)}</strong>
          </span>
        </div>
      </div>
    </div>
  );
}

📊 Performance Measurement

Measuring useMemo Impact

jsx
function PerformanceMeasurement() {
  const [dataSize, setDataSize] = useState(10000);
  const [useOptimization, setUseOptimization] = useState(true);
  const [triggerUpdate, setTriggerUpdate] = useState(0);

  // Generate test data
  const testData = useMemo(() => {
    console.log("Generating test data");
    return Array.from({ length: dataSize }, (_, i) => ({
      id: i,
      value: Math.random() * 100,
      category: ["A", "B", "C"][Math.floor(Math.random() * 3)],
    }));
  }, [dataSize]);

  // 🎯 Performance timing hook
  const usePerformanceTimer = (label) => {
    const startTimeRef = useRef(null);
    const [duration, setDuration] = useState(0);

    const start = useCallback(() => {
      startTimeRef.current = performance.now();
    }, []);

    const end = useCallback(() => {
      if (startTimeRef.current) {
        const duration = performance.now() - startTimeRef.current;
        setDuration(duration);
        console.log(`${label}: ${duration.toFixed(2)}ms`);
      }
    }, [label]);

    return { start, end, duration };
  };

  const optimizedTimer = usePerformanceTimer("Optimized calculation");
  const unoptimizedTimer = usePerformanceTimer("Unoptimized calculation");

  // 🎯 Expensive calculation with optimization
  const optimizedResult = useMemo(() => {
    if (!useOptimization) return null;

    optimizedTimer.start();

    const result = testData.reduce(
      (acc, item) => {
        acc.total += item.value;
        acc.count += 1;
        acc.categories[item.category] =
          (acc.categories[item.category] || 0) + 1;

        if (item.value > acc.max) acc.max = item.value;
        if (item.value < acc.min) acc.min = item.value;

        return acc;
      },
      {
        total: 0,
        count: 0,
        categories: {},
        max: -Infinity,
        min: Infinity,
      }
    );

    result.average = result.total / result.count;

    optimizedTimer.end();
    return result;
  }, [testData, useOptimization, optimizedTimer]);

  // 🎯 Expensive calculation without optimization
  const calculateUnoptimized = () => {
    if (useOptimization) return null;

    unoptimizedTimer.start();

    const result = testData.reduce(
      (acc, item) => {
        acc.total += item.value;
        acc.count += 1;
        acc.categories[item.category] =
          (acc.categories[item.category] || 0) + 1;

        if (item.value > acc.max) acc.max = item.value;
        if (item.value < acc.min) acc.min = item.value;

        return acc;
      },
      {
        total: 0,
        count: 0,
        categories: {},
        max: -Infinity,
        min: Infinity,
      }
    );

    result.average = result.total / result.count;

    unoptimizedTimer.end();
    return result;
  };

  const unoptimizedResult = !useOptimization ? calculateUnoptimized() : null;
  const currentResult = useOptimization ? optimizedResult : unoptimizedResult;

  const renderCount = useRef(0);
  renderCount.current += 1;

  return (
    <div className="performance-measurement">
      <h3>Performance Measurement Demo</h3>

      <div className="controls">
        <div className="control-group">
          <label>Data Size:</label>
          <select
            value={dataSize}
            onChange={(e) => setDataSize(Number(e.target.value))}
          >
            <option value={1000}>1,000 items</option>
            <option value={10000}>10,000 items</option>
            <option value={50000}>50,000 items</option>
            <option value={100000}>100,000 items</option>
          </select>
        </div>

        <div className="control-group">
          <label>
            <input
              type="checkbox"
              checked={useOptimization}
              onChange={(e) => setUseOptimization(e.target.checked)}
            />
            Use useMemo Optimization
          </label>
        </div>

        <div className="control-group">
          <button onClick={() => setTriggerUpdate((prev) => prev + 1)}>
            Force Re-render (Trigger: {triggerUpdate})
          </button>
        </div>
      </div>

      <div className="performance-stats">
        <h4>Performance Statistics</h4>
        <div className="stats-grid">
          <div className="stat">
            <strong>Render Count:</strong> {renderCount.current}
          </div>
          <div className="stat">
            <strong>Data Size:</strong> {dataSize.toLocaleString()} items
          </div>
          <div className="stat">
            <strong>Optimization:</strong>{" "}
            {useOptimization ? "Enabled" : "Disabled"}
          </div>
          <div className="stat">
            <strong>Last Calculation Time:</strong>
            {useOptimization
              ? `${optimizedTimer.duration.toFixed(2)}ms`
              : `${unoptimizedTimer.duration.toFixed(2)}ms`}
          </div>
        </div>
      </div>

      {currentResult && (
        <div className="calculation-results">
          <h4>Calculation Results</h4>
          <div className="results-grid">
            <div className="result">
              <strong>Total:</strong> {currentResult.total.toFixed(2)}
            </div>
            <div className="result">
              <strong>Count:</strong> {currentResult.count}
            </div>
            <div className="result">
              <strong>Average:</strong> {currentResult.average.toFixed(2)}
            </div>
            <div className="result">
              <strong>Min:</strong> {currentResult.min.toFixed(2)}
            </div>
            <div className="result">
              <strong>Max:</strong> {currentResult.max.toFixed(2)}
            </div>
          </div>

          <div className="category-breakdown">
            <h5>Category Breakdown:</h5>
            {Object.entries(currentResult.categories).map(
              ([category, count]) => (
                <div key={category} className="category-stat">
                  {category}: {count} items
                </div>
              )
            )}
          </div>
        </div>
      )}

      <div className="performance-notes">
        <h4>Performance Notes:</h4>
        <ul>
          <li>
            <strong>With useMemo:</strong> Calculation only runs when data
            changes
          </li>
          <li>
            <strong>Without useMemo:</strong> Calculation runs on every render
          </li>
          <li>
            <strong>Force re-render:</strong> Test how optimization affects
            performance
          </li>
          <li>
            <strong>Data size:</strong> Larger datasets show bigger performance
            differences
          </li>
        </ul>
      </div>
    </div>
  );
}

🎯 When and Why: useMemo Decision Framework

Quick Decision Tree

🤔 Should I use useMemo?

├── Is this an expensive calculation?
│   ├── Yes → Continue evaluation...
│   └── No → Don't use useMemo ❌
│
├── Does the calculation run on every render?
│   ├── Yes → Continue evaluation...
│   └── No → Don't use useMemo ❌
│
├── Do the inputs change frequently?
│   ├── Yes → useMemo might not help ⚠️
│   └── No → Continue evaluation...
│
├── Is this for reference stability?
│   ├── Yes → Use useMemo ✅
│   └── No → Continue evaluation...
│
├── Is this preventing child re-renders?
│   ├── Yes → Use useMemo ✅
│   └── No → Continue evaluation...
│
└── Is the calculation actually expensive?
    ├── Yes → Use useMemo ✅
    └── No → Don't use useMemo ❌

Performance Guidelines

jsx
// 🎯 When TO use useMemo:

// ✅ Expensive calculations
const expensiveValue = useMemo(() => {
  return heavyCalculation(data);
}, [data]);

// ✅ Object/array reference stability
const stableConfig = useMemo(
  () => ({
    theme: "dark",
    features: ["a", "b", "c"],
  }),
  []
);

// ✅ Preventing child re-renders
const memoizedProps = useMemo(
  () => ({
    data: processedData,
    config: settings,
  }),
  [processedData, settings]
);

// ✅ Complex filtering/sorting
const filteredData = useMemo(() => {
  return data
    .filter((item) => item.active)
    .sort((a, b) => a.name.localeCompare(b.name));
}, [data]);

// 🎯 When NOT to use useMemo:

// ❌ Simple calculations
const doubled = count * 2; // Don't memo

// ❌ Primitive values
const isEven = count % 2 === 0; // Don't memo

// ❌ Always changing dependencies
const timestamp = useMemo(() => Date.now(), [Date.now()]); // Useless

// ❌ More expensive than the calculation
const simple = useMemo(() => a + b, [a, b]); // Overhead > benefit

🎤 Interview Insights

Common Interview Questions

  1. "When would you use useMemo?"

    • Expensive calculations that don't need to run on every render
    • Object/array reference stability for child components
    • Complex data transformations (filtering, sorting, grouping)
    • Preventing unnecessary re-renders in React.memo components
  2. "What's the difference between useMemo and useCallback?"

    • useMemo: Memoizes the result of a calculation
    • useCallback: Memoizes the function itself
    • Both prevent unnecessary re-computations
    • Show examples of when to use each
  3. "How do you measure if useMemo is actually helping?"

    • Use React DevTools Profiler
    • Console.time() for manual timing
    • Performance.now() for precise measurements
    • Compare render times with/without memoization
  4. "What are the downsides of useMemo?"

    • Memory overhead (storing cached values)
    • Comparison overhead (checking dependencies)
    • Can make code more complex
    • Premature optimization can hurt performance

Code Review Red Flags

jsx
// 🚨 Red Flags in Interviews:

// ❌ Memoizing everything
const simple = useMemo(() => a + b, [a, b]);

// ❌ Missing dependencies
const calc = useMemo(() => a * b * c, [a, b]); // Missing 'c'

// ❌ Expensive dependencies
const result = useMemo(() => process(data), [data.map((x) => x.id)]);

// ❌ Object dependencies
const result = useMemo(() => process(user.name), [user]); // Use user.name

// ❌ Always changing dependencies
const result = useMemo(() => calculate(), [Math.random()]);

🎯 Key Takeaways

Mental Model

jsx
// 🧠 Think of useMemo as:

// "A cache that stores the result of expensive calculations
//  and only recalculates when dependencies change"

const memoizedValue = useMemo(() => {
  // This only runs when dependencies change
  return expensiveCalculation(dependencies);
}, [dependencies]);

Best Practices Summary

  1. Profile first - Measure before optimizing
  2. Use for expensive operations - Not simple calculations
  3. Stabilize references - For objects/arrays passed to children
  4. Specific dependencies - Use primitive values when possible
  5. Don't over-memoize - It has overhead too
  6. Consider alternatives - Sometimes restructuring is better
  7. Test performance impact - Verify it actually helps

Production Tips

  • Start without useMemo - Add only when needed
  • Use React DevTools Profiler - Identify actual bottlenecks
  • Consider component splitting - Sometimes better than memoization
  • Watch bundle size - Don't memoize everything
  • Document expensive operations - Help future developers understand

Next up: useCallback: Function Memoization - Master function memoization and prevent unnecessary re-renders.

Previous: useRef: Refs vs Variables


💡 Pro tip: In interviews, always explain the trade-offs. Show that you understand useMemo has overhead and should only be used when the benefits outweigh the costs. Demonstrate performance measurement techniques.

PrevioususeEffect: Dependencies & Cleanup MasteryNextuseCallback: Function Memoization

Open source, free forever. Built by iammhador.

Contribute on GitHub