DEV Community

Safal Bhandari
Safal Bhandari

Posted on

Understanding useCallback in React: Why It’s Important (With Example)

When building React applications, especially ones that involve complex component trees or performance-sensitive rendering, unnecessary re-renders can become a real performance bottleneck. This is where the useCallback hook comes into play.


What is useCallback?

useCallback is a React hook that returns a memoized version of a callback function — that is, it ensures the function reference only changes if its dependencies change.

The syntax looks like this:

const memoizedCallback = useCallback(() => {
  // Your function logic here
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode

Why is it important?

In React, functions are recreated every time a component re-renders. Even if the function logic hasn’t changed, its reference in memory is new. This can cause problems in scenarios like:

  1. Passing callbacks to child components
    If the child is wrapped in React.memo (or memoized with a custom comparison), it will still re-render if the callback reference changes.

  2. Using callbacks in dependencies
    If you pass a callback to another useEffect or hook, it may trigger unintended re-execution unless the reference is stable.

  3. Performance optimization
    In large applications, avoiding unnecessary re-renders saves memory and improves responsiveness.


Example Without useCallback

import React, { useState } from 'react';

const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click Me</button>;
});

export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    console.log('Button clicked');
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <Child onClick={handleClick} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

What happens?

  • Every time you click Increase Count, App re-renders.
  • handleClick is recreated, so Child sees a new prop reference and re-renders — even though the logic hasn’t changed.

Example With useCallback

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

const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click Me</button>;
});

export default function App() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <Child onClick={handleClick} />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

What changes?

  • handleClick is memoized with useCallback.
  • The reference to handleClick remains the same between renders (unless dependencies change).
  • Child won’t re-render when count changes, because its onClick prop didn’t change.

When to Use useCallback

  • When passing callbacks to memoized child components.
  • When a callback is used inside another hook’s dependency array.
  • When re-creating a function on every render is expensive.

When NOT to Use useCallback

  • If the callback is not passed to children or used in a dependency-sensitive hook.
  • If the function is trivial and re-creating it is cheaper than memoizing it (over-optimization can harm readability).

Top comments (0)