Skip to main content
useCallback is a React Hook that lets you cache a function definition between re-renders.
function useCallback<T>(
  callback: T,
  deps: Array<mixed> | void | null
): T

Parameters

callback
T
required
The function value that you want to cache. It can take any arguments and return any values. React will return (not call) your function back to you during the initial render. On subsequent renders, React will return the same function if the dependencies have not changed. Otherwise, it will return the function you passed during the current render.
deps
Array<mixed> | void | null
The list of all reactive values referenced inside the callback code. React will compare each dependency with its previous value using the Object.is comparison.
  • If omitted, a new function is created on every render (defeats the purpose)
  • If [] (empty array), the function never changes
  • If [dep1, dep2], the function updates when dependencies change

Returns

On the initial render, useCallback returns the callback function you passed. On subsequent renders, it will either return an already stored callback function from the last render (if the dependencies haven’t changed), or return the callback function you passed during this render.

Usage

Skipping re-rendering of components

When you optimize rendering performance, you’ll sometimes need to cache functions that you pass to child components:
import { useCallback, memo } from 'react';

function ProductPage({ productId, referrer }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails
    });
  }, [productId, referrer]);
  
  return <ShippingForm onSubmit={handleSubmit} />;
}

const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ... will only re-render if onSubmit changes
});
useCallback is only useful as an optimization when passing functions to components wrapped in memo, or when the function is used as a dependency in other Hooks.

Updating state from a memoized callback

Use an updater function to avoid dependencies on state:
function TodoList() {
  const [todos, setTodos] = useState([]);
  
  // ❌ Depends on todos
  const handleAddTodo = useCallback((text) => {
    setTodos([...todos, { id: nextId++, text }]);
  }, [todos]); // Creates new function when todos changes
  
  // ✅ No dependencies needed
  const handleAddTodo = useCallback((text) => {
    setTodos(prevTodos => [...prevTodos, { id: nextId++, text }]);
  }, []); // Function never changes
  
  return <AddTodo onAdd={handleAddTodo} />;
}

Preventing an Effect from firing too often

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState('');
  
  // Memoize the function so it doesn't change on every render
  const createOptions = useCallback(() => {
    return {
      serverUrl: 'https://localhost:1234',
      roomId: roomId
    };
  }, [roomId]); // Only recreate if roomId changes
  
  useEffect(() => {
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
  }, [createOptions]); // Won't run on every render
  
  return (
    <>
      <input value={message} onChange={e => setMessage(e.target.value)} />
      <p>Room: {roomId}</p>
    </>
  );
}
In this example, it would be better to move createOptions inside the effect or remove the function entirely:
useEffect(() => {
  const options = {
    serverUrl: 'https://localhost:1234',
    roomId: roomId
  };
  const connection = createConnection(options);
  connection.connect();
  return () => connection.disconnect();
}, [roomId]); // Simpler and more direct

Optimizing a custom Hook

function useRouter() {
  const { dispatch } = useContext(RouterContext);
  
  // Memoize functions returned from custom hooks
  const navigate = useCallback((url) => {
    dispatch({ type: 'navigate', url });
  }, [dispatch]);
  
  const goBack = useCallback(() => {
    dispatch({ type: 'back' });
  }, [dispatch]);
  
  return { navigate, goBack };
}

// Now components using this hook won't re-render unnecessarily
function MyComponent() {
  const { navigate } = useRouter();
  
  useEffect(() => {
    // This effect won't re-run unless navigate actually changes
    // (which only happens if dispatch changes)
  }, [navigate]);
}

Common Patterns

Event handlers

function Component() {
  // ❌ Unnecessary - not passed to memoized child
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return <button onClick={handleClick}>Click</button>;
}

// ✅ Better - no need for useCallback
function Component() {
  function handleClick() {
    console.log('clicked');
  }
  
  return <button onClick={handleClick}>Click</button>;
}

With external store

function SearchResults({ query }) {
  // Cache the subscribe function
  const subscribe = useCallback((callback) => {
    return searchAPI.subscribe(query, callback);
  }, [query]);
  
  const results = useSyncExternalStore(
    subscribe,
    () => searchAPI.getSnapshot(query),
    () => []
  );
  
  return <Results items={results} />;
}

TypeScript

import { useCallback } from 'react';

// Function type is inferred from callback
const handleClick = useCallback(() => {
  console.log('clicked');
}, []);
// Type: () => void

// With parameters
const handleSubmit = useCallback((data: FormData) => {
  submitForm(data);
}, []);
// Type: (data: FormData) => void

// With return value
const calculate = useCallback((a: number, b: number): number => {
  return a + b;
}, []);
// Type: (a: number, b: number) => number

// Explicitly type the callback
type ClickHandler = (event: MouseEvent) => void;

const handleClick = useCallback<ClickHandler>((event) => {
  console.log(event.clientX);
}, []);

Generic callbacks

interface Props<T> {
  items: T[];
  onSelect: (item: T) => void;
}

function List<T>({ items, onSelect }: Props<T>) {
  const handleClick = useCallback((item: T) => {
    onSelect(item);
  }, [onSelect]);
  
  return (
    <>
      {items.map(item => (
        <button onClick={() => handleClick(item)}>
          {String(item)}
        </button>
      ))}
    </>
  );
}

Troubleshooting

My callback runs on every render

Make sure you provided a dependency array:
// ❌ Missing dependency array
const callback = useCallback(() => {
  // ...
}); // Creates new function every render!

// ✅ With dependency array
const callback = useCallback(() => {
  // ...
}, []); // Function stays the same

My callback is being created on every render despite dependencies

One of your dependencies might be changing on every render:
function Component() {
  const options = { mode: 'fast' }; // New object every render
  
  const callback = useCallback(() => {
    processData(options);
  }, [options]); // options changes every render!
}
Solutions:
const options = { mode: 'fast' }; // Outside component

function Component() {
  const callback = useCallback(() => {
    processData(options);
  }, []); // options never changes
}

Should I add useCallback everywhere?

No! Only use useCallback when:
  1. You’re passing the function to a component wrapped in memo
  2. The function is a dependency of another Hook (useEffect, useMemo, etc.)
  3. The function is expensive to create (rare)
// ❌ Unnecessary - no optimization benefit
function Component() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return <button onClick={handleClick}>Click</button>;
}

// ✅ Better - simpler and same performance
function Component() {
  function handleClick() {
    console.log('clicked');
  }
  
  return <button onClick={handleClick}>Click</button>;
}

useCallback vs useMemo

// These are equivalent:
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

const memoizedCallback = useMemo(() => {
  return () => {
    doSomething(a, b);
  };
}, [a, b]);
HookPurposeReturns
useCallbackCache function definitionThe function itself
useMemoCache computation resultThe computed value
// useCallback: Cache the function
const handleClick = useCallback(() => {
  console.log('clicked');
}, []);

// useMemo: Cache the result
const sortedItems = useMemo(() => {
  return items.sort((a, b) => a.value - b.value);
}, [items]);