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',