The error React Hook “useState” is called conditionally. React Hooks must be called in the exact same order in every component render is one of the most common React mistakes. It violates the Rules of Hooks. Here’s why it happens and how to fix every variant.
๐ Table of Contents
- Why This Rule Exists
- The Broken Code
- Fix 1: Call Hooks First, Condition Later
- Fix 2: Hook Inside a Condition Block
- Fix 3: Hook Inside a Loop
- Fix 4: Hook After an Early Return
- The Two Rules of Hooks
- Handling Conditional Logic Correctly
- Enforce the Rules with ESLint
- Frequently Asked Questions
- Conclusion
Why This Rule Exists
React tracks hooks by their call order, not by name. On every render, React expects the same hooks in the same sequence. If a hook is inside a condition, it might run on one render but not the next โ breaking React’s internal tracking and causing state to be assigned to the wrong hook.
The Broken Code
// ๐ Hook inside a condition โ VIOLATES rules of hooks
function Profile({ userId }) {
if (!userId) {
return <div>No user</div>; // early return BEFORE hooks
}
const [user, setUser] = useState(null); // โ conditionally called
// If userId is falsy, this hook is skipped โ breaks React's tracking
return <div>{user?.name}</div>;
}
Fix 1: Call Hooks First, Condition Later
// โ
All hooks at the top, unconditionally
function Profile({ userId }) {
const [user, setUser] = useState(null); // always called
// Conditional logic AFTER all hooks
if (!userId) {
return <div>No user</div>;
}
return <div>{user?.name}</div>;
}
Fix 2: Hook Inside a Condition Block
// ๐ useEffect inside an if
function Component({ isActive }) {
if (isActive) {
useEffect(() => { // โ conditional hook
console.log('active');
}, []);
}
}
// โ
Move the condition INSIDE the hook
function Component({ isActive }) {
useEffect(() => {
if (isActive) { // condition inside the hook
console.log('active');
}
}, [isActive]);
}
Fix 3: Hook Inside a Loop
// ๐ Hook in a loop โ order changes if items.length changes
function List({ items }) {
return items.map(item => {
const [selected, setSelected] = useState(false); // โ in a loop
return <Item key={item.id} />;
});
}
// โ
Extract into a child component โ each has its own hooks
function List({ items }) {
return items.map(item => <Item key={item.id} item={item} />);
}
function Item({ item }) {
const [selected, setSelected] = useState(false); // โ
top level of component
return <div onClick={() => setSelected(!selected)}>{item.name}</div>;
}
Fix 4: Hook After an Early Return
// ๐ Hook after a conditional return
function Widget({ data }) {
const [count, setCount] = useState(0);
if (!data) return null; // early return
const [name, setName] = useState(''); // โ never reached when !data
return <div>{name}: {count}</div>;
}
// โ
ALL hooks before ANY conditional return
function Widget({ data }) {
const [count, setCount] = useState(0);
const [name, setName] = useState(''); // moved up
if (!data) return null; // early return AFTER all hooks
return <div>{name}: {count}</div>;
}
The Two Rules of Hooks
- Only call hooks at the top level โ never inside conditions, loops, or nested functions. Always in the same order.
- Only call hooks from React functions โ React components or custom hooks (functions starting with
use). Not from regular JavaScript functions.
Handling Conditional Logic Correctly
function Dashboard({ user, showStats }) {
// โ
Always call all hooks unconditionally
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Conditions go INSIDE the hook, not around it
if (showStats) {
fetchStats().then(setData).finally(() => setLoading(false));
} else {
setLoading(false);
}
}, [showStats]);
// Conditional RENDERING is fine โ it's not a conditional hook
if (!user) return <Login />;
if (loading) return <Spinner />;
return showStats ? <Stats data={data} /> : <Overview />;
}
Enforce the Rules with ESLint
npm install --save-dev eslint-plugin-react-hooks
// .eslintrc โ catches these errors before runtime
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
Frequently Asked Questions
Q: Why can’t hooks be conditional? It seems arbitrary.
A: React identifies hooks by their call order (first useState, second useState, etc.), not by name. If a hook is sometimes skipped, the order shifts and React assigns state to the wrong hook. Consistent order is what makes hooks work.
Q: Can I use conditions inside a hook?
A: Yes! Conditions INSIDE a hook (inside the useEffect callback, for example) are fine. The rule only forbids conditionally CALLING the hook itself.
Q: How do I conditionally run an effect?
A: Put the condition inside the effect body and include the condition variable in the dependency array: useEffect(() => { if (cond) {...} }, [cond]).
Q: What about conditional rendering โ is that allowed?
A: Yes. Conditional rendering (returning different JSX based on state) is completely fine, as long as all hooks are called before any conditional return. Rendering conditions aren’t hook conditions.
Q: Can I call a custom hook conditionally?
A: No โ custom hooks follow the same rules since they call hooks internally. Call them unconditionally at the top level, and put conditions inside them.
Conclusion
The “React Hook is called conditionally” error means a hook is inside a condition, loop, or after an early return. The fix is always the same: call ALL hooks unconditionally at the top of your component, before any conditional returns or logic. Put conditions INSIDE hooks (in the effect body), not around them. Install eslint-plugin-react-hooks to catch these mistakes automatically before they reach runtime โ it’s the single best safeguard against hook rule violations.
๐ You might also like
๐ Share this article




โ๏ธ Leave a Comment