This ESLint warning occurs when your useEffect hook references variables, props, or state that are not included in its dependency array. When dependencies are missing, your effect may run with stale values or fail to re-run when those values change, leading to bugs and unexpected behavior.
The "React Hook useEffect has a missing dependency" warning is triggered by ESLint's react-hooks/exhaustive-deps rule, not by React itself. This rule analyzes your useEffect hooks and compares the variables, props, state, and functions referenced inside the effect against the dependency array you provided. When you use a value inside useEffect that can change over time (like props, state, or functions defined in the component), React needs to know about it through the dependency array. This tells React when to re-run the effect. If you omit a dependency, the effect will continue using the old value from when it first ran, even after the value has changed. This can lead to subtle bugs where your UI doesn't update correctly, API calls use stale parameters, or event handlers reference old state. The ESLint rule exists to catch these issues before they become runtime bugs.
Review your useEffect code and list every variable that comes from outside the effect scope. This includes props, state, context values, and functions.
function MyComponent({ userId, onUpdate }) {
const [data, setData] = useState(null);
useEffect(() => {
// userId, onUpdate, and fetchData are dependencies
fetchData(userId).then(result => {
setData(result);
onUpdate(result);
});
}, []); // ❌ Missing dependencies!
}Look for variables that can change between renders. Constants defined outside the component are safe to omit.
Include every dependency in the array. This is the most straightforward fix.
function MyComponent({ userId, onUpdate }) {
const [data, setData] = useState(null);
useEffect(() => {
fetchData(userId).then(result => {
setData(result);
onUpdate(result);
});
}, [userId, onUpdate]); // ✅ Dependencies added
}The effect will now re-run whenever userId or onUpdate changes.
If you're using derived values or transformations, move them inside useEffect to reduce dependencies.
// Before: external calculation creates dependency
function MyComponent({ user }) {
const userName = user.firstName + ' ' + user.lastName;
useEffect(() => {
console.log(userName);
}, [userName]); // userName recreated every render
}
// After: calculation inside effect
function MyComponent({ user }) {
useEffect(() => {
const userName = user.firstName + ' ' + user.lastName;
console.log(userName);
}, [user]); // Only depend on user object
}If your effect depends on a function, wrap it with useCallback to prevent unnecessary re-runs.
function MyComponent({ userId }) {
const [data, setData] = useState(null);
// Without useCallback: function recreated every render
const loadData = useCallback(async () => {
const result = await fetchData(userId);
setData(result);
}, [userId]); // loadData only changes when userId changes
useEffect(() => {
loadData();
}, [loadData]); // ✅ Safe dependency
}This ensures the function reference stays stable unless its dependencies change.
If a value never changes, move it outside the component to prove it's not reactive.
// Move constants outside
const API_ENDPOINT = 'https://api.example.com';
const DEFAULT_OPTIONS = { timeout: 5000 };
function MyComponent({ userId }) {
useEffect(() => {
fetch(`${API_ENDPOINT}/users/${userId}`, DEFAULT_OPTIONS);
}, [userId]); // Only userId is a dependency
}Constants defined outside the component don't need to be in the dependency array.
When updating state based on previous state, use the functional form to remove the state dependency.
function Counter({ increment }) {
const [count, setCount] = useState(0);
// Before: depends on count
useEffect(() => {
const timer = setInterval(() => {
setCount(count + increment);
}, 1000);
return () => clearInterval(timer);
}, [count, increment]); // Resets timer every second!
// After: no count dependency needed
useEffect(() => {
const timer = setInterval(() => {
setCount(prevCount => prevCount + increment);
}, 1000);
return () => clearInterval(timer);
}, [increment]); // ✅ Only increment is needed
}Check your code editor and build output to confirm the ESLint warning is gone.
# Run ESLint to check for warnings
npm run lint
# Or build the project
npm run buildThe warning should no longer appear. Test that your component behaves correctly by changing the dependency values and verifying the effect re-runs as expected.
When to disable the rule: In rare cases, you may intentionally want an effect to run only once with specific values. Before disabling, ensure you understand the risks:
useEffect(() => {
// I want this to run only on mount, using initial props
logAnalytics(initialUserId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);Understanding effect timing: Effects with different dependencies run at different times. An empty array [] means "run once after mount", while [value] means "run after mount and whenever value changes".
Objects and arrays as dependencies: Comparing objects and arrays by reference can cause infinite loops. Use useMemo to stabilize them:
const config = useMemo(() => ({
apiKey: key,
timeout: 3000
}), [key]);
useEffect(() => {
initializeAPI(config);
}, [config]);ESLint configuration: The exhaustive-deps rule is part of eslint-plugin-react-hooks. Ensure you have it installed and configured in .eslintrc:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/exhaustive-deps": "warn"
}
}React 18+ considerations: With concurrent features, stale closures become even more problematic. Always include dependencies to ensure your effects work correctly with concurrent rendering and automatic batching.
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