React error #310 (tested)

React error #310, Rendered more hooks than during the previous render, fires when a component calls a different number of hooks on one render than it did on the last one. We built a component that calls useState inside an if block, re-rendered it with the condition flipped, and captured the real dev-mode error and React's own diagnostic warning.

1. The exact error we triggered

We rendered this component tree with react-dom/client under @testing-library/react (jsdom), first with showExtra=false, then re-rendered the same mounted tree with showExtra=true:

function Child({ showExtra }) {
  const [base] = useState('base');
  if (showExtra) {
    const [extra] = useState('extra'); // only called on some renders
    return <div>{base}{extra}</div>;
  }
  return <div>{base}</div>;
}

function Parent({ showExtra }) {
  return <Child showExtra={showExtra} />;
}

First render (showExtra=false) mounted fine: <div>base</div>, one hook call. Re-rendering the same Child instance with showExtra=true added a second useState call that did not run on the previous render. React caught it immediately and threw, in dev mode, with this exact message and stack:

Error: Rendered more hooks than during the previous render.
    at updateWorkInProgressHook (react-dom.development.js:15688:13)
    at updateReducer (react-dom.development.js:15749:14)
    at updateState (react-dom.development.js:16185:10)
    at Object.useState (react-dom.development.js:17096:16)
    at useState (react.development.js:1622:21)
    at Child (repro.mjs:12:21)
    at renderWithHooks (react-dom.development.js:15486:18)
    at updateFunctionComponent (react-dom.development.js:19617:20)
    at beginWork (react-dom.development.js:21640:16)

Before that throw, React also logged a separate diagnostic warning to console.error, with a before/after table pinpointing which hook slot changed:

Warning: React has detected a change in the order of Hooks called by Child. This will
lead to bugs and errors if not fixed. For more information, read the Rules of Hooks:
https://reactjs.org/link/rules-of-hooks

   Previous render            Next render
   ------------------------------------------------------
1. useState                   useState
2. undefined                  useState
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    at Child (repro.mjs:9:18)
    at Parent (repro.mjs:18:19)

That table is the fastest way to find the bug in a real app: row 2 shows slot 2 was undefined (not called) on the previous render and useState (called) on this render. That is the conditional hook, named exactly.

2. Why this happens

React does not know your hooks by name. Internally each component has a linked list of hook records, and every useState/useEffect/useRef/etc. call reads the next node in that list, in call order. The list is built on the first render and then walked, in the same order, on every re-render. If a render calls one more hook than the list has nodes for, updateWorkInProgressHook finds no next node and throws Error(p(310)) — React's minified error #310, "Rendered more hooks than during the previous render."

This is also why the official rule is "call hooks in the same order every render," not "call the same hooks." Wrapping a hook in if, putting it after an early return, or calling it inside a loop with a variable iteration count all change the count or order of list nodes between renders.

3. Confirming the minified mapping

Production React ships minified, so the message in a production error is just Minified React error #310 with a link to the decoder. We confirmed the mapping is real, not guessed, by grepping the installed react-dom.production.min.js (v18.3.1) for the literal throw site:

...if(null!==b)O=b,N=a;else{if(null===a)throw Error(p(310));N=a;a={memoizedState:...

That throw Error(p(310)) sits inside the same hook-list-walking logic as updateWorkInProgressHook in the dev build — when the next node in the list is missing, production throws error #310 instead of the English message. Same bug, same code path, different build.

4. The fix: move the condition inside the hook, not around it

Call every hook unconditionally, at the top level of the component, in the same order every render. Put the conditional logic inside the hook's callback or arguments instead:

// Before (throws #310 when showExtra flips)
function Child({ showExtra }) {
  const [base] = useState('base');
  if (showExtra) {
    const [extra] = useState('extra');
    return <div>{base}{extra}</div>;
  }
  return <div>{base}</div>;
}

// After: always call the hook, branch on the value instead
function Child({ showExtra }) {
  const [base] = useState('base');
  const [extra] = useState('extra'); // always called, same slot every render
  return <div>{base}{showExtra && extra}</div>;
}

For useEffect specifically, put the condition inside the effect body, not around the call:

// Before
if (cond) {
  useEffect(() => { doThing(); }, []);
}

// After
useEffect(() => {
  if (cond) doThing();
}, [cond]);

We re-ran our repro with the hook moved outside the if and the same prop flip: no warning, no throw, both renders completed and the tree updated correctly.

5. Where this bug usually hides

If the crash only happens after server-side rendering specifically, you may be hitting a hydration mismatch instead — see Minified React error #130, which covers the FAQ mention of this error, or React hydration error (Next.js) for hydration-specific mismatches (error #418).

6. Quick checklist

Frequently asked questions

What does 'Rendered more hooks than during the previous render' mean?

React tracks hooks as an ordered list built on first render. On every later render it walks that same list in order. If a render calls one more hook than the list has slots for — most often because a hook sits inside an if, after an early return, or in a loop — React has no slot to read from and throws this error. We reproduced it with a useState call inside an if block.

Is this the same as 'React has detected a change in the order of Hooks'?

They are two different messages from the same underlying cause. React first logs the order-change warning, with a before/after table of which hook slot changed, then throws 'Rendered more hooks than during the previous render' (or its minified form, error #310) once it can no longer continue reconciling. We captured both in the same repro run.

What is minified React error #310?

It is the production-build version of the same throw. We confirmed by grepping react-dom.production.min.js 18.3.1 for the literal site: 'if(null===a)throw Error(p(310))' inside the same hook-list-walking code as the dev-mode updateWorkInProgressHook function that produces the English message.

How do I fix a conditional hook without changing behavior?

Call the hook unconditionally every render, and move the condition inside it: for useState, call it always and branch on the value when you use it; for useEffect, put the if-check inside the effect callback and add the condition's inputs to the dependency array instead of guarding the useEffect call itself.

Can this happen from an early return instead of an if block?

Yes. A common pattern is 'if (!data) return null;' placed before a hook call further down the component. While data is loading, that hook is skipped; once data arrives, the component takes the other branch and calls a hook it didn't call before, which is the same hook-count mismatch we reproduced here.

Written with AI assistance. Measured on 2026-10-03 with Node.js 24.21.0, React 18.3.1, react-dom 18.3.1, @testing-library/react 16.3.3 and jsdom 30.1.1 (global-jsdom). We rendered a real component tree with react-dom's client renderer, flipped a prop to change which branch ran, and captured the actual thrown error and React's dev warning. curl and the headers tool are free; Node.js and React are free and open-source.