Skip to content

React useMemo useCallback hooks ​

You should use useMemo and useCallback in React with TypeScript primarily for performance optimization by memoizing expensive calculations or function definitions, preventing unnecessary re-renders of child components.


💡 Key Concepts: Memoization ​

Memoization is a technique where the result of an expensive function call is cached, and the cached result is returned when the same inputs occur again.

  • useMemo: Memoizes a value.
  • useCallback: Memoizes a function definition.

1. When and How to use useMemo ​

The useMemo hook is used to memoize a calculated value. It only re-calculates the value when one of its dependencies changes.

➡️ Syntax (TypeScript) ​

typescript
const memoizedValue = useMemo<TResult>(
  () => {
    // Expensive calculation goes here
    return result;
  },
  [dependency1, dependency2] // Dependencies array
);

🗓️ Real Example Situation: Expensive Calculation ​

Use useMemo when you have a complex, time-consuming calculation inside your component that shouldn't run on every single render, especially if only unrelated props/state have changed.

Scenario: Filtering a large list of items based on a search term.

tsx
import React, { useMemo, useState } from 'react';

// Define the type for an item
interface Item {
  id: number;
  name: string;
  category: string;
}

const ItemList: React.FC<{ items: Item[]; searchTerm: string }> = ({ items, searchTerm }) => {
  // Use useMemo to memoize the filtered list
  const filteredItems = useMemo<Item[]>(() => {
    console.log('Filtering items...'); // This log indicates when the calculation runs
    if (!searchTerm) {
      return items;
    }
    return items.filter(item =>
      item.name.toLowerCase().includes(searchTerm.toLowerCase())
    );
  }, [items, searchTerm]); // Dependencies: only re-calculate when 'items' or 'searchTerm' changes

  return (
    <div>
      <h3>Filtered Results ({filteredItems.length})</h3>
      <ul>
        {filteredItems.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

// --- Parent Component for context ---
const App: React.FC = () => {
  const [searchTerm, setSearchTerm] = useState('');
  const [count, setCount] = useState(0); // State for unrelated re-renders
  const data: Item[] = useMemo(() => ([ /* large array of data */ ]), []); // Mock data

  return (
    <div>
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      <button onClick={() => setCount(c => c + 1)}>
        Rerender (Count: {count})
      </button>
      {/* ItemList's filtering is skipped when 'count' changes, but 'searchTerm' doesn't */}
      <ItemList items={data} searchTerm={searchTerm} />
    </div>
  );
};

2. When and How to use useCallback ​

The useCallback hook is used to memoize a function definition. It returns a memoized version of the function that only changes if one of its dependencies has changed.

Syntax (TypeScript) ​

typescript
const memoizedFunction = useCallback<TFunction>(
  (arg1: TArg1, arg2: TArg2): TResult => {
    // Function logic goes here
    return result;
  },
  [dependency1, dependency2] // Dependencies array
);

🗓️ Real Example Situation: Passing a Prop to a Memoized Child Component ​

Use useCallback when you pass a function as a prop to a child component that is wrapped in React.memo().

Why? In React, functions are recreated on every render of the parent component. When a new function is passed as a prop, the React.memo() child component sees a prop change (since oldFunction !== newFunction) and re-renders unnecessarily. useCallback prevents this.

Scenario: Preventing a memoized button component from re-rendering just because its onClick handler is recreated.

tsx
import React, { useCallback, useState } from 'react';

// 1. Child component wrapped in React.memo for optimization
interface ButtonProps {
  onClick: (value: string) => void;
  label: string;
}

// React.memo ensures this component only re-renders if its props change (shallow comparison)
const MemoizedButton = React.memo<ButtonProps>(({ onClick, label }) => {
  console.log(`Rendering ${label} Button`);
  return (
    <button onClick={() => onClick(label)}>
      {label}
    </button>
  );
});


// 2. Parent component
const ParentComponent: React.FC = () => {
  const [count, setCount] = useState(0);
  const [inputVal, setInputVal] = useState('');

  // ❌ Without useCallback: handleButtonClick would be a new function on every render,
  //    forcing MemoizedButton to re-render even if 'count' hasn't changed.

  // ✅ With useCallback: The function definition is memoized. It only changes
  //    when 'count' (its dependency) changes.
  const handleButtonClick = useCallback((buttonLabel: string) => {
    setCount(c => c + 1);
    console.log(`Button ${buttonLabel} clicked. New count: ${count + 1}`);
  }, [count]); // Dependency: The count state, since it's used inside setCount (though here c=>c+1 is safer)

  return (
    <div>
      <input
        value={inputVal}
        onChange={(e) => setInputVal(e.target.value)}
        placeholder="Typing causes re-render"
      />
      <p>Current Count: {count}</p>

      {/* This button will only re-render when 'count' changes, thanks to useMemoizedButton and useCallback */}
      <MemoizedButton
        label="Increment"
        onClick={handleButtonClick}
      />
    </div>
  );
};

🛑 When NOT to use them ​

Avoid using useMemo or useCallback in these situations:

  1. Trivial Calculations: If the computation is simple (e.g., a + b, a simple string concatenation, or filtering a small array), the overhead of the hook itself often outweighs the performance gain.
  2. Every Function/Value: Don't wrap every function or value in your component. This adds complexity and memory overhead. Only use them for performance-critical scenarios (heavy computations or passing props to memoized child components).
  3. No Dependencies: If your function or value doesn't rely on state or props, you can often define it outside the component function entirely, which is the ultimate form of memoization.