This ESLint error occurs when useEffect or other React Hooks are called conditionally, inside loops, or after early returns. React requires all Hooks to be called in the same order on every render to maintain internal state consistency.
This error is triggered by the eslint-plugin-react-hooks linting rule that enforces React's fundamental Rules of Hooks. The error appears when you place a Hook call (like useEffect, useState, useCallback, etc.) inside a conditional statement, loop, or nested function, or after an early return statement. React Hooks rely on call order to maintain their internal state between renders. Each Hook call is tracked by React using a numeric index based on the order in which Hooks are called. If the order changes between renders—such as when a Hook is conditionally called—React loses track of which state belongs to which Hook, causing bugs and unexpected behavior. The eslint-plugin-react-hooks package, developed by the React team, automatically detects these violations during development to prevent runtime bugs that would be difficult to debug.
The simplest fix is to ensure all Hooks are called at the top of your component function, before any conditional logic or early returns.
Before (incorrect):
function MyComponent({ isEnabled }) {
if (!isEnabled) {
return <div>Disabled</div>;
}
// ❌ Error: Hook called after early return
useEffect(() => {
console.log('Component mounted');
}, []);
return <div>Enabled</div>;
}After (correct):
function MyComponent({ isEnabled }) {
// ✅ Hook called at top level
useEffect(() => {
console.log('Component mounted');
}, []);
if (!isEnabled) {
return <div>Disabled</div>;
}
return <div>Enabled</div>;
}Instead of conditionally calling the Hook, call it unconditionally but add your condition inside the Hook's callback. This ensures the Hook runs on every render while the logic executes conditionally.
Before (incorrect):
function MyComponent({ shouldFetch, userId }) {
// ❌ Error: Hook called conditionally
if (shouldFetch) {
useEffect(() => {
fetchUserData(userId);
}, [userId]);
}
return <div>Content</div>;
}After (correct):
function MyComponent({ shouldFetch, userId }) {
// ✅ Hook always called, condition inside
useEffect(() => {
if (!shouldFetch) {
return; // Early return inside the effect
}
fetchUserData(userId);
}, [shouldFetch, userId]);
return <div>Content</div>;
}When you need to conditionally use Hooks based on props or state, create a child component that contains the Hook calls, then conditionally render that component.
Before (incorrect):
function Dashboard({ user }) {
if (user.isAdmin) {
// ❌ Error: Hook called conditionally
useEffect(() => {
loadAdminData();
}, []);
}
return <div>Dashboard</div>;
}After (correct):
function AdminEffects() {
// ✅ Hook always called in this component
useEffect(() => {
loadAdminData();
}, []);
return null; // This component only runs effects
}
function Dashboard({ user }) {
return (
<div>
{user.isAdmin && <AdminEffects />}
Dashboard
</div>
);
}For effects that should only run when certain values change, rely on the dependency array instead of conditional Hook calls.
Before (incorrect):
function SearchResults({ query }) {
// ❌ Error: Hook called conditionally
if (query.length > 0) {
useEffect(() => {
searchAPI(query);
}, [query]);
}
return <div>Results</div>;
}After (correct):
function SearchResults({ query }) {
// ✅ Hook always called, dependencies control execution
useEffect(() => {
if (query.length === 0) {
return;
}
searchAPI(query);
}, [query]); // Effect re-runs when query changes
return <div>Results</div>;
}Why Hook Order Matters:
React doesn't use names or IDs to track Hooks—it uses the order in which they're called. React maintains an internal array of Hook state, and each call to a Hook like useState or useEffect corresponds to an index in that array. When Hook calls are conditional, the indices shift between renders, causing React to return the wrong state values.
ESLint Configuration:
The react-hooks/rules-of-hooks rule is part of eslint-plugin-react-hooks. Ensure your ESLint config includes:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}Custom Hooks Follow the Same Rules:
Custom Hooks (functions starting with "use") must also follow these rules. Any Hook calls inside your custom Hooks must be unconditional:
// ❌ Incorrect
function useCustomHook(shouldRun) {
if (shouldRun) {
useEffect(() => { /* ... */ }, []);
}
}
// ✅ Correct
function useCustomHook(shouldRun) {
useEffect(() => {
if (!shouldRun) return;
// ... effect logic
}, [shouldRun]);
}False Positives:
Occasionally, ESLint may flag code that appears conditional but actually isn't (e.g., after complex control flow). In rare cases where you're certain the code is correct, you can disable the rule for that line:
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => { /* ... */ }, []);However, this should be a last resort—most of the time, the linter is correctly identifying a real issue.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React
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