This ESLint error occurs when useState is called inside a conditional statement, loop, or after an early return. React requires hooks to be called in the same order on every render to maintain state consistency.
This error is reported by the eslint-plugin-react-hooks when it detects a useState hook being called conditionally. React hooks rely on call order to associate state with specific hook calls across renders. When you call useState inside an if statement, loop, or after an early return, the hook might not execute on every render, breaking React's internal tracking mechanism. React uses a linked list internally to track hooks in the order they're called. If the order changes between renders because a conditional statement prevents a hook from executing, React can't match the stored state to the correct hook call, leading to bugs or crashes. The eslint-plugin-react-hooks enforces the Rules of Hooks to prevent these issues at development time, catching violations before they cause runtime errors.
Always call hooks at the top level of your function component, before any conditional logic or early returns:
// ❌ WRONG - useState inside conditional
function MyComponent({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null); // Error!
}
// ...
}
// ✅ CORRECT - useState at top level
function MyComponent({ isLoggedIn }) {
const [user, setUser] = useState(null);
if (isLoggedIn) {
// Use the state here
}
// ...
}This ensures hooks are called in the same order every render.
Instead of conditionally calling the hook, always call it and conditionally use its value:
// ❌ WRONG
function UserProfile({ showGreeting }) {
if (showGreeting) {
const [greeting, setGreeting] = useState('Hello'); // Error!
}
return <div>Profile</div>;
}
// ✅ CORRECT - Hook always called, usage is conditional
function UserProfile({ showGreeting }) {
const [greeting, setGreeting] = useState('Hello');
return (
<div>
{showGreeting && <h1>{greeting}</h1>}
<div>Profile</div>
</div>
);
}If you need hooks to run only in certain conditions, create a separate component:
// ❌ WRONG
function Dashboard({ userType }) {
if (userType === 'admin') {
const [adminData, setAdminData] = useState([]); // Error!
}
return <div>Dashboard</div>;
}
// ✅ CORRECT - Separate component
function AdminPanel() {
const [adminData, setAdminData] = useState([]);
// Admin-specific logic
return <div>Admin Panel: {adminData.length} items</div>;
}
function Dashboard({ userType }) {
return (
<div>
<h1>Dashboard</h1>
{userType === 'admin' && <AdminPanel />}
</div>
);
}This pattern keeps hooks at the top level of their respective components.
Initialize state with appropriate defaults and update conditionally:
// ❌ WRONG
function DataLoader({ shouldLoad }) {
if (shouldLoad) {
const [data, setData] = useState(null); // Error!
const [loading, setLoading] = useState(false); // Error!
}
return null;
}
// ✅ CORRECT - Always call hooks with appropriate defaults
function DataLoader({ shouldLoad }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!shouldLoad) return; // Conditional logic inside useEffect
setLoading(true);
fetchData().then(result => {
setData(result);
setLoading(false);
});
}, [shouldLoad]);
if (!shouldLoad) return null;
if (loading) return <div>Loading...</div>;
return <div>{data}</div>;
}Ensure all hook calls happen before any return statements:
// ❌ WRONG - Hook after early return
function ErrorBoundary({ error, children }) {
if (error) {
return <div>Error: {error.message}</div>;
}
const [retryCount, setRetryCount] = useState(0); // Error! Never reached if error exists
return children;
}
// ✅ CORRECT - All hooks before any returns
function ErrorBoundary({ error, children }) {
const [retryCount, setRetryCount] = useState(0);
if (error) {
return (
<div>
Error: {error.message}
<button onClick={() => setRetryCount(c => c + 1)}>
Retry ({retryCount})
</button>
</div>
);
}
return children;
}Understanding React's Hook State Machine
React maintains hooks in a linked list indexed by call order. Each render, React traverses this list in sequence. If a hook is skipped due to a conditional, the indices shift, causing React to associate state with the wrong hook calls.
ESLint Configuration
The rules-of-hooks rule is part of eslint-plugin-react-hooks. Ensure it's enabled in your ESLint config:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error"
}
}Custom Hooks and Conditionals
Custom hooks must also follow these rules. If you need conditional logic, wrap it in a custom hook that always calls its internal hooks:
// ✅ Custom hook that always calls useState
function useConditionalState(condition, initialValue) {
const [state, setState] = useState(initialValue);
return condition ? [state, setState] : [null, () => {}];
}TypeScript Considerations
When using TypeScript with conditional rendering patterns, ensure type guards don't introduce hook call order issues. Always call hooks before type narrowing logic.
React Compiler Future
The upcoming React Compiler may optimize some of these patterns, but the fundamental rule of consistent hook call order will remain a requirement for the foreseeable future.
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