The "React Hook is called conditionally" error occurs when you call a React Hook inside a conditional statement, loop, or nested function. React requires that Hooks are always called in the same order on every render "" this is one of the fundamental Rules of Hooks.
// Wrong Problem "" Hook inside condition
function App({ isLoggedIn }) {
if (isLoggedIn) {
const [user, setUser] = useState(null); // Error!
}
}
// Correct Solution "" always call Hook at top level
function App({ isLoggedIn }) {
const [user, setUser] = useState(null); // Always called
if (!isLoggedIn) return null; // Condition after hooks
}
function UserProfile({ showDetails }) {
if (showDetails) {
const [details, setDetails] = useState({}); // Wrong Conditional hook!
useEffect(() => { fetchDetails(); }, []); // Wrong Conditional hook!
}
return <div>Profile</div>;
}
function UserProfile({ showDetails }) {
// Correct Always call hooks at the top level
const [details, setDetails] = useState({});
useEffect(() => {
if (showDetails) { // Correct Condition inside the hook
fetchDetails().then(setDetails);
}
}, [showDetails]);
return <div>{showDetails && <p>{details.bio}</p>}</div>;
}
function Dashboard({ user }) {
if (!user) return <p>Please log in</p>; // Early return
const [data, setData] = useState([]); // Wrong Hook after return!
useEffect(() => { fetchData(); }, []); // Wrong Hook after return!
return <div>{data.map(item => <p>{item}</p>)}</div>;
}
function Dashboard({ user }) {
// Correct All hooks BEFORE any return
const [data, setData] = useState([]);
useEffect(() => {
if (user) fetchData().then(setData); // Condition inside effect
}, [user]);
if (!user) return <p>Please log in</p>; // Return AFTER hooks
return <div>{data.map(item => <p>{item}</p>)}</div>;
}
function ItemList({ items }) {
return items.map(item => {
const [selected, setSelected] = useState(false); // Wrong Hook in loop!
return <div onClick={() => setSelected(!selected)}>{item}</div>;
});
}
// Correct Extract to a separate component
function Item({ item }) {
const [selected, setSelected] = useState(false); // Correct Top level
return (
<div onClick={() => setSelected(!selected)}>
{item} {selected ? '\u2713' : ''}
</div>
);
}
function ItemList({ items }) {
return items.map(item => <Item key={item.id} item={item} />);
}
function App({ isAdmin }) {
if (isAdmin) {
const adminData = useAdminData(); // Wrong Conditional custom hook!
}
}
// Correct Always call the hook, pass condition as parameter
function App({ isAdmin }) {
const adminData = useAdminData(isAdmin); // Hook decides internally
}
// Inside useAdminData:
function useAdminData(isAdmin) {
const [data, setData] = useState(null);
useEffect(() => {
if (isAdmin) fetchAdminData().then(setData); // Condition inside
}, [isAdmin]);
return data;
}
React associates each Hook call with its position in the component call sequence. If a condition, loop, early return, nested function, or try block changes that sequence between renders, later Hook state is read from the wrong position. The rule protects state identity; it is not only a lint preference.
Keep Hook calls at the top level of a component or custom Hook. Put the condition inside the effect, event handler, memoized calculation, or returned JSX. An early return is safe only after every Hook that participates in the component has been called in the same order.
For an optional subscription, call the effect every render and return without subscribing when disabled. For separate feature modes with different state, render separate child components so each component has its own stable Hook sequence. A custom Hook follows the same rules as a component and must not hide conditional calls.
Run the Hooks lint rules in development and test transitions across every condition that previously changed the call order. Strict Mode can expose unsafe effects, but the definitive correction is a stable call sequence with complete cleanup.
Try this next
0 of 2 completed
React associates hook state by call order. If one render calls useState, then useEffect, and the next render skips the useState because an if branch changed, every later hook shifts position.
Usually you still call the hook, then make the work conditional. For useEffect, write the guard inside the effect: if (!enabled) return; then run the subscription or fetch. For a custom hook, pass enabled as an option and let the hook decide whether to do anything.
Do not call hooks directly inside map, for, or while in the same component. The number and order of calls can change when the list length changes or items are filtered. Instead, render a child component for each item and put the hook inside that child component.
Explore 500+ free tutorials across 20+ languages and frameworks.