LEARN · DEBUGGING GUIDE

Diagnosing ‘React Hook Called Conditionally’ in Function Components

React will break if hooks are called inside conditions, but the why isn’t always obvious. Here’s how to find and fix every subtle instance, fast.

BeginnerReact bugs4 min read

What this usually means

React hooks (useState, useEffect, useContext, etc.) must run in the same order every render. Calling a hook inside an if/else, loop, or a nested function breaks the call order, causing React to misalign stateful logic. Even a seemingly harmless conditional, like inside an early return or short-circuit operator, will trigger this error. The violation often lurks under refactors or when hooks are called in helper functions triggered by conditions.

( 01 )Fast diagnosis

The first ten minutes — establish facts before touching code.

  • 1Reproduce the bug and copy the full error message—note the exact hook name and file/line number.
  • 2Search for 'use' keywords in the indicated component (e.g., grep -n 'use' MyComponent.jsx).
  • 3Visually scan for any hooks inside if, switch, loops, ternaries, or after early returns.
  • 4Check for hooks inside helper functions or custom hooks that are only invoked conditionally.
  • 5Comment out suspicious conditionals to see if the error disappears and confirm location.
  • 6If the error isn't obvious, disable all conditionals and slowly reintroduce to pinpoint the culprit.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchThe full component source file where the error points (file/line in stack trace)
  • searchHelper utility functions inside the same file that might hide hooks
  • searchCustom hooks (e.g., useMyFeature.js) imported and called within affected component
  • searchAny recent diffs/PRs involving hook refactors (git diff, GitHub PR view)
  • searchBrowser developer console for atypical error stacks
  • searchJest or React Testing Library results if tests fail only on certain inputs
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningHooks placed inside an if block (e.g., if (condition) { useEffect(...) })
  • warningEarly returns before hooks are called, skipping hook execution on some renders
  • warningHooks inside for/while loops or map/filter callbacks
  • warningCustom hooks called inside conditionals or nested functions
  • warningHooks inside event handlers or callbacks (e.g., onClick={() => useState(...)})
  • warningHooks placed after a return statement, inside dead code
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildMove all hook calls to the top level of the function component, before any conditionals or returns.
  • buildInitialize hooks unconditionally, then use return/conditional logic to choose what JSX to render.
  • buildRefactor custom hooks to accept parameters—even if they're unused on certain paths—so they're always run.
  • buildRemove hooks from inside helper or callback functions. Call hooks in the main body only.
  • buildUse state/refs to conditionally apply effects/logic inside useEffect/useCallback instead of conditional hook calls.
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedReload the app and confirm the error no longer appears in the console.
  • verifiedRun your full test suite (npm test or yarn test) to ensure all transitions and renders work.
  • verifiedInsert console.log in all hook calls to confirm order and consistent execution per render.
  • verifiedTest conditional code paths that previously skipped hooks for correct behavior.
  • verifiedHave code reviewers check for new conditional hook usage before merging.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningSilencing the error without understanding the cause—this will resurface unpredictably.
  • warningMoving hook calls inside deeply nested functions or callbacks as a workaround.
  • warningWrapping hooks in functions that may only sometimes be invoked.
  • warningAssuming small changes (like converting to ternaries) sidestep the underlying rule.
  • warningShipping code that 'works on my machine' but fails on different input/props.
( 07 )War story

Hook Called Conditionally After Feature Toggle Refactor

Frontend EngineerReact 17, TypeScript, Vite, custom useFeatureFlag hook

Timeline

  1. 09:02Deploy triggers 'Hooks can only be called inside the body of a function component' error in Sentry
  2. 09:04Browser console shows error on page load for FeatureSettings.tsx
  3. 09:09Notice useFeatureFlag() is inside if (props.featureEnabled) block
  4. 09:15Moving hook outside conditional eliminates error, but now feature logic breaks
  5. 09:19Refactor so useFeatureFlag is always called, but its return value ignored when feature is off
  6. 09:24All local and CI tests pass, deploy succeeds

I pushed a refactor guarded by a feature flag, only to have production start failing instantly—none of my local tests caught it. Sentry logs pointed to a hook violation in FeatureSettings.tsx.

On review, I saw we'd wrapped useFeatureFlag() in an if (props.featureEnabled) conditional, thinking it would only execute when needed. React disagreed, and crashed the component tree.

Moving the hook out, I realized we still needed to handle when the feature was off, so I defaulted its return to 'null' in those cases. Lesson learned: hooks run every render, no exceptions, and conditional logic always belongs after initialization.

Root cause

A feature flag conditional bypassed a hook call, causing inconsistent hook order and a runtime crash.

The fix

Move hook to unconditional top-level execution; gate feature logic using the hook's return value instead.

The lesson

Even seemingly benign conditionals around hooks will break React—always call hooks at the top, condition logic after.

( 08 )Why Hooks Can’t Be Conditional—The Internals

React relies on a linked-list structure internally to track hooks per component instance. If the number or order of hooks changes between renders—say, due to a conditional—React wires state updates to the wrong variables.

The error is guardrail, not a suggestion: breaking this rule leads to totally unpredictable bugs, not just warnings.

( 09 )Subtle Anti-Patterns: Where Conditionals Hide

Helpers inside the component can seem innocuous, but any hook call—even inside a function that’s only called sometimes—can trip this error.

Similarly, returning JSX early based on props before any hooks are run can cause a code path that skips later hooks entirely. Always structure early returns after hooks.

( 10 )Linting Isn’t Enough—Manual Code Review Required

eslint-plugin-react-hooks will catch most violations, but can miss dynamically constructed function calls or cases where file boundaries obscure the real execution order.

Scan for all use* calls in every component and hook you touch; don’t trust automation alone.

( 11 )Testing the Edge Cases

Test the component with all possible prop permutations, especially those toggling conditionals.

Add temporary logs to verify every hook is hit on every path; missing logs mean missing hooks and a lurking bug.

Frequently asked questions

Can I call hooks conditionally if the same hooks are always called in order?

No—React requires unbroken, consistent hook calls. Even if conditions always resolve the same way today, future changes can break the order.

Is it safe to call hooks inside loops or map functions?

No. Each render must have the same number of hooks, in the same order. Loops can cause a different number of calls per render, which violates this contract.

What about custom hooks—are they different?

Custom hooks follow the same rule. They must be called at the top level, never conditionally, and their internal hooks must also follow the rule.

How do I conditionally use data from a hook?

Call the hook unconditionally, then apply conditional logic to its return value. Don't wrap the call itself in any kind of condition.

Why did this error suddenly appear after a small refactor?

Moving code, adding early returns, or wrapping hooks in conditionals changes execution flow, which breaks the hook order. Even tiny refactors can surface the bug.