React: Performance Optimization: useMemo and useCallback

Last updated: 2026-10-26

1. What You'll Learn



2. The Story of a Search Filter Page

The Relationship Among the Three Components of Performance Optimization

100%
flowchart TD
    A[Parent Component Rendering] --> B{Props It's changed?}
    B -->|Yes| C[React.memo Intercept?]
    B -->|No| D[Skip subcomponents ✅]
    C -->|A rough comparison is roughly equal| D
    C -->|Shallow Comparison Inequality| E{Function/The object is new.?}
    E -->|Yes| F[useCallback/useMemo]
    F --> G[Stable Citation → memo Effective ✅]
    E -->|No| D
    
    style F fill:#e8f5e9,stroke:#2e7d32
    style D fill:#c8e6c9,stroke:#2e7d32
    style G fill:#c8e6c9,stroke:#2e7d32

(1) Pain Point: Recalculation with Every Input

Alice created a product search page with 10,000 products to filter through:

JSX
function ShopPage() {
  const [products] = useState(generate10000Products())
  const [search, setSearch] = useState('')
  const [sortBy, setSortBy] = useState('name')

  // ❌ Question:Re-filter after each input 10,000 Items
  const filtered = products
    .filter(p => p.name.includes(search))
    .sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1)

  return (
    <div>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">By Name</option>
        <option value="price">By Price</option>
      </select>
      <ProductList products={filtered} />
    </div>
  )
}
▶ Try it Yourself

Issue: Every time the keyboard input (setSearch) is entered, it triggers filter and sort to run again—10,000 records have to be filtered each time, causing the interface to lag.

(2) The useMemo Solution

JSX
function ShopPage() {
  const [products] = useState(generate10000Products())
  const [search, setSearch] = useState('')
  const [sortBy, setSortBy] = useState('name')

  // ✅ useMemo:Only at search or  sortBy Recalculate only when there is a change
  const filtered = useMemo(() => {
    console.log('Refilter...(Only when the search terms or sorting criteria change)')
    return products
      .filter(p => p.name.includes(search))
      .sort((a, b) => a[sortBy] > b[sortBy] ? 1 : -1)
  }, [search, sortBy, products])

  return (
    <div>
      <input value={search} onChange={e => setSearch(e.target.value)} />
      <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
        <option value="name">By Name</option>
        <option value="price">By Price</option>
      </select>
      <ProductList products={filtered} />
    </div>
  )
}
▶ Try it Yourself

Benefits: Filtering is only re-run when the input changes to either search or sortBy; other state changes (such as the parent component being re-rendered) do not trigger filtering, resulting in a 10- to 100-fold performance improvement.



3. useMemo: Caching Calculation Results

Hook What to remember Return value When dependencies change
useMemo Calculation Result Cached Value Recalculate
useCallback Function References Cached Functions Creating New Functions
React.memo Rendered result of the entire component Skip re-rendering Re-render when props change

(1) Basic Syntax

JSX
const memoizedValue = useMemo(() => {
  return Costly Computations()
}, [Dependency1, Dependency2])
▶ Try it Yourself
Section Description
() => Computation A function that needs to be cached and returns the result of the calculation
[Dependencies] Recalculate when dependencies change
memoizedValue Cached results (returns the previous result directly if dependencies haven't changed)

(2) Applicable Scenarios

JSX
// Scene 1:Data Conversion(Filter、Sort、Grouping)
const activeUsers = useMemo(
  () => users.filter(u => u.isActive).sort((a, b) => a.name.localeCompare(b.name)),
  [users]
)

// Scene 2:Format(Numbers、Date、String Concatenation)
const formattedPrice = useMemo(
  () => new Intl.NumberFormat('zh-CN', { style: 'currency', currency: 'CNY' }).format(price),
  [price]
)

// Scene 3:Derived State(Calculating New Data from Existing Data)
const statistics = useMemo(() => ({
  total: orders.length,
  paid: orders.filter(o => o.status === 'paid').length,
  amount: orders.reduce((sum, o) => sum + o.amount, 0),
  avgAmount: orders.length ? orders.reduce((sum, o) => sum + o.amount, 0) / orders.length : 0
}), [orders])
▶ Try it Yourself

Output:

TEXT
...

▶ Example: Performance Comparison

JSX
// ============================================
// Example:useMemo vs no useMemo Performance Comparison
// ============================================

function ExpensiveCalc({ num, unrelated }) {
  // ❌ None useMemo:Executed on every render
  const slowResult1 = slowFibonacci(num)

  // ✅ With useMemo: num If it hasn't changed, don't recalculate it.
  const slowResult2 = useMemo(() => slowFibonacci(num), [num])

  return (
    <div>
      <p>num={num} The Fibonacci sequence</p>
      <p>Without useMemo: {slowResult1}(Rendering calculations each time)</p>
      <p>With useMemo: {slowResult2}(Only at num Calculate when changes occur)</p>
      <p>Unrelated Status:{unrelated}(Click the button to change this value)</p>
    </div>
  )
}

function slowFibonacci(n) {
  if (n <= 1) return n
  let a = 0, b = 1, c
  for (let i = 2; i <= n; i++) { c = a + b; a = b; b = c }
  console.log('fibonacci Calculation!n=' + n)
  return b
}
// Click to Make a Difference unrelated → without useMemo recalculates,with useMemo skips
▶ Try it Yourself

Output:

TEXT
Fibonacci(n): without useMemo recalculates every render, with useMemo skips when only unrelated state changes. Console: "fibonacci calculated! n=X"


4. useCallback: Remembering Function References

(1) Basic Syntax

JSX
const memoizedFn = useCallback(() => {
  doSomething(a, b)
}, [a, b])
▶ Try it Yourself

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps) — what is stored is the function itself, not the result of the function.

(2) Why is useCallback needed?

JSX
function Parent() {
  const [count, setCount] = useState(0)

  // ❌ Every time Parent Rendering,handleClick These are all new functions
  // resulting in Child 's  React.memo Failure
  const handleClick = () => {
    console.log('Clicked')
  }

  // ✅ useCallback:handleClick Citation unchanged
  // Child 's  React.memo Works properly(Skip Re-rendering)
  const handleClick = useCallback(() => {
    console.log('Clicked')
  }, [])  // Empty Dependency:This function will never change.
}
▶ Try it Yourself

(3) React.memo: Preventing Child Components from Rendering Again

▶ Try it Yourself

5. When to Use It, and When Not to Use It

(1) When to Use It

Scenario Hook Reason
Big Data Filtering/Sorting useMemo Avoid traversing large amounts of data on every render
Complex Calculations (Math/Formatting) useMemo No need to recalculate if the result remains the same
Passing props to a React.memo child component useCallback Keeping function references stable so that React.memo works
Functions in useEffect Dependencies useCallback Preventing useEffect from Running Multiple Times Due to Changes in Function References

(2) Situations Where It Is Not Needed

JSX
// ❌ Not necessary:Simple Calculations
const doubled = useMemo(() => count * 2, [count])
// ✅ Write it directly:count * 2 It's already pretty fast as it is
const doubled = count * 2

// ❌ Not necessary:Handle events without passing them to child components
const handleClick = useCallback(() => {
  console.log('Click')
}, [])
// ✅ Just write it down.
const handleClick = () => console.log('Click')

// ❌ Not necessary:The child component isn't being used. React.memo
// useCallback It's been packaged, but no one is buying it."Stable Citation"
▶ Try it Yourself

(3) The Cost of Abuse

JSX
// useMemo and  useCallback It involves expenses(Relies heavily on arrays)
// Abuse = Introducing Additional Comparison Logic,Slower than if it weren't optimized

// Rule of thumb:
// 1. First, let's write the version without optimization.
// 2. Feeling some lag(fps < 60)Use again
// 3. use  React DevTools Profiler Measurement
▶ Try it Yourself

6. Complete Example: Message List in a Chat App

▶ Expand runnable example (112 lines)
▶ Try it Yourself

Output:

TEXT
... ('fibonacci Calculation!n=' + n)

Expected Output: A chat list with 100 messages. When you click "Increase Unread Count," the message list will not re-render (no output in the console) due to useCallback and React.memo; only the count will change.


▶ Example 2: useCallback prevents useEffect from running multiple times

▶ Try it Yourself

Output:

TEXT
Search box + pagination. Type "React" → 3 results ("Results React - Page 1", etc.). useCallback prevents infinite useEffect loop. Prev/Next buttons.

Output:

TEXT
Subheading: "🔍 Search(useCallback Demo)". Displays: "🔍 Search(useCallback Demo)". State: query (setter: setQuery), results (setter: setResults), page (setter: setPage). Button: setPage(p => p + 1)}>Next Page. Input: Enter a search term.... List: {r.title}. useEffect manages side effects. useCallback memoizes handler. Async data fetching/loading states

▶ Example 3: Using useMemo to Implement Virtual List Calculations

JSX
function VirtualList({ items, itemHeight = 40, visibleHeight = 400 }) {
  const [scrollTop, setScrollTop] = useState(0)

  const visibleCount = Math.ceil(visibleHeight / itemHeight)
  const totalHeight = useMemo(() => items.length * itemHeight, [items.length, itemHeight])

  const { startIndex, endIndex, offsetY } = useMemo(() => {
    const start = Math.floor(scrollTop / itemHeight)
    const end = Math.min(start + visibleCount + 1, items.length)
    return { startIndex: start, endIndex: end, offsetY: start * itemHeight }
  }, [scrollTop, itemHeight, visibleCount, items.length])

  const visibleItems = useMemo(() => items.slice(startIndex, endIndex), [items, startIndex, endIndex])

  return (
    <div onScroll={e => setScrollTop(e.currentTarget.scrollTop)}
      style={{ height: visibleHeight, overflowY: 'auto', border: '1px solid #ddd', borderRadius: 4 }}>
      <div style={{ height: totalHeight, position: 'relative' }}>
        <div style={{ position: 'absolute', top: offsetY, width: '100%' }}>
          {visibleItems.map((item, i) => (
            <div key={startIndex + i} style={{ height: itemHeight, padding: '0 12px', display: 'flex', alignItems: 'center', borderBottom: '1px solid #f0f0f0' }}>
              Row {startIndex + i}: {item}
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT
Virtual scroll: renders only visible rows (~10 of 1000+). Scroll → rows swap. Total height = items × 40px. useMemo computes visible range.

Output:

TEXT
Displays: "items.slice(startIndex, endIndex), [items, startIndex, endIn". State: scrollTop (setter: setScrollTop). useMemo optimizes computation

▶ Example 4: Optimizing Lists with React.memo + useCallback

▶ Try it Yourself

Output:

TEXT
Product list (Keyboard $79, Mouse $49, Monitor $399, Headphones $129) with filter. "Remove" deletes item. React.memo + useCallback = only changed items re-render.

Output:

TEXT
Rendering: ...

▶ Example 5: When Not to Use useMemo—Anti-Pattern Comparison

JSX
function BadExample({ count }) {
  // ❌ Simple calculations are not required useMemo——Create memo Expenses > The Calculation Itself
  const doubled = useMemo(() => count * 2, [count])
  // ✅ Direct Calculation
  const doubledOk = count * 2

  // ❌ Each render creates a new object as useMemo Dependency
  const config = useMemo(() => ({ count }), [count])
  const badResult = useMemo(() => config.count * 3, [config]) // config A new reference each time!
  // ✅ Using Primitive Types Directly as Dependencies
  const goodResult = useMemo(() => count * 3, [count])

  return <p>Doubled: {doubledOk}, Tripled: {goodResult}</p>
}

function GoodExample({ items, query }) {
  // ✅ Filter/Sorting large amounts of data useMemo
  const filtered = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase().includes(query.toLowerCase())
    ).sort((a, b) => a.name.localeCompare(b.name))
  }, [items, query])

  // ✅ Complex objects passed to child components use useMemo
  const chartData = useMemo(() => ({
    labels: filtered.map(i => i.name),
    values: filtered.map(i => i.price),
  }), [filtered])

  return (
    <div>
      <p>Found: {filtered.length} items</p>
      <Chart data={chartData} />
    </div>
  )
}
▶ Try it Yourself

Output:

TEXT
❌ useMemo for count*2 (overhead > calculation). ❌ New object as dep breaks memo. ✅ useMemo for large filter/sort + derived chart data objects.

❓ FAQ

Q Do useMemo and useCallback always improve performance?
A Not necessarily. They come with their own overhead—they compare the dependencies array on every render. Overusing them can actually slow things down. React’s official recommendation: Start by writing the unoptimized version, and only optimize when you notice lag. Rule of thumb: Use them only for calculations passed to a React.memo child component or those with an overhead greater than 1 ms.
Q What is the difference between useMemo and a regular variable?
A useMemo has "memory"—if the dependencies haven’t changed, it uses the cached value directly without re-evaluating the expression. A regular variable is re-evaluated on every render. However, useMemo needs to compare the array of dependencies during rendering, so "simple calculations are actually slower with useMemo."
Q When should you use useCallback to preserve a function reference?
A You only need to use it when both of the following conditions are met: ① The function is passed as a prop to a React.memo child component; ② The function is in the dependencies array of a useEffect hook. If neither of these conditions applies, you can simply write the function directly; useCallback is unnecessary.
Q What is the difference between useMemo and useCallback?
A useMemo(() => value, deps) caches the result (value) of a calculation, while useCallback(() => fn, deps) caches a function reference. In fact, useCallback(fn, deps) is equivalent to useMemo(() => fn, deps). Use useCallback when you need to cache a function passed to a child component; use useMemo when you need to cache a computation result.

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Create a PrimeCalculator component that takes a number n as input and calculates the nth prime number. Use useMemo to cache the result so it isn't recalculated every time a new value is entered.
  2. Advanced Exercise (Difficulty ⭐⭐): Create a ProductTable component that contains 500 product records and supports filtering by category and sorting by price. Use useMemo to optimize the filtering and sorting logic, and use useCallback combined with React.memo to optimize list rendering.
  3. Challenge (Difficulty: ⭐⭐⭐): Create a DataDashboard component that aggregates 10,000 order records across three different dimensions (time, region, and category), and use useMemo to implement hierarchical caching (so that only the relevant layers are recalculated when the dimensions change).
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏