← Back to React Course | Chapter 5: Core Hooks | Lesson 8 of 12

useCallback Hook

useCallback is React saving a copy of a specific instruction sheet, so it can hand out the exact same one again instead of writing a new copy every time.

Why Function References Matter

Every time a component renders, any function defined inside it is recreated as a brand-new function, even if its code looks identical. This matters when that function is passed as a prop to a child wrapped in React.memo, because a new function reference makes the child think its props changed.

Note: Function identity ('is this the same function as before') is what React.memo checks -- not just whether the code inside looks the same.

Warning: Passing an inline arrow function directly as a prop recreates it every render, which can defeat a child's React.memo optimization.

Example: Why Function References Matter

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function Parent() {
      const [count, setCount] = React.useState(0);
      function handleClick() { console.log("Clicked"); } // recreated every render
      return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<Parent />);
  </script>
</body>
</html>

Memoizing a Function with useCallback

useCallback(fn, dependencies) returns the same function reference across renders, as long as the listed dependencies haven't changed. This lets you pass a genuinely stable function reference down to memoized child components.

Note: Pair useCallback with React.memo on the receiving child component -- using one without the other provides no real benefit.

Warning: If you forget to list a variable the callback uses in its dependency array, the callback will use a stale, outdated value of that variable.

Example: Memoizing a Function with useCallback

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function Button({ onClick, label }) {
      return <button onClick={onClick}>{label}</button>;
    }
    const MemoButton = React.memo(Button);
    function Parent() {
      const [count, setCount] = React.useState(0);
      const handleClick = React.useCallback(() => {
        console.log("Stable callback");
      }, []);
      return <div><p>{count}</p><MemoButton onClick={handleClick} label="Click" /><button onClick={() => setCount(count + 1)}>+1</button></div>;
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<Parent />);
  </script>
</body>
</html>

useCallback vs useMemo

useCallback and useMemo solve the same underlying problem in slightly different ways: useMemo caches a calculated VALUE, while useCallback caches a FUNCTION itself. In fact, useCallback(fn, deps) behaves the same as useMemo(() => fn, deps).

Note: Use useMemo when you want to cache a computed result; use useCallback specifically when you want to cache a function reference.

Warning: Confusing the two syntaxes -- like wrapping a function in useMemo without returning it from an inner function -- is a common typo-level mistake.

Example: useCallback vs useMemo

markup
<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    function Demo() {
      const [n, setN] = React.useState(1);
      const doubled = React.useMemo(() => n * 2, [n]); // caches a VALUE
      const logDoubled = React.useCallback(() => console.log(doubled), [doubled]); // caches a FUNCTION
      return <button onClick={logDoubled}>Doubled: {doubled}</button>;
    }
    ReactDOM.createRoot(document.getElementById('root')).render(<Demo />);
  </script>
</body>
</html>
Common Mistakes
  1. Wrapping every function in useCallback by default, even ones with no real performance need for it.
  2. Forgetting to include values the callback closes over in its dependency array, causing it to use stale data.
  3. Expecting useCallback alone to prevent a child component from re-rendering -- it must be paired with React.memo on the child to have that effect.
Chapter Summary
  • useCallback memoizes a function so the same function reference is reused between renders.
  • It's useful when passing callbacks to child components wrapped in React.memo.
  • Without a stable reference, a new function is created every render, defeating memoization in children.
  • Like useMemo, it should be used for genuine performance needs, not by default everywhere.
Browser Support

Available since React 16.8, when Hooks were introduced.

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.