Update error message for suspending at sync priority (#23361)
Instead of adding a new Suspense boundary, the default recommendation is to wrap the suspending update with startTransition.
Andrew Clark committed
Feb 24, 2022 at 21:02 UTC
3a60844a0f3449d7e6d208e31df44c5ba3466e38
6 files changed
+19
-26
packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js
+2
-2
@@ -187,7 +187,7 @@ describe('ReactDOMServerSuspense', () => {
187
1,
188
);
189
},
190
- 'Add a <Suspense fallback=...> component higher in the tree',
190
+ 'A component suspended while responding to synchronous input.',
191
);
192
193
itThrowsWhenRendering(
@@ -200,7 +200,7 @@ describe('ReactDOMServerSuspense', () => {
200
1,
201
);
202
},
203
- 'Add a <Suspense fallback=...> component higher in the tree',
203
+ 'A component suspended while responding to synchronous input.',
204
);
205
}
206
packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js
+6
-4
@@ -78,11 +78,13 @@ function renderToStringImpl(
78
}
79
80
if (!readyToStream) {
81
+ // Note: This error message is the one we use on the client. It doesn't
82
+ // really make sense here. But this is the legacy server renderer, anyway.
83
+ // We're going to delete it soon.
84
throw new Error(
82
- 'A React component suspended while rendering, but no fallback UI was specified.\n' +
83
- '\n' +
84
- 'Add a <Suspense fallback=...> component higher in the tree to ' +
85
- 'provide a loading indicator or placeholder to display.',
85
+ 'A component suspended while responding to synchronous input. This ' +
86
+ 'will cause the UI to be replaced with a loading indicator. To fix, ' +
87
+ 'updates that suspend should be wrapped with startTransition.',
88
);
89
}
90
packages/react-reconciler/src/ReactFiberThrow.new.js
+4
-8
@@ -499,15 +499,11 @@ function throwException(
499
// This is a sync/discrete update. We treat this case like an error
500
// because discrete renders are expected to produce a complete tree
501
// synchronously to maintain consistency with external state.
502
-
503
- // TODO: We should never call getComponentNameFromFiber in production.
504
- // Log a warning or something to prevent us from accidentally bundling it.
502
const uncaughtSuspenseError = new Error(
506
- (getComponentNameFromFiber(sourceFiber) || 'A React component') +
507
- ' suspended while rendering, but no fallback UI was specified.\n' +
508
- '\n' +
509
- 'Add a <Suspense fallback=...> component higher in the tree to ' +
510
- 'provide a loading indicator or placeholder to display.',
503
+ 'A component suspended while responding to synchronous input. This ' +
504
+ 'will cause the UI to be replaced with a loading indicator. To ' +
505
+ 'fix, updates that suspend should be wrapped ' +
506
+ 'with startTransition.',
507
);
508
509
// If we're outside a transition, fall through to the regular error path.
packages/react-reconciler/src/ReactFiberThrow.old.js
+4
-8
@@ -499,15 +499,11 @@ function throwException(
499
// This is a sync/discrete update. We treat this case like an error
500
// because discrete renders are expected to produce a complete tree
501
// synchronously to maintain consistency with external state.
502
-
503
- // TODO: We should never call getComponentNameFromFiber in production.
504
- // Log a warning or something to prevent us from accidentally bundling it.
502
const uncaughtSuspenseError = new Error(
506
- (getComponentNameFromFiber(sourceFiber) || 'A React component') +
507
- ' suspended while rendering, but no fallback UI was specified.\n' +
508
- '\n' +
509
- 'Add a <Suspense fallback=...> component higher in the tree to ' +
510
- 'provide a loading indicator or placeholder to display.',
503
+ 'A component suspended while responding to synchronous input. This ' +
504
+ 'will cause the UI to be replaced with a loading indicator. To ' +
505
+ 'fix, updates that suspend should be wrapped ' +
506
+ 'with startTransition.',
507
);
508
509
// If we're outside a transition, fall through to the regular error path.
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+1
-3
@@ -1011,9 +1011,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1011
ReactNoop.flushSync(() => {
1012
ReactNoop.render(<AsyncText text="Async" />);
1013
});
1014
- }).toThrow(
1015
- 'AsyncText suspended while rendering, but no fallback UI was specified.',
1016
- );
1014
+ }).toThrow('A component suspended while responding to synchronous input.');
1015
});
1016
1017
// @gate enableCache
scripts/error-codes/codes.json
+2
-1
@@ -409,5 +409,6 @@
409
"421": "There was an error while hydrating this Suspense boundary. Switched to client rendering.",
410
"422": "There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.",
411
"423": "This root received an early update, before anything was able hydrate. Switched the entire root to client rendering.",
412
- "424": "Text content does not match server-rendered HTML."
412
+ "424": "Text content does not match server-rendered HTML.",
413
+ "425": "A component suspended while responding to synchronous input. This will cause the UI to be replaced with a loading indicator. To fix, updates that suspend should be wrapped with startTransition."
414
}