React.memo, useMemo & useCallback
📖 Concept
By default, when a parent component re-renders, all of its children also re-render. In a small app, this is fine. In a large app, it can lead to lag.
1. React.memo: A Higher-Order Component that skips re-rendering a component if its props haven't changed. It uses "shallow comparison".
2. useMemo: Memoizes the result of a calculation. Use it when you have an expensive operation (like filtering a large list) that you only want to re-run when specific inputs change.
3. useCallback:
Memoizes a function instance. In JavaScript, functions are objects, and every time a component re-renders, it creates a "new" version of its functions. Passing these new functions to a memoized child will break React.memo. useCallback prevents this by returning the same function instance across renders.
The Golden Rule: Don't optimize prematurely! Memoization adds its own overhead. Use it only when you have a measurable performance problem.
💻 Code Example
1import React, { useState, useMemo, useCallback } from 'react';23// 1. Memoized Child Component4const HeavyChild = React.memo(({ onAction, items }) => {5 console.log('HeavyChild re-rendering');6 return (7 <div className="p-4 border mt-4">8 <p>I only re-render if my props change!</p>9 <button onClick={onAction} className="bg-blue-500 text-white p-2">Click Me</button>10 <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>11 </div>12 );13});1415function Parent() {16 const [count, setCount] = useState(0);17 const [items] = useState(['Apple', 'Banana', 'Cherry']);1819 // 2. useCallback: Prevents creating a new function on every render20 const handleAction = useCallback(() => {21 alert('Action triggered from child!');22 }, []); // Empty deps = always the same function2324 // 3. useMemo: Only re-sort the list if 'items' changes25 const sortedItems = useMemo(() => {26 console.log('Sorting items...');27 return [...items].sort();28 }, [items]);2930 return (31 <div className="p-10">32 <h2 className="text-2xl font-bold">Performance Demo</h2>33 <p>Parent Count: {count}</p>34 <button35 onClick={() => setCount(c => c + 1)}36 className="bg-gray-200 p-2"37 >38 Increment Parent (Child won't re-render)39 </button>4041 <HeavyChild onAction={handleAction} items={sortedItems} />42 </div>43 );44}
🏋️ Practice Exercise
- Build a list of 500 items and a search filter. Use
useMemoto optimize the filtering. - Create a child component and observe it re-rendering using
console.log. Wrap it inReact.memoand see what stops it. - Pass a function from a parent to a memoized child. Notice the child re-renders even with
React.memo. Fix it usinguseCallback. - Use the 'Why Did You Render' (wdyr) library to identify components that are re-rendering unnecessarily.
⚠️ Common Mistakes
Wrapping every single function and calculation in useMemo/useCallback (this is often slower due to overhead).
Forgetting that
React.memoonly does a shallow comparison (it won't detect changes inside objects or arrays).Not including all required dependencies in the dependency array.
Using memoization for simple values that are cheap to calculate.
💼 Interview Questions
🎤 Mock Interview
Practice a live interview for React.memo, useMemo & useCallback