Short answer
Find the Hook that runs only sometimes: a useState, useEffect, useMemo or custom Hook below an if (loading) return, inside a condition, or inside a loop. Move every Hook to the top of the component, above the first return, and put the condition inside the Hook or after it. The reverse message, Rendered fewer hooks than expected, has the same cause and the same fix. If it appeared right after an edit in the dev server, reload the page once first: Fast Refresh can keep the old Hook order.
Why the Hook count changes, and the fix
01
A Hook below an early return
“Do not call Hooks after a conditional return statement.” React, Rules of Hooks
On the first render, data is still loading and the component returns before it reaches the useMemo or useEffect below. When the data arrives, it runs that Hook too, so this render has more Hooks than the last one. Move the Hook above the return and let it handle empty data.
// breaks: useMemo runs only after loading ends function Orders({ orders, loading }) { if (loading) return <Spinner />; const total = useMemo(() => sum(orders), [orders]); return <Total value={total} />; } // works: every Hook runs on every render function Orders({ orders, loading }) { const total = useMemo(() => sum(orders ?? []), [orders]); if (loading) return <Spinner />; return <Total value={total} />; }02
A Hook inside a condition or loop
“Hooks rely on a stable call order on every render of the same component.” React, State: A Component’s Memory
if (user) { useEffect(…) } or a useState inside .map() changes the number of Hook calls whenever the condition or the list length changes. Call the Hook unconditionally and move the condition inside it, or move the list item into its own component that has its own Hooks.
// breaks if (user) { useEffect(() => track(user.id), [user]); } // works useEffect(() => { if (user) track(user.id); }, [user]);03
A component called as a function
“Don’t call Hooks from regular JavaScript functions.” React, Rules of Hooks
Calling a component like renderRow(item) or Row(item) instead of rendering <Row item={item} /> runs its Hooks as part of the parent. When the number of rows changes, the parent’s Hook count changes with it. Render it as JSX so each row keeps its own Hooks.
04
Catch it before it ships
“Additionally, a linter plugin catches most mistakes.” React, State: A Component’s Memory
The React Hooks ESLint plugin flags a Hook after an early return or inside a condition as you type. Turn it on, so code an AI tool wrote or you pasted in is caught in the editor instead of in production.
Checked against the vendor’s docs on : React: Rules of Hooks; React: State, a component’s memory (why Hook order matters).
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.See scope and start
Related: React “Invalid hook call” · React “Too many re-renders” · React “changing an uncontrolled input to be controlled”