Skip to content

React Hooks: Mastering useCallback (Casual Guide)

Posted on:January 27, 2026 at 07:02 PM

Hey there! 👋

So, you’ve mastered useState and useEffect. Now you’re looking at useCallback and wondering, “Do I wrap everything in this?”

Short answer: No. Long answer: Read on. ☕

I remember when I first started with Hooks, I used useCallback everywhere. I thought I was making my app “faster”. Spoiler: I wasn’t. I was just adding complexity.

Let’s demystify this hook together.

The Problem: Functions are different every time

In JavaScript, functions are objects. And every time a React component re-renders, all the functions inside it are re-created.

const MyComponent = () => {
  // This function is BRAND NEW on every render
  const handleClick = () => {
    console.log("Clicked!");
  };

  return <ChildComponent onClick={handleClick} />;
};

Usually, this is fine. Browsers are fast. But, if ChildComponent is wrapped in React.memo (to prevent unnecessary re-renders), it checks if its props have changed.

Since handleClick is technically a new function every time, React.memo thinks the props changed, and re-renders the child anyway. All that optimization work? Wasted. 📉

The Solution: useCallback

useCallback tells React: “Hey, keep this function exactly the same between renders, unless something specific changes.”

import { useCallback, useState } from "react";

const MyComponent = () => {
  const [count, setCount] = useState(0);

  // Now, this function stays the same!
  const handleClick = useCallback(() => {
    console.log("Clicked!");
  }, []); // <--- Dependency array

  return <ChildComponent onClick={handleClick} />;
};

Now, ChildComponent sees the exact same function reference. If it uses React.memo, it won’t re-render unnecessary.

When should you actually use it?

Don’t use it prematurely. Here is my rule of thumb:

  1. Passing props to memoized components: If you are passing a function to a big list or a heavy component wrapped in React.memo, use it.
  2. useEffect dependencies: If a function is in a useEffect dependency array, wrap it in useCallback so it doesn’t trigger the effect on every render.

Example: The “Too Many Renders” Trap

Imagine you have a useEffect that fetches data when a fetchData function is called.

// WITHOUT useCallback
const fetchData = () => { ... };

useEffect(() => {
  fetchData();
}, [fetchData]); // 🚹 Infinite Loop Alert!

Because fetchData is new every render, the effect sees it as “changed” and runs again
 which triggers a re-render
 which creates a new fetchData
 and boom. Infinite loop. đŸ’„

Fix it with useCallback:

// WITH useCallback
const fetchData = useCallback(() => { ... }, []);

useEffect(() => {
  fetchData();
}, [fetchData]); // ✅ Safe and sound

Closing

Optimization is a double-edged sword. useCallback has a cost (React has to store the function in memory). Use it when you have a specific performance problem or referential equality issue, not just because it looks cool.

Keep it simple! 🚀