This ESLint error occurs when a React Hook like useContext is called inside a conditional statement, loop, or nested function. React requires all Hooks to be called in the same order on every render to maintain state consistency.
This error is triggered by the eslint-plugin-react-hooks rule "rules-of-hooks" when it detects a Hook being called conditionally. React Hooks rely on a consistent call order to associate each Hook with its internal state. When you call a Hook inside an if statement, loop, or nested function, that Hook might be skipped on some renders, breaking React's ability to track which state belongs to which Hook call. React maintains an internal linked list of Hooks for each component. Every time your component renders, React expects the same Hooks to be called in the same order. If a Hook is sometimes present and sometimes absent (due to conditional execution), the positions in this list shift, causing React to lose track of the state and potentially leading to bugs that are extremely difficult to debug. This rule applies to all Hooks: useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, and any custom Hooks you create. The ESLint plugin enforces this rule at build time to prevent these runtime errors.
Ensure all Hooks are called at the very beginning of your function component, before any conditional logic or early returns.
Before (incorrect):
function MyComponent({ isLoggedIn }) {
if (isLoggedIn) {
const user = useContext(UserContext); // ❌ Conditional Hook call
return <div>Welcome {user.name}</div>;
}
return <div>Please log in</div>;
}After (correct):
function MyComponent({ isLoggedIn }) {
const user = useContext(UserContext); // ✅ Always called
if (isLoggedIn) {
return <div>Welcome {user.name}</div>;
}
return <div>Please log in</div>;
}Call the Hook unconditionally, then use conditional logic with the value it returns.
Before (incorrect):
function UserProfile({ showTheme }) {
if (showTheme) {
const theme = useContext(ThemeContext); // ❌ Conditional Hook call
return <div className={theme}>Profile</div>;
}
return <div>Profile</div>;
}After (correct):
function UserProfile({ showTheme }) {
const theme = useContext(ThemeContext); // ✅ Always called
return <div className={showTheme ? theme : ''}>Profile</div>;
}Don't conditionally call useEffect. Instead, call it unconditionally and handle conditions inside the effect function.
Before (incorrect):
function DataFetcher({ shouldFetch }) {
if (shouldFetch) {
useEffect(() => { // ❌ Conditional Hook call
fetchData();
}, []);
}
}After (correct):
function DataFetcher({ shouldFetch }) {
useEffect(() => { // ✅ Always called
if (shouldFetch) {
fetchData();
}
}, [shouldFetch]);
}If you need truly different Hook behavior based on conditions, split your component into smaller components where each always calls the same Hooks.
Before (incorrect):
function Dashboard({ userType }) {
if (userType === 'admin') {
const adminData = useContext(AdminContext); // ❌ Conditional Hook call
return <AdminDashboard data={adminData} />;
}
return <UserDashboard />;
}After (correct):
function AdminDashboardWrapper() {
const adminData = useContext(AdminContext); // ✅ Always called in this component
return <AdminDashboard data={adminData} />;
}
function Dashboard({ userType }) {
if (userType === 'admin') {
return <AdminDashboardWrapper />;
}
return <UserDashboard />;
}Ensure your ESLint configuration has the react-hooks plugin enabled to catch these errors during development.
Add to your .eslintrc.js or eslintrc.json:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}If using Create React App, this is included by default. For other setups, install the plugin:
npm install eslint-plugin-react-hooks --save-devWhy React enforces Hook order:
React doesn't use names or identifiers to track Hooks. Instead, it relies entirely on call order. Each Hook call gets the next slot in an internal array. This design allows Hooks to work without requiring you to manually specify unique keys, but it also means the order must never change between renders.
Custom Hooks follow the same rules:
Custom Hooks are just functions that call built-in Hooks, so they must also be called unconditionally:
// ❌ Wrong
if (condition) {
useCustomHook();
}
// ✅ Correct
useCustomHook(); // Then handle condition inside the custom HookEarly returns are safe after all Hooks:
You can return early from a component as long as all Hooks have already been called:
function Component({ user }) {
const theme = useContext(ThemeContext); // ✅ Called first
const [count, setCount] = useState(0); // ✅ Called second
if (!user) {
return null; // ✅ Safe - all Hooks already called
}
return <div className={theme}>{count}</div>;
}Performance considerations:
Calling Hooks unconditionally doesn't hurt performance. Hooks are extremely lightweight, and calling them without using their values is negligible. React's reconciliation optimizations handle this efficiently.
TypeScript and type narrowing:
When you move a Hook call outside a conditional, you might need to handle null/undefined cases in your types:
function Component({ enabled }: { enabled: boolean }) {
const value = useContext(MyContext); // Always called, might be null
if (enabled && value) {
// TypeScript knows value is non-null here
return <div>{value.data}</div>;
}
return null;
}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