Skip to content

React errorReact: “Rendered more hooks than during the previous render”

The screen crashes with Rendered more hooks than during the previous render, usually the moment data finishes loading or a prop changes. React matches each Hook to its state by call order. A component that calls a Hook on one render and skips it on another, most often because of an early return above it, breaks that order.

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

  1. 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} />;
    }
  2. 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]);
  3. 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.

  4. 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.

Related: React “Invalid hook call” · React “Too many re-renders” · React “changing an uncontrolled input to be controlled”

Questions

More on this error

Why does it only crash after the data loads?

Because that is the first render that reaches the Hook below the loading check. The render before it returned early and skipped the Hook, so React sees one more Hook than last time.

What about “Rendered fewer hooks than expected”?

It is the same problem the other way round: a render skipped a Hook that ran last time, often because of an early return that only triggers sometimes. Move the Hooks above every return.

Can you fix it for me?

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. 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.

A different error?

Describe it and get a written quote

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.