Core Difference
useMemo() hook memoizes a value (the result of a function call), preventing re-computation unless dependencies change. useCallback() hook memoizes a function, preventing its re-creation unless its dependencies change. Both aim to optimize performance by preserving referential equality.Key Characteristics
useMemo
- Memoizes Values: caches the result of an expensive calculation.
- Returns a Value: the hook returns the memoized value directly.
- Use Case: optimizing computationally intensive calculations or ensuring stable object/array references for props.
useCallback
- Memoizes Functions: caches the function definition itself.
- Returns a Function: the hook returns the memoized function.
- Use Case: preventing unnecessary re-renders of child components that receive callback functions as props, especially when those children are wrapped in
React.memo.
When to Use
useMemo
Use it when you have a complex calculation that produces a value (e.g. filtered list, aggregated data) and you want to avoid re-running that calculation on every render.useCallback
Use it when you are passing a function down to a child component that is optimized withReact.memo. This ensures the child doesn't re-render just because the parent re-created the function on its own render.Examples
- Filtering a List (useMemo): memoizing a filtered list of items to avoid re-filtering on every render if the original list or filter criteria haven't changed.
- Stable Event Handler (useCallback): passing a stable
onClickhandler to a memoized button component to prevent its re-render.
Code Examples
- https://codesandbox.io/p/sandbox/filtering-a-list-4dwjsj
Filtering a list (useMemo)
// App.tsx import ProductList from './components/ProductList' const products = [ { id: 1, name: 'milk' }, { id: 2, name: 'butter' }, { id: 3, name: 'bread' }, ] export default function App() { console.log('App render') return <ProductList products={products} filter="milk" /> }// ProductList.tsx import { useMemo } from 'react' interface Product { id: number name: string } interface ProductListProps { products: Product[] filter: string } function ProductList({ products, filter }: ProductListProps) { const filteredProducts = useMemo(() => { console.log('Filtering products') return products.filter((p) => p.name.includes(filter)) }, [products, filter]) return ( <ul> {filteredProducts.map((product) => ( <li key={product.id}>{product.name}</li> ))} </ul> ) } export default ProductList - https://codesandbox.io/p/sandbox/stable-event-handler-z2g23k
Stable Event handler (useCallback)
// App.tsx 'use client' import { useCallback, useState } from 'react' import MemoizedButton from './components/MemoizedButton' function App() { const [count, setCount] = useState<number>(0) const handleClick = useCallback(() => { setCount((prevCount) => prevCount + 1) }, []) return ( <div> <MemoizedButton onClick={handleClick}>Increment Count</MemoizedButton> <p>Count: {count}</p> </div> ) } export default App// MemoizedButton.tsx import React from 'react' interface MemoizedButtonProps { onClick: () => void children: React.ReactNode } const MemoizedButton = React.memo(({ onClick, children }: MemoizedButtonProps) => { console.log('MemoizedButton rendered') return ( <button type="button" onClick={onClick}> {children} </button> ) }) export default MemoizedButton