🔴▶️ 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 asuseMemo
. WhileuseCallback
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 whenroomId
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
oruseMemo
, 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 👋