This ESLint warning occurs when you use variables, props, or state inside hooks like useEffect, useCallback, or useMemo without including them in the dependency array. Missing dependencies can cause stale closures where your hook uses outdated values, leading to subtle bugs that are hard to debug.
The react-hooks/exhaustive-deps ESLint rule enforces that all values referenced inside React hooks are included in their dependency arrays. When a variable, prop, or state value is used inside useEffect, useCallback, useMemo, or other hooks but not listed in the dependency array, ESLint flags it. This rule exists to prevent stale closures - a situation where your hook "remembers" old values instead of using current ones. React hooks rely on the dependency array to know when to re-run effects or recalculate memoized values. If a dependency is missing, React won't re-run the hook when that value changes, causing your app to use outdated data and behave unpredictably.
Read the complete ESLint warning in your terminal or editor. It will tell you exactly which variable is missing from the dependency array. Look for messages like: "React Hook useEffect has a missing dependency: 'count'. Either include it or remove the dependency array."
Example warning:
Line 12:6: React Hook useEffect has a missing dependency: 'userId'.
Either include it or remove the dependency array react-hooks/exhaustive-depsThe most straightforward fix is to add the missing variable to the dependency array. This ensures your hook re-runs whenever that value changes.
// BEFORE - ESLint warning
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // Missing userId dependency
return <div>{user?.name}</div>;
}
// AFTER - Fixed
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // userId added to dependencies
return <div>{user?.name}</div>;
}Most modern IDEs with ESLint integration will offer an auto-fix option to add missing dependencies automatically.
If the missing dependency is a function defined in your component, wrap it in useCallback. This prevents the function from being recreated on every render, avoiding infinite loops.
// BEFORE - Function recreated every render
function SearchComponent({ apiEndpoint }) {
const [results, setResults] = useState([]);
const searchItems = (query) => {
fetch(apiEndpoint + query).then(r => r.json()).then(setResults);
};
useEffect(() => {
searchItems("default");
}, [searchItems]); // searchItems changes every render - infinite loop!
return <div>{results.length} results</div>;
}
// AFTER - Function memoized with useCallback
function SearchComponent({ apiEndpoint }) {
const [results, setResults] = useState([]);
const searchItems = useCallback((query) => {
fetch(apiEndpoint + query).then(r => r.json()).then(setResults);
}, [apiEndpoint]); // Only recreate when apiEndpoint changes
useEffect(() => {
searchItems("default");
}, [searchItems]); // Safe - searchItems reference is stable
return <div>{results.length} results</div>;
}If a variable or function is only used inside one hook, move its declaration inside that hook. This removes it from the dependency requirements entirely.
// BEFORE - External variable requires dependency
function Timer() {
const [seconds, setSeconds] = useState(0);
const interval = 1000;
useEffect(() => {
const timer = setInterval(() => {
setSeconds(s => s + 1);
}, interval);
return () => clearInterval(timer);
}, [interval]); // interval dependency required
return <div>{seconds}s</div>;
}
// AFTER - Constant moved inside effect
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = 1000; // Moved inside
const timer = setInterval(() => {
setSeconds(s => s + 1);
}, interval);
return () => clearInterval(timer);
}, []); // No dependencies needed
return <div>{seconds}s</div>;
}When your effect only needs the previous state value to calculate the next one, use the functional form of setState. This removes the state variable from the dependency array.
// BEFORE - count is a dependency
function Counter({ increment }) {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + increment); // Uses current count
}, 1000);
return () => clearInterval(timer);
}, [count, increment]); // count causes effect to restart constantly
return <div>{count}</div>;
}
// AFTER - Functional update removes count dependency
function Counter({ increment }) {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + increment); // Uses previous value
}, 1000);
return () => clearInterval(timer);
}, [increment]); // Only increment needed as dependency
return <div>{count}</div>;
}If a value never changes or doesn't depend on props/state, move it outside the component. Constants, utility functions, and configuration objects can be declared at the module level.
// BEFORE - Function inside component
function DataTable({ data }) {
const formatDate = (date) => new Date(date).toLocaleDateString();
const formattedData = useMemo(() => {
return data.map(item => ({
...item,
date: formatDate(item.date)
}));
}, [data, formatDate]); // formatDate is a dependency
return <table>{/* render formattedData */}</table>;
}
// AFTER - Function extracted outside
const formatDate = (date) => new Date(date).toLocaleDateString();
function DataTable({ data }) {
const formattedData = useMemo(() => {
return data.map(item => ({
...item,
date: formatDate(item.date)
}));
}, [data]); // formatDate not needed - it's stable
return <table>{/* render formattedData */}</table>;
}Instead of depending on an entire object, destructure and depend only on the specific properties you use. This prevents unnecessary re-runs when unrelated properties change.
// BEFORE - Depends on entire user object
function UserGreeting({ user }) {
useEffect(() => {
console.log(`Hello ${user.name}`);
}, [user]); // Re-runs when any user property changes
return <div>Welcome {user.name}</div>;
}
// AFTER - Depends only on name property
function UserGreeting({ user }) {
const { name } = user;
useEffect(() => {
console.log(`Hello ${name}`);
}, [name]); // Only re-runs when name changes
return <div>Welcome {name}</div>;
}As a last resort, you can disable the ESLint rule for a specific line using a comment. However, this should be rare and well-documented, as it can hide real bugs.
function AnalyticsTracker() {
useEffect(() => {
// This should only run once on mount to initialize analytics
initializeAnalytics();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Intentionally empty - we want this to run only once
return null;
}Before disabling, ask yourself: "What breaks if I add the dependency?" If adding it causes problems, the real issue is likely in your code structure, not the ESLint rule.
The exhaustive-deps rule is one of the most valuable ESLint rules for React developers because it catches bugs that are extremely difficult to debug in production. Stale closures can cause intermittent bugs that only appear under specific user interaction patterns. In React 18+, the new useEffectEvent hook (currently experimental) is designed to solve cases where you need to reference values without adding them as dependencies. For complex dependencies like objects or arrays, consider using deep comparison hooks from libraries like react-use or ahooks if you truly need to compare values rather than references. When working with third-party libraries, some may not follow React hooks best practices, causing unavoidable exhaustive-deps warnings. In these cases, wrapping the library call in your own hook with proper dependencies is often the solution. The React team intentionally made this rule strict because the alternative - allowing missing dependencies - leads to subtle timing bugs that can go unnoticed for months. If you find yourself frequently fighting this rule, it often indicates that your component is doing too much and would benefit from being split into smaller, more focused components.
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