This ESLint warning occurs when useMemo references a variable that changes over time but is not listed in its dependency array, which can cause the memoized value to become stale and lead to bugs.
The "React Hook useMemo has a missing dependency" warning is raised by the eslint-plugin-react-hooks exhaustive-deps rule. This rule verifies that all reactive values used inside the useMemo calculation function are properly declared in the dependency array. When you use useMemo, React compares the dependencies from the current render with those from the previous render using Object.is(). If all dependencies are identical, useMemo returns the previously calculated value without re-running the calculation. However, if a reactive value (props, state, or variables declared in your component body) is used inside useMemo but not included in the dependency array, React won't know to recalculate when that value changes. This creates a "stale closure" where the memoized value references old data, potentially causing incorrect behavior in your application. The exhaustive-deps rule acts as a safeguard against these bugs by analyzing your code and ensuring every reactive value used in the calculation is tracked. When it detects a missing dependency, it provides a warning with the exact variable name that should be added to the array.
Look at the ESLint warning message to see which variable is missing. The warning typically shows:
React Hook useMemo has a missing dependency: 'count'.
Either include it or remove the dependency array.Examine your useMemo hook and identify all variables from outside the hook that are used inside the calculation function:
function MyComponent({ multiplier }) {
const [count, setCount] = useState(0);
// ❌ 'count' is missing from dependencies
const doubled = useMemo(() => {
return count * 2;
}, []); // Empty array
return <div>{doubled}</div>;
}Include the variable in the dependency array so React knows to recalculate when it changes:
function MyComponent({ multiplier }) {
const [count, setCount] = useState(0);
// ✅ 'count' is now included
const doubled = useMemo(() => {
return count * 2;
}, [count]); // Added count
return <div>{doubled}</div>;
}The memoized value will now update whenever count changes.
If the missing dependency is an object or array that is recreated on every render, wrap it with useMemo to maintain a stable reference:
function MyComponent({ items }) {
// ❌ Config is recreated every render
const config = { threshold: 10 };
const filtered = useMemo(() => {
return items.filter(item => item.value > config.threshold);
}, [items, config]); // config changes every render!
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}Fix by memoizing the config object:
function MyComponent({ items }) {
// ✅ Config is memoized
const config = useMemo(() => ({ threshold: 10 }), []);
const filtered = useMemo(() => {
return items.filter(item => item.value > config.threshold);
}, [items, config]); // Now stable
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}If the missing dependency is a function, wrap it with useCallback to create a stable reference:
function MyComponent({ data }) {
const [multiplier, setMultiplier] = useState(2);
// ❌ processItem is recreated every render
const processItem = (item) => item * multiplier;
const processed = useMemo(() => {
return data.map(processItem);
}, [data, processItem]); // processItem changes every render!
return <div>{processed.join(', ')}</div>;
}Fix using useCallback:
function MyComponent({ data }) {
const [multiplier, setMultiplier] = useState(2);
// ✅ processItem is memoized
const processItem = useCallback((item) => item * multiplier, [multiplier]);
const processed = useMemo(() => {
return data.map(processItem);
}, [data, processItem]); // Now stable
return <div>{processed.join(', ')}</div>;
}If a variable never changes, you can move it outside the component or inside the useMemo hook to avoid dependency warnings:
// ✅ Option 1: Move constant outside component
const DEFAULT_CONFIG = { threshold: 10, limit: 100 };
function MyComponent({ items }) {
const filtered = useMemo(() => {
return items.filter(item => item.value > DEFAULT_CONFIG.threshold);
}, [items]); // No need to include DEFAULT_CONFIG
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}Or move it inside the hook:
function MyComponent({ items }) {
const filtered = useMemo(() => {
// ✅ Option 2: Define inside useMemo
const config = { threshold: 10, limit: 100 };
return items.filter(item => item.value > config.threshold);
}, [items]); // No need to include config
return <ul>{filtered.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}Understanding Object.is() Comparison
React uses Object.is() to compare dependencies, which means objects and arrays are compared by reference, not by content. Two objects with identical properties are considered different if they have different references. This is why you need to memoize objects and arrays used as dependencies.
When to Suppress the Warning
In rare cases, you may intentionally want to omit a dependency (though this is generally not recommended). You can disable the rule for a specific line:
const value = useMemo(() => {
return computeExpensiveValue(prop);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Intentionally emptyOnly do this if you fully understand the implications and are certain the omitted dependency will not cause stale closures. Document why you're disabling the rule.
Performance Considerations
Adding dependencies to useMemo is not a performance problem—it's the correct behavior. The purpose of useMemo is to skip expensive calculations when inputs haven't changed, not to cache a value forever. If you find yourself fighting the exhaustive-deps rule, you may be using useMemo unnecessarily for simple calculations that don't need memoization.
Custom Hooks and exhaustive-deps
You can configure the exhaustive-deps rule to check custom hooks that follow the same dependency pattern by using the additionalHooks option in your ESLint config:
{
"rules": {
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "(useMyCustomHook|useAnotherHook)"
}]
}
}React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite