React effects that re-run on every render cause unnecessary API calls, subscriptions, and performance issues. When effects depend on functions that are recreated each render, React thinks the dependency changed—even when it hasn't.
The Problem
When you pass a function to effect dependencies, React compares it by reference. Since functions are recreated on every render, the effect triggers unnecessarily.
function MyComponent({ userId }) {
const [data, setData] = useState(null);
// ❌ Problem: fetchUser is recreated on every render
const fetchUser = () => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setData);
};
// Effect re-runs unnecessarily
useEffect(() => {
fetchUser();
}, [fetchUser]); // fetchUser changes every render
return <div>{data?.name}</div>;
}The Solution: Memoized Functions
Use useCallback to memoize functions—they only get recreated when specified dependencies change.
import { useEffect, useCallback, useState } from 'react';
function MyComponent({ userId }) {
const [data, setData] = useState(null);
// ✅ Solution: Memoize the function
const fetchUser = useCallback(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setData);
}, [userId]); // Only recreate when userId changes
useEffect(() => {
fetchUser();
}, [fetchUser]); // Now stable unless userId changes
return <div>{data?.name}</div>;
}How It Works
- useCallback wraps your function and returns a memoized version
- The function only gets recreated when dependencies change
- The effect sees the same function reference across renders
- Effect only runs when the function actually changes
When to Use This Pattern
Use memoized functions when:
- Passing functions to effect dependencies
- Passing callbacks to child components wrapped in React.memo
- Creating event handlers that trigger expensive operations
- Building custom hooks that return functions
Don't use it for:
- Simple inline event handlers
- Functions that don't cause performance issues
- Every function in your component (premature optimization)
Key Takeaways
- useCallback prevents function recreation across renders
- Use it when functions are dependencies in effects or passed to memoized components
- Always include all dependencies to avoid stale closures
- Measure performance impact before optimizing
- Consider moving functions inside effects when they're only used there
