The Problem

Custom hooks are a powerful abstraction, but when they contain conditional logic that changes which or how many hooks are called, they violate React's Rules of Hooks and corrupt state.

Symptoms

  • State from one useState call appears in another useState call
  • useEffect runs with wrong dependencies
  • Custom hook returns different shape on different renders
  • App crashes only on specific user flows (not on initial load)

Real Error Scenario

```javascript // BAD: The number of hooks called depends on the data function useUserConfig(user) { if (!user) return null;

const [theme, setTheme] = useState('light'); const [notifications, setNotifications] = useState(true);

if (user.isAdmin) { const [auditLog, setAuditLog] = useState([]); // Only called for admins! return { theme, notifications, auditLog }; }

return { theme, notifications }; } ```

When a non-admin user logs in as admin (or vice versa), the hook calls a different number of useState calls. React's internal hook list gets corrupted.

How to Fix It

Fix 1: Always Call All Hooks

```javascript function useUserConfig(user) { const [theme, setTheme] = useState('light'); const [notifications, setNotifications] = useState(true); const [auditLog, setAuditLog] = useState([]); // Always called

if (!user) return null;

return { theme, notifications, auditLog: user.isAdmin ? auditLog : undefined }; } ```

Fix 2: Split Into Separate Hooks

```javascript function useBaseConfig() { const [theme] = useState('light'); const [notifications] = useState(true); return { theme, notifications }; }

function useAdminConfig() { const [auditLog] = useState([]); return { auditLog }; }

function UserPanel({ user }) { const config = useBaseConfig(); // Always called const adminConfig = user?.isAdmin ? useAdminConfig() : null; // Separate component would be better } ```

Fix 3: Extract Conditional Logic into a Separate Component

```javascript function AdminDashboard() { const { theme, notifications } = useBaseConfig(); const { auditLog } = useAdminConfig(); return <AdminView theme={theme} auditLog={auditLog} />; }

function UserDashboard() { const { theme, notifications } = useBaseConfig(); return <UserView theme={theme} />; }

function Dashboard({ user }) { if (!user) return <Login />; return user.isAdmin ? <AdminDashboard /> : <UserDashboard />; } ```

Each component has a stable hook call order, and the conditional is at the component level, not the hook level.