Skip to content

Commit c8da2e9

Browse files
committed
Name content inside "Suspense fallback"
1 parent 223f81d commit c8da2e9

File tree

2 files changed

+74
-6
lines changed

2 files changed

+74
-6
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,52 @@ describe('ReactDOMFizzServer', () => {
816816
expect(loggedErrors).toEqual([theError]);
817817
});
818818

819+
it('should have special stacks if Suspense fallback', async () => {
820+
const infinitePromise = new Promise(() => {});
821+
const InfiniteComponent = React.lazy(() => {
822+
return infinitePromise;
823+
});
824+
825+
function Throw({text}) {
826+
throw new Error(text);
827+
}
828+
829+
function App() {
830+
return (
831+
<Suspense fallback="Loading">
832+
<div>
833+
<Suspense fallback={<Throw text="Bye" />}>
834+
<InfiniteComponent text="Hi" />
835+
</Suspense>
836+
</div>
837+
</Suspense>
838+
);
839+
}
840+
841+
const loggedErrors = [];
842+
function onError(x, errorInfo) {
843+
loggedErrors.push({
844+
message: x.message,
845+
componentStack: errorInfo.componentStack,
846+
});
847+
return 'Hash of (' + x.message + ')';
848+
}
849+
loggedErrors.length = 0;
850+
851+
await act(() => {
852+
const {pipe} = renderToPipeableStream(<App />, {
853+
onError,
854+
});
855+
pipe(writable);
856+
});
857+
858+
expect(loggedErrors.length).toBe(1);
859+
expect(loggedErrors[0].message).toBe('Bye');
860+
expect(normalizeCodeLocInfo(loggedErrors[0].componentStack)).toBe(
861+
componentStack(['Throw', 'Suspense Fallback', 'div', 'Suspense', 'App']),
862+
);
863+
});
864+
819865
it('should asynchronously load a lazy element', async () => {
820866
let resolveElement;
821867
const lazyElement = React.lazy(() => {
@@ -1797,7 +1843,7 @@ describe('ReactDOMFizzServer', () => {
17971843
function normalizeCodeLocInfo(str) {
17981844
return (
17991845
str &&
1800-
String(str).replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
1846+
String(str).replace(/\n +(?:at|in) ([^\(]+) [^\n]*/g, function (m, name) {
18011847
return '\n in ' + name + ' (at **)';
18021848
})
18031849
);

packages/react-server/src/ReactFizzServer.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,8 +1105,8 @@ function pushComponentStack(task: Task): void {
11051105
function createComponentStackFromType(
11061106
parent: null | ComponentStackNode,
11071107
type: Function | string | symbol,
1108-
owner: null | ReactComponentInfo | ComponentStackNode, // DEV only
1109-
stack: null | Error, // DEV only
1108+
owner: void | null | ReactComponentInfo | ComponentStackNode, // DEV only
1109+
stack: void | null | string | Error, // DEV only
11101110
): ComponentStackNode {
11111111
if (__DEV__) {
11121112
return {
@@ -1122,6 +1122,20 @@ function createComponentStackFromType(
11221122
};
11231123
}
11241124

1125+
function replaceSuspenseComponentStackWithSuspenseFallbackStack(
1126+
componentStack: null | ComponentStackNode,
1127+
): null | ComponentStackNode {
1128+
if (componentStack === null) {
1129+
return null;
1130+
}
1131+
return createComponentStackFromType(
1132+
componentStack.parent,
1133+
'Suspense Fallback',
1134+
__DEV__ ? componentStack.owner : null,
1135+
__DEV__ ? componentStack.stack : null,
1136+
);
1137+
}
1138+
11251139
type ThrownInfo = {
11261140
componentStack?: string,
11271141
};
@@ -1350,6 +1364,8 @@ function renderSuspenseBoundary(
13501364
contentRootSegment.parentFlushed = true;
13511365

13521366
if (request.trackedPostpones !== null) {
1367+
// Stash the original stack frame.
1368+
const suspenseComponentStack = task.componentStack;
13531369
// This is a prerender. In this mode we want to render the fallback synchronously and schedule
13541370
// the content to render later. This is the opposite of what we do during a normal render
13551371
// where we try to skip rendering the fallback if the content itself can render synchronously
@@ -1374,6 +1390,10 @@ function renderSuspenseBoundary(
13741390
request.resumableState,
13751391
prevContext,
13761392
);
1393+
task.componentStack =
1394+
replaceSuspenseComponentStackWithSuspenseFallbackStack(
1395+
suspenseComponentStack,
1396+
);
13771397
boundarySegment.status = RENDERING;
13781398
try {
13791399
renderNode(request, task, fallback, -1);
@@ -1419,7 +1439,7 @@ function renderSuspenseBoundary(
14191439
task.context,
14201440
task.treeContext,
14211441
null, // The row gets reset inside the Suspense boundary.
1422-
task.componentStack,
1442+
suspenseComponentStack,
14231443
!disableLegacyContext ? task.legacyContext : emptyContextObject,
14241444
__DEV__ ? task.debugTask : null,
14251445
);
@@ -1572,7 +1592,9 @@ function renderSuspenseBoundary(
15721592
task.context,
15731593
task.treeContext,
15741594
task.row,
1575-
task.componentStack,
1595+
replaceSuspenseComponentStackWithSuspenseFallbackStack(
1596+
task.componentStack,
1597+
),
15761598
!disableLegacyContext ? task.legacyContext : emptyContextObject,
15771599
__DEV__ ? task.debugTask : null,
15781600
);
@@ -1744,7 +1766,7 @@ function replaySuspenseBoundary(
17441766
task.context,
17451767
task.treeContext,
17461768
task.row,
1747-
task.componentStack,
1769+
replaceSuspenseComponentStackWithSuspenseFallbackStack(task.componentStack),
17481770
!disableLegacyContext ? task.legacyContext : emptyContextObject,
17491771
__DEV__ ? task.debugTask : null,
17501772
);

0 commit comments

Comments
 (0)