diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js index 9e59cee39a891..48ab3565c6466 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js @@ -430,26 +430,6 @@ describe('ReactDOMServerHooks', () => { expect(domNode.textContent).toEqual('hi'); }); - itThrowsWhenRendering( - 'with a warning for useRef inside useReducer', - async render => { - function App() { - const [value, dispatch] = useReducer((state, action) => { - useRef(0); - return state + 1; - }, 0); - if (value === 0) { - dispatch(); - } - return value; - } - - const domNode = await render(, 1); - expect(domNode.textContent).toEqual('1'); - }, - 'Rendered more hooks than during the previous render', - ); - itRenders('with a warning for useRef inside useState', async render => { function App() { const [value] = useState(() => { @@ -686,6 +666,32 @@ describe('ReactDOMServerHooks', () => { ); }); + describe('invalid hooks', () => { + it('warns when calling useRef inside useReducer', async () => { + function App() { + const [value, dispatch] = useReducer((state, action) => { + useRef(0); + return state + 1; + }, 0); + if (value === 0) { + dispatch(); + } + return value; + } + + let error; + try { + await serverRender(); + } catch (x) { + error = x; + } + expect(error).not.toBe(undefined); + expect(error.message).toContain( + 'Rendered more hooks than during the previous render', + ); + }); + }); + itRenders( 'can use the same context multiple times in the same function', async render => { diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index b07667527277c..09828fd12b04b 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -839,7 +839,24 @@ function updateWorkInProgressHook(): Hook { // Clone from the current hook. if (nextCurrentHook === null) { - throw new Error('Rendered more hooks than during the previous render.'); + const currentFiber = currentlyRenderingFiber.alternate; + if (currentFiber === null) { + // This is the initial render. This branch is reached when the component + // suspends, resumes, then renders an additional hook. + const newHook: Hook = { + memoizedState: null, + + baseState: null, + baseQueue: null, + queue: null, + + next: null, + }; + nextCurrentHook = newHook; + } else { + // This is an update. We should always have a current hook. + throw new Error('Rendered more hooks than during the previous render.'); + } } currentHook = nextCurrentHook; diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index 73479626e1fbc..08f3ee46ffe1f 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -839,7 +839,24 @@ function updateWorkInProgressHook(): Hook { // Clone from the current hook. if (nextCurrentHook === null) { - throw new Error('Rendered more hooks than during the previous render.'); + const currentFiber = currentlyRenderingFiber.alternate; + if (currentFiber === null) { + // This is the initial render. This branch is reached when the component + // suspends, resumes, then renders an additional hook. + const newHook: Hook = { + memoizedState: null, + + baseState: null, + baseQueue: null, + queue: null, + + next: null, + }; + nextCurrentHook = newHook; + } else { + // This is an update. We should always have a current hook. + throw new Error('Rendered more hooks than during the previous render.'); + } } currentHook = nextCurrentHook; diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index 37fd06f1e0260..63dc3c04d0a37 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -1071,7 +1071,9 @@ describe('ReactHooks', () => { expect(() => { expect(() => { ReactTestRenderer.create(); - }).toThrow('Rendered more hooks than during the previous render.'); + }).toThrow( + 'Should have a queue. This is likely a bug in React. Please file an issue.', + ); }).toErrorDev([ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks', 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',