This ESLint warning appears when a context value from useContext is used inside useEffect or another hook without being included in the dependency array. React needs to track context values as dependencies to ensure effects re-run when the context changes.
The "react-hooks/exhaustive-deps" ESLint rule warns you when a value used inside a React hook (like useEffect, useMemo, or useCallback) is not included in its dependency array. When you consume a context value using useContext() and then use that value inside another hook, React's linter flags it as a missing dependency because context values are reactive - they can change over time. Context values are treated the same as props or state: if any variable is used inside a hook but is defined outside of it, it should be included in the dependency array. This ensures your hook "reacts" to every change of that value. If you don't include the context value in the dependency array, your effect may use stale data or fail to update when the context changes. The warning exists to prevent bugs where your component doesn't properly respond to context updates. React compares dependencies using Object.is comparison, and when the context value changes, any effects depending on it should re-run to stay synchronized with the current state.
The simplest and most correct solution is to include the context value in your dependency array:
import { useContext, useEffect } from 'react';
import { UserContext } from './UserContext';
function MyComponent() {
const userContext = useContext(UserContext);
useEffect(() => {
if (userContext.isAuthenticated) {
// Do something with userContext
console.log('User:', userContext.user);
}
}, [userContext]); // Include the entire context
return <div>...</div>;
}If you only use specific properties, destructure and include only those:
useEffect(() => {
if (isAuthenticated) {
console.log('User:', user);
}
}, [isAuthenticated, user]); // More specific dependenciesTo prevent unnecessary effect triggers, wrap your context value with useMemo in the provider:
import { createContext, useMemo, useState } from 'react';
export const UserContext = createContext(null);
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
// Memoize the entire context value
const contextValue = useMemo(() => ({
user,
isAuthenticated,
setUser,
setIsAuthenticated
}), [user, isAuthenticated]);
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
);
}This ensures the context value only changes when its actual data changes, not on every provider render.
If your context provides functions, wrap them with useCallback to maintain stable references:
import { createContext, useCallback, useMemo, useState } from 'react';
export function UserProvider({ children }) {
const [user, setUser] = useState(null);
// Memoize functions
const login = useCallback((userData) => {
setUser(userData);
}, []);
const logout = useCallback(() => {
setUser(null);
}, []);
const contextValue = useMemo(() => ({
user,
login,
logout
}), [user, login, logout]);
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
);
}If a value doesn't need to be a dependency, move its declaration inside the effect:
function MyComponent() {
const { apiUrl } = useContext(ConfigContext);
useEffect(() => {
// Move derived values inside
const endpoint = `${apiUrl}/users`;
fetch(endpoint)
.then(res => res.json())
.then(data => console.log(data));
}, [apiUrl]); // Only include what actually changes
}This reduces the number of dependencies and makes the effect more focused.
Ensure you have the React Hooks ESLint plugin installed and configured:
npm install eslint-plugin-react-hooks --save-devIn your ESLint config (.eslintrc.js or .eslintrc.json):
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}The exhaustive-deps rule helps catch these issues automatically and suggests fixes.
If your context contains both stable configuration and frequently-changing state, split them:
// Stable config context
export const ConfigContext = createContext({ apiUrl: '', theme: 'light' });
// Frequently-changing state context
export const UserStateContext = createContext({ user: null });
function MyComponent() {
const config = useContext(ConfigContext);
const userState = useContext(UserStateContext);
useEffect(() => {
// Effect only re-runs when config changes, not on every user state change
console.log('API URL:', config.apiUrl);
}, [config]);
}This prevents unnecessary re-renders in components that only need stable values.
When to suppress the warning: Only disable the exhaustive-deps rule with // eslint-disable-next-line react-hooks/exhaustive-deps if you have a specific reason and understand the implications. Common valid cases include effects that should only run once on mount or when you're implementing custom dependency logic. However, examine your code first to find a better solution.
Shallow equality checking: React uses Object.is comparison for dependencies. If your context value is an object that gets recreated on every render (even with identical properties), it will trigger effects unnecessarily. Always use useMemo for object and array context values.
Context value stability impact: A change in context values triggers dependency arrays in every context consumer. Memoizing in the provider is highly effective since you only memoize once and it benefits all consumers. This is especially important in large applications where a context might have dozens of consumers.
Performance considerations: If you notice performance issues with context causing too many re-renders, consider using a state management library like Zustand or Redux, or split your context into smaller, more focused contexts using the single-responsibility principle.
TypeScript typing: When using TypeScript, properly type your context to get better autocomplete and catch dependency issues at compile time. Create interfaces for your context shape and use them consistently.
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