This React warning occurs when you call hooks inside conditions, loops, or after early returns, violating the Rules of Hooks. React requires hooks to be called in the same order on every render to maintain state consistency.
This warning occurs when you violate one of React's fundamental Rules of Hooks by calling a hook conditionally. React Hooks must always be called at the top level of your component function, before any early returns, and in the same order on every render. React uses the order of hook calls to track state and effects between renders. When you call hooks conditionally or after early returns, React can't guarantee the same call order on every render, breaking its internal state management system. React stores hooks in a linked list, with each hook call adding a new object in a strict sequence. If this sequence changes between renders, React loses track of which state belongs to which hook. This rule applies to all hooks including useState, useEffect, useContext, useCallback, useMemo, useRef, and custom hooks. Breaking this rule can lead to state corruption, incorrect behavior, and hard-to-debug issues in your application.
Ensure all hooks are called before any conditional logic or early returns. This is the most common fix.
Before (incorrect):
function MyComponent({ isLoggedIn }) {
if (!isLoggedIn) {
return <Login />;
}
// ❌ Hook called after conditional return
const [data, setData] = useState(null);
return <Dashboard data={data} />;
}After (correct):
function MyComponent({ isLoggedIn }) {
// ✅ Hook called at the top level
const [data, setData] = useState(null);
if (!isLoggedIn) {
return <Login />;
}
return <Dashboard data={data} />;
}If you need conditional behavior, put the condition inside the hook rather than conditionally calling the hook.
For useEffect:
// ❌ Wrong - conditionally calling the hook
if (shouldFetch) {
useEffect(() => {
fetchData();
}, []);
}
// ✅ Correct - condition inside the hook
useEffect(() => {
if (shouldFetch) {
fetchData();
}
}, [shouldFetch]);
// ✅ Also correct - early return inside the hook
useEffect(() => {
if (!shouldFetch) return;
fetchData();
}, [shouldFetch]);When you need conditional initialization, use a default value that works for all cases.
// ❌ Wrong - conditionally calling useState
function UserProfile({ userId }) {
if (userId) {
const [user, setUser] = useState(null);
}
// ...
}
// ✅ Correct - always call useState
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
if (userId) {
fetchUser(userId).then(setUser);
}
}, [userId]);
if (!user) return <Loading />;
return <div>{user.name}</div>;
}If your component has fundamentally different logic based on conditions, consider splitting it into separate components.
// ❌ Wrong - trying to use hooks conditionally
function DashboardOrLogin({ isLoggedIn }) {
if (isLoggedIn) {
const [data, setData] = useState(null);
return <Dashboard data={data} />;
} else {
const [email, setEmail] = useState('');
return <Login email={email} onChange={setEmail} />;
}
}
// ✅ Correct - split into separate components
function Dashboard() {
const [data, setData] = useState(null);
return <div>{/* dashboard UI */}</div>;
}
function Login() {
const [email, setEmail] = useState('');
return <form>{/* login form */}</form>;
}
function App({ isLoggedIn }) {
return isLoggedIn ? <Dashboard /> : <Login />;
}Use the official ESLint plugin to catch these errors during development.
Install the plugin:
npm install eslint-plugin-react-hooks --save-devConfigure in .eslintrc.json:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}This will show errors in your IDE before you even run the code, catching violations of the Rules of Hooks immediately.
Why React uses call order instead of IDs:
React deliberately chose to track hooks by their call order rather than by unique IDs because it keeps the hooks API simple and prevents naming conflicts. Using call order means you don't need to name each hook call, and React can automatically manage the state array efficiently.
Custom hooks must follow the same rules:
When you create custom hooks, they must also follow the Rules of Hooks. Any hook that calls other hooks internally must be called unconditionally. The hook name must start with "use" so ESLint can enforce these rules.
Loops and hooks:
Never call hooks inside loops because the number of iterations might change between renders:
// ❌ Wrong
items.map(item => {
const [state, setState] = useState(item.value);
// ...
});
// ✅ Correct - one component per item
items.map(item => <ItemComponent key={item.id} item={item} />);Try-catch and hooks:
Avoid wrapping hook calls in try-catch blocks where the execution path might vary. Instead, handle errors inside the hook:
// ✅ Handle errors inside useEffect
useEffect(() => {
try {
fetchData();
} catch (error) {
console.error(error);
}
}, []);React Compiler (future):
The upcoming React Compiler may help catch some of these issues at build time, but the Rules of Hooks will still apply as they're fundamental to how React works.
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