diff --git a/packages/react-dom/src/__tests__/ReactUpdates-test.js b/packages/react-dom/src/__tests__/ReactUpdates-test.js
index bc7767b12efd1..2bfab445cc3b5 100644
--- a/packages/react-dom/src/__tests__/ReactUpdates-test.js
+++ b/packages/react-dom/src/__tests__/ReactUpdates-test.js
@@ -20,6 +20,19 @@ let waitFor;
let assertLog;
let assertConsoleErrorDev;
+function normalizeCodeLocInfo(str) {
+ return (
+ str &&
+ str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
+ const dot = name.lastIndexOf('.');
+ if (dot !== -1) {
+ name = name.slice(dot + 1);
+ }
+ return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
+ })
+ );
+}
+
describe('ReactUpdates', () => {
beforeEach(() => {
jest.resetModules();
@@ -1972,13 +1985,65 @@ describe('ReactUpdates', () => {
}
const container = document.createElement('div');
- const root = ReactDOMClient.createRoot(container);
- await expect(async () => {
- await act(() => {
- ReactDOM.flushSync(() => {
- root.render();
- });
+ const errors = [];
+ const root = ReactDOMClient.createRoot(container, {
+ onUncaughtError: (error, errorInfo) => {
+ errors.push(
+ `${error.message}${normalizeCodeLocInfo(errorInfo.componentStack)}`,
+ );
+ },
+ });
+ await act(() => {
+ ReactDOM.flushSync(() => {
+ root.render();
});
- }).rejects.toThrow('Maximum update depth exceeded');
+ });
+
+ expect(errors).toEqual([
+ 'Maximum update depth exceeded. ' +
+ 'This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. ' +
+ 'React limits the number of nested updates to prevent infinite loops.' +
+ '\n in NonTerminating (at **)',
+ ]);
+ });
+
+ it('prevents infinite update loop triggered by too many updates in ref callbacks', async () => {
+ let scheduleUpdate;
+ function TooManyRefUpdates() {
+ const [count, _scheduleUpdate] = React.useReducer(c => c + 1, 0);
+ scheduleUpdate = _scheduleUpdate;
+
+ return (
+
{
+ for (let i = 0; i < 50; i++) {
+ scheduleUpdate(1);
+ }
+ }}>
+ {count}
+
+ );
+ }
+
+ const container = document.createElement('div');
+ const errors = [];
+ const root = ReactDOMClient.createRoot(container, {
+ onUncaughtError: (error, errorInfo) => {
+ errors.push(
+ `${error.message}${normalizeCodeLocInfo(errorInfo.componentStack)}`,
+ );
+ },
+ });
+ await act(() => {
+ root.render();
+ });
+
+ expect(errors).toEqual([
+ 'Maximum update depth exceeded. ' +
+ 'This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. ' +
+ 'React limits the number of nested updates to prevent infinite loops.' +
+ '\n in div' +
+ '\n in TooManyRefUpdates (at **)',
+ ]);
});
});