βοΈ React Hook: useCallback β
π Quick Summary β
useCallbackmemoizes a function definition so that it keeps the same reference across renders unless dependencies change.- Useful when passing callbacks to child components that depend on reference equality (e.g., wrapped in
React.memo). - Syntax:tsx
const memoizedFn = useCallback(() => { /* logic */ }, [deps]);
π§ Mental Model β
- Think of
useCallbackas a function factory with a memory. - Without it, every render creates a new function reference β may trigger unnecessary child re-renders.
- With it, React reuses the same function reference if dependencies havenβt changed.
π Key Concepts β
Function Identity
- Functions are objects in JS β new render = new function reference.
useCallbackpreserves identity until deps change.
When to Use
- Passing callbacks to memoized children (
React.memo). - Event handlers that donβt need to change every render.
- Passing callbacks to memoized children (
Dependencies
- If dependencies change, new function is created.
- If
[], function is stable forever (likecomponentDidMount).
Relation to
useMemouseCallback(fn, deps)is equivalent touseMemo(() => fn, deps).- Difference is semantic intent:
useMemoβ values,useCallbackβ functions.
π» Code Examples β
Example 1: Stable Callback for Child Component β
import { useState, useCallback, memo } from "react";
const Child = memo(({ onClick }: { onClick: () => void }) => {
console.log("Child rendered");
return <button onClick={onClick}>Click Me</button>;
});
export default function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("Button clicked");
}, []);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
<Child onClick={handleClick} />
</div>
);
}How it works (stepβbyβstep):
Parentre-renders whencountchanges.- Without
useCallback,handleClickwould be a new function each time βChildwould re-render unnecessarily. - With
useCallback,handleClickkeeps the same reference, soChilddoesnβt re-render unless needed.
Example 2: Callback with Dependencies β
import { useState, useCallback } from "react";
export default function SearchBox() {
const [query, setQuery] = useState("");
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
}, []);
return (
<input value={query} onChange={handleChange} placeholder="Search..." />
);
}How it works (stepβbyβstep):
handleChangeis created once withuseCallback.- Each keystroke calls the same function reference.
- If dependencies were added, the function would be recreated when they change.
Example 3: Passing Stable Callback with Dependency β
import { useState, useCallback, memo } from "react";
const ExpensiveList = memo(({ filter }: { filter: (item: string) => boolean }) => {
console.log("ExpensiveList rendered");
const items = ["apple", "banana", "cherry"];
return (
<ul>{items.filter(filter).map((i, idx) => <li key={idx}>{i}</li>)}</ul>
);
});
export default function Parent() {
const [letter, setLetter] = useState("a");
const filterFn = useCallback((item: string) => item.startsWith(letter), [letter]);
return (
<div>
<ExpensiveList filter={filterFn} />
<button onClick={() => setLetter("b")}>Show B</button>
</div>
);
}How it works (stepβbyβstep):
- Parent renders with
letter = "a". filterFncreated with dependencyletter.ExpensiveListonly re-renders whenletterchanges β stable function identity otherwise.
Example 4: useCallback vs useMemo β
const fn1 = useCallback(() => compute(), [deps]);
const fn2 = useMemo(() => () => compute(), [deps]);- Both are equivalent β
fn1andfn2are memoized functions. - Use
useCallbackfor functions,useMemofor computed values.
β οΈ Common Pitfalls & Gotchas β
- β Using
useCallbackeverywhere β unnecessary complexity. - β Forgetting dependencies β stale closures (function uses outdated variables).
- β Passing inline functions to memoized children β defeats memoization.
β Best Practices β
- Use it with
React.memoor heavy child components. - Keep dependency arrays accurate.
- Avoid premature optimization: measure before adding.
- Donβt wrap every function in
useCallbackblindly.
β Interview Q&A β
Q1. Why do we need useCallback?
A: To memoize function references so they donβt change on every render, preventing unnecessary re-renders in memoized children.
Q2. Difference between useCallback and useMemo?
A:
useCallbackβ memoizes a function.useMemoβ memoizes a value.
Implementation-wise,useCallback(fn, deps)isuseMemo(() => fn, deps).
Q3. Does useCallback guarantee no re-render of child?
A: No. It only guarantees the function reference is stable. Child still re-renders if its other props or state change.
Q4. When should you not use useCallback?
A: For simple inline event handlers in non-memoized children. Wrapping them adds unnecessary complexity.
Q5. Whatβs a stale closure issue in useCallback?
A: When a callback captures outdated state/props due to missing dependencies in its dependency array.
Q6. Is useCallback just performance optimization?
A: Yes. It doesnβt add new functionality, only helps optimize renders and reference stability.