🔴▶️ Prefer watching instead?

I break all of this down with real examples and a live demo in my YouTube video.

👉 Watch the full video on useCallback here

(Don’t forget to like & subscribe if it helps you!)


React performance optimization can get tricky, especially when it comes to unnecessary re-renders. One of the tools React gives us to help with this is the useCallback hook. In this post, we’ll dive into what it does, when to use it (and when not to), and walk through some common use cases with examples.


🔍 What Is useCallback?

According to the official React docs:

"useCallback is a React Hook that lets you cache a function definition between re-renders."

In simpler terms, useCallback helps you memoize a function, so it doesn’t get re-created every time the component re-renders—unless one of its dependencies has changed.

This can be especially helpful in performance-sensitive situations, such as components that perform heavy computations or when passing functions to child components that rely on React.memo.

⚠️ It's important to note that useCallback is NOT the same as useMemo. While useCallback memoizes a function, useMemo memoizes the result of a function.


💡 The Most Common Use Case - Passing Callback Functions to Child Components

A very common use of useCallback is when you pass functions down to child components.

Let’s say you have a parent component that passes a callback to a memoized child. If that function is re-created on every render, it will cause the child component to re-render as well—even if the rest of the props are the same.

By wrapping the callback in useCallback, you ensure that the function only changes when its dependencies do.

However, this doesn't mean you should always memoize functions. Using useCallback adds a dependency check on every render, so you should weigh its use carefully—especially if the child component is lightweight.


🧪 Example 1: Skipping Unnecessary Renders

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

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

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

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

  return (
    <div>
      <h1>Count: {count}h1>
      <button onClick={() => setCount(count + 1)}>Increase Countbutton>
      <Child onClick={handleClick} />
    div>
  );
}

🔎 Explanation

  • Without useCallback, handleClick would be recreated on every render, triggering a re-render of even if nothing changed.

  • Since we use useCallback, handleClick stays the same between renders (unless its dependencies change), so the child doesn’t re-render unnecessarily.


🔁 Example 2: Preventing an Effect from Firing Too Often

import React, { useState, useCallback, useEffect } from "react";

function ChatRoom({ roomId }) {
  const [message, setMessage] = useState("");

  // Memoize options generator so it only updates when roomId changes
  const createOptions = useCallback(() => {
    return {
      serverUrl: "https://localhost:1234",
      roomId,
    };
  }, [roomId]);

  useEffect(() => {
    const options = createOptions();
    const connection = createConnection(options);
    connection.connect();

    return () => connection.disconnect();
  }, [createOptions]);

  // ...

🔎 Explanation

  • createOptions is recreated only when roomId changes.

  • This ensures useEffect only runs when needed and doesn’t cause unnecessary API calls.


🧠 Final Thoughts

useCallback is a powerful tool, but like all optimization tools, it should be used with intention.

If your component isn't facing performance issues, there's no need to prematurely optimize.


✅ Use useCallback when:

  • You're passing callbacks to memoized child components (React.memo) and want to avoid unnecessary re-renders.
  • The function is used inside a useEffect or useMemo, and re-creating it would trigger the hook unnecessarily.
  • You're working with performance-sensitive components where re-rendering has a noticeable cost.
  • The callback is part of a stable API or needs to maintain reference equality between renders (e.g., for third-party integrations).

❌ Don’t use useCallback:

  • Just out of habit — not every function needs to be memoized.
  • When your component renders quickly and doesn't pass callbacks to memoized children.
  • When the callback doesn’t depend on any props or state, and it's fine to redefine it.
  • If using it adds more complexity than performance gain.

⚠️ Remember: useCallback itself has a cost. If you're not solving a real re-render or dependency issue, it may not be worth using.


Thanks for sticking around!

Go ship something great.
Catch you in the next one 👋