The Problem

React Hooks must be called in the exact same order on every render. Calling a hook inside a conditional, loop, or nested function breaks this rule and causes state mismatches that corrupt your component's internal state.

Symptoms

  • Error: "Rendered more hooks than during the previous render"
  • Error: "Rendered fewer hooks than expected"
  • State values are swapped between different useState calls
  • Custom hook returns undefined or wrong data
  • Works in some code paths but crashes in others

Real Error Message

``` Error: Rendered more hooks than during the previous render. at MyComponent at App

Warning: React has detected a change in the order of Hooks called by MyComponent. This will lead to bugs and errors if not fixed. ```

What NOT to Do

```javascript // BAD: Hook inside conditional function UserPanel({ isAdmin }) { const [name, setName] = useState('');

if (isAdmin) { const [permissions, setPermissions] = usePermissions(); // WRONG! }

return <div>{name}</div>; }

// BAD: Hook inside loop function FormList({ fields }) { fields.map(field => { const [value, setValue] = useState(field.default); // WRONG! return <input value={value} />; }); }

// BAD: Hook after early return function Dashboard({ user }) { if (!user) return <Login />; const [settings, setSettings] = useSettings(); // WRONG! Not called on all paths } ```

How to Fix It

Fix 1: Move Hook to Top Level

```javascript function UserPanel({ isAdmin }) { const [name, setName] = useState(''); const [permissions, setPermissions] = usePermissions(); // Always called

return ( <div> {name} {isAdmin && <PermissionsEditor permissions={permissions} />} </div> ); } ```

Fix 2: Move Hook Inside the Conditional Component

```javascript function AdminPanel() { const [permissions, setPermissions] = usePermissions(); // Safe: component always renders return <PermissionsEditor permissions={permissions} />; }

function UserPanel({ isAdmin }) { const [name, setName] = useState(''); return ( <div> {name} {isAdmin && <AdminPanel />} </div> ); } ```

Fix 3: Use Hook Return Value Conditionally

```javascript function Dashboard({ user }) { const [settings, setSettings] = useSettings(); // Always called

if (!user) return <Login />;

// Use settings conditionally, but hook is always called return <div>{settings.theme}</div>; } ```

Fix 4: Custom Hook with Conditional Logic Inside

```javascript // The custom hook can have conditionals internally function useConditionalPermissions(isAdmin) { const [permissions] = useState(() => isAdmin ? fetchAdminPermissions() : fetchUserPermissions() ); return permissions; // Hook is always called at top level }

function UserPanel({ isAdmin }) { const permissions = useConditionalPermissions(isAdmin); // Safe return <div>{permissions}</div>; } ```

Enforcement

Install and configure the ESLint plugin:

bash
npm install eslint-plugin-react-hooks
json
{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

This catches hook violations at lint time before the code ever runs.