[Fiber] Name content inside "Suspense fallback" (#33724)
Same as #33723 but for Fiber.
Sebastian Markbåge committed
Jul 8, 2025 at 00:00 UTC
b44a99bf58d69d52b5288d9eadcc6d226d705e11
2 files changed
+43
-3
packages/react-reconciler/src/ReactFiberComponentStack.js
+8
-2
@@ -34,7 +34,7 @@ import {
34
} from 'shared/ReactComponentStackFrame';
35
import {formatOwnerStack} from 'shared/ReactOwnerStackFrames';
36
37
-function describeFiber(fiber: Fiber): string {
37
+function describeFiber(fiber: Fiber, childFiber: null | Fiber): string {
38
switch (fiber.tag) {
39
case HostHoistable:
40
case HostSingleton:
@@ -44,6 +44,10 @@ function describeFiber(fiber: Fiber): string {
44
// TODO: When we support Thenables as component types we should rename this.
45
return describeBuiltInComponentFrame('Lazy');
46
case SuspenseComponent:
47
+ if (fiber.child !== childFiber && childFiber !== null) {
48
+ // If we came from the second Fiber then we're in the Suspense Fallback.
49
+ return describeBuiltInComponentFrame('Suspense Fallback');
50
+ }
51
return describeBuiltInComponentFrame('Suspense');
52
case SuspenseListComponent:
53
return describeBuiltInComponentFrame('SuspenseList');
@@ -70,8 +74,9 @@ export function getStackByFiberInDevAndProd(workInProgress: Fiber): string {
74
try {
75
let info = '';
76
let node: Fiber = workInProgress;
77
+ let previous: null | Fiber = null;
78
do {
74
- info += describeFiber(node);
79
+ info += describeFiber(node, previous);
80
if (__DEV__) {
81
// Add any Server Component stack frames in reverse order.
82
const debugInfo = node._debugInfo;
@@ -88,6 +93,7 @@ export function getStackByFiberInDevAndProd(workInProgress: Fiber): string {
93
}
94
}
95
}
96
+ previous = node;
97
// $FlowFixMe[incompatible-type] we bail out when we get a null
98
node = node.return;
99
} while (node);
packages/react-reconciler/src/__tests__/ReactErrorStacks-test.js
+35
-1
@@ -87,7 +87,7 @@ describe('ReactFragment', () => {
87
function normalizeCodeLocInfo(str) {
88
return (
89
str &&
90
- str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
90
+ str.replace(/\n +(?:at|in) ([^\(]+) [^\n]*/g, function (m, name) {
91
return '\n in ' + name + ' (at **)';
92
})
93
);
@@ -168,6 +168,40 @@ describe('ReactFragment', () => {
168
]);
169
});
170
171
+ it('includes built-in for Suspense fallbacks', async () => {
172
+ const SomethingThatSuspends = React.lazy(() => {
173
+ return new Promise(() => {});
174
+ });
175
+
176
+ ReactNoop.createRoot({
177
+ onCaughtError,
178
+ }).render(
179
+ <CatchingBoundary>
180
+ <Suspense fallback={<SomethingThatErrors />}>
181
+ <SomethingThatSuspends />
182
+ </Suspense>
183
+ </CatchingBoundary>,
184
+ );
185
+ await waitForAll([]);
186
+ expect(didCatchErrors).toEqual([
187
+ 'uh oh',
188
+ componentStack([
189
+ 'SomethingThatErrors',
190
+ 'Suspense Fallback',
191
+ 'CatchingBoundary',
192
+ ]),
193
+ ]);
194
+ expect(rootCaughtErrors).toEqual([
195
+ 'uh oh',
196
+ componentStack([
197
+ 'SomethingThatErrors',
198
+ 'Suspense Fallback',
199
+ 'CatchingBoundary',
200
+ ]),
201
+ __DEV__ ? componentStack(['SomethingThatErrors']) : null,
202
+ ]);
203
+ });
204
+
205
// @gate enableActivity
206
it('includes built-in for Activity', async () => {
207
ReactNoop.createRoot({