1. Introduction
In React, hooks are powerful tools that let you manage state and lifecycle methods in functional components. Among these hooks, useCallback is often mentioned in the same conversation as useMemo, useEffect, and useRef because it deals with performance optimization.
This post will dive into what useCallback does, how it differs from other optimization hooks, and the scenarios in which you should (and shouldn’t) use it.
2. What Is useCallback?
useCallback is a React hook that returns a memoized version of a function. That means React will give you the same function reference across renders, as long as the dependencies you specify haven’t changed.
The basic signature for useCallback looks like this:
const memoizedCallback = useCallback(
() => {
// function logic here
},
[dependencies]
);
- memoizedCallback: The function reference you can call later.
- dependencies: Values that, if changed, cause useCallback to recompute the function reference.
3. Why Use useCallback?
In React, functions (like any other object or array) are recreated on every render. This isn’t usually a big deal—React’s reconciliation engine is smart enough to handle frequent re-renders. However, in some cases, passing a new function reference to child components or hooks can lead to extra rendering or expensive calculations.
Common Use Cases
3.1 Prevent Unnecessary Renders in Child Components
If you pass a function down to a child component that’s wrapped with React.memo, that child will re-render every time it receives a new function reference—even if the function’s internal logic is effectively the same. By memoizing the function with useCallback, the reference stays stable across renders, preventing unnecessary updates.
3.2 Expensive Computations in Callback Functions
If your function does a lot of heavy lifting (e.g., sorting large data sets, complex calculations), you might benefit from memoizing it, so that it’s only recalculated when needed.
3.3 Integration with Other Hooks
Certain hooks (like useEffect or data-fetching hooks) might rely on stable function references to avoid triggering side effects unnecessarily.
4. Basic Example
import React, { useState, useCallback } from "react";
import List from "./List";
function App() {
const [count, setCount] = useState(0);
const [items, setItems] = useState(["Apple", "Banana", "Carrot"]);
// Memoize the increment function
const increment = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return (
<div>
<button onClick={increment}>Increment</button>
<p>Count: {count}</p>
{/* Passing down a stable function reference to List */}
<List items={items} onAddItem={setItems} />
</div>
);
}
export default App;
In this example:
- The increment function is wrapped in useCallback with an empty dependency array, meaning it only creates the function once and never updates it.
- The list of items is managed separately. When you pass onAddItem to List, that reference remains stable (unless setItems changes), reducing the chance of re-renders in child components that depend on that function reference.
5. useCallback vs. useMemo
useCallback and useMemo are closely related:
- useCallback(fn, deps) is essentially the same as useMemo(() => fn, deps).
- useCallback is specifically designed to memoize functions, whereas useMemo is designed to memoize the result of a function call.
In other words, useMemo returns a cached value, and useCallback returns a cached function.
6. Dependency Arrays
Like many React hooks, useCallback uses a dependency array to determine when it needs to recalculate the memoized function.
- If you include variables or state values in the dependency array, the function reference gets updated whenever those values change.
- If the dependency array is empty, the function is created exactly once when the component mounts and never changes afterward.
Be careful: If you leave out a necessary dependency (e.g., a piece of state that the callback uses), you might encounter stale state or unexpected behavior in your app.
const memoizedFn = useCallback(() => {
// uses stateA, stateB
}, [stateA, stateB]);
7. Performance Considerations
7.1 Overhead of Memoization
While useCallback can prevent unnecessary re-renders, using it everywhere might actually hurt performance. Memoization itself has a cost because React needs to compare the dependencies to decide whether to reuse the function reference.
Rule of Thumb: Only use useCallback (and other memoization hooks) in performance-critical areas. Otherwise, the overhead of managing the dependencies might outweigh the benefits.
7.2 Identify Bottlenecks
To know whether useCallback is beneficial, you need to identify parts of your app that are re-rendering too often. Tools like React Profiler can help you see which components render frequently and why.
8. More Advanced Examples
8.1 Using useCallback for Event Handlers in Large Lists
If you render a large list of items, each with an onClick handler, you may trigger numerous unnecessary re-renders. By memoizing the event handler, you can cut down on those updates.
function LargeList({ data, onItemSelect }) {
return (
<ul>
{data.map((item) => (
<ListItem
key={item.id}
item={item}
onSelect={onItemSelect} // stable reference
/>
))}
</ul>
);
}
In this scenario, onItemSelect could be memoized in the parent component so that each ListItem doesn’t see a new reference on every render.
8.2 Function Factories or Closures
If your callback uses values from the parent component’s scope, you’ll need to ensure those are in the dependency array:
function Parent({ multiplier }) {
const [value, setValue] = useState(1);
const multiplyValue = useCallback(() => {
setValue((prev) => prev * multiplier);
}, [multiplier]); // Include multiplier in dependencies
return <Child onMultiply={multiplyValue} />;
}
If you omit [multiplier], your function might always use an outdated value of multiplier.
9. When Not to Use useCallback
- Simple Components: If your child components aren’t memoized or don’t rely on strict reference equality, useCallback may not provide any benefit.
- Rare Renders: If a component rarely re-renders (e.g., it only re-renders when user input changes), the overhead of useCallback might exceed the cost of just recreating the function.
- Global or Static Functions: If a function doesn’t depend on props/state, you can define it outside the component entirely and avoid the need for useCallback.
10. Conclusion
useCallback is a valuable React hook for stabilizing function references and improving performance in certain situations. However, it’s not a silver bullet—use it judiciously where it genuinely helps cut down on re-renders, and always profile to see if your changes have a positive impact.
Key Takeaways
- useCallback(fn, deps) memoizes a function based on dependencies.
- Use it to prevent unnecessary re-renders when passing functions to memoized child components.
- Don’t overuse it—memoization itself comes with overhead.
- Profile your application to identify real performance bottlenecks before introducing optimization hooks.
With these insights, you can confidently incorporate useCallback into your React code where it matters, balancing readability, maintainability, and performance.
Enjoy your Coding!
'Dev > React' 카테고리의 다른 글
A Practical Guide to useDebugValue in React 19 (0) | 2025.03.05 |
---|---|
A Practical Guide to useContext in React 19 (0) | 2025.03.05 |
A Practical Guide to useActionState in React 19 (0) | 2025.03.04 |
export 'default' (imported as 'jwtDecode') was not found in 'jwt-decode (0) | 2024.06.07 |
useActionState에 대해서 톺아보기 (0) | 2024.05.25 |