Skip to content

Commit 4a1f290

Browse files
authored
[Fizz] Add Owner Stacks when render is aborted (facebook#32735)
1 parent 526dd34 commit 4a1f290

File tree

3 files changed

+81
-1
lines changed

3 files changed

+81
-1
lines changed

packages/react-noop-renderer/src/ReactNoopServer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ function render(children: React$Element<any>, options?: Options): Destination {
364364
children,
365365
null,
366366
null,
367+
null,
367368
options ? options.progressiveChunkSize : undefined,
368369
options ? options.onError : undefined,
369370
options ? options.onAllReady : undefined,

packages/react-server/src/ReactFizzServer.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4762,6 +4762,27 @@ function abortTask(task: Task, request: Request, error: mixed): void {
47624762
}
47634763
}
47644764

4765+
function abortTaskDEV(task: Task, request: Request, error: mixed): void {
4766+
if (__DEV__) {
4767+
const prevTaskInDEV = currentTaskInDEV;
4768+
const prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack;
4769+
setCurrentTaskInDEV(task);
4770+
ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;
4771+
try {
4772+
abortTask(task, request, error);
4773+
} finally {
4774+
setCurrentTaskInDEV(prevTaskInDEV);
4775+
ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl;
4776+
}
4777+
} else {
4778+
// These errors should never make it into a build so we don't need to encode them in codes.json
4779+
// eslint-disable-next-line react-internal/prod-error-codes
4780+
throw new Error(
4781+
'abortTaskDEV should never be called in production mode. This is a bug in React.',
4782+
);
4783+
}
4784+
}
4785+
47654786
function safelyEmitEarlyPreloads(
47664787
request: Request,
47674788
shellComplete: boolean,
@@ -6111,7 +6132,11 @@ export function abort(request: Request, reason: mixed): void {
61116132
// This error isn't necessarily fatal in this case but we need to stash it
61126133
// so we can use it to abort any pending work
61136134
request.fatalError = error;
6114-
abortableTasks.forEach(task => abortTask(task, request, error));
6135+
if (__DEV__) {
6136+
abortableTasks.forEach(task => abortTaskDEV(task, request, error));
6137+
} else {
6138+
abortableTasks.forEach(task => abortTask(task, request, error));
6139+
}
61156140
abortableTasks.clear();
61166141
}
61176142
if (request.destination !== null) {

packages/react-server/src/__tests__/ReactServer-test.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,28 @@
1010

1111
'use strict';
1212

13+
let act;
1314
let React;
1415
let ReactNoopServer;
1516

17+
function normalizeCodeLocInfo(str) {
18+
return (
19+
str &&
20+
str.replace(/^ +(?:at|in) ([\S]+)[^\n]*/gm, function (m, name) {
21+
const dot = name.lastIndexOf('.');
22+
if (dot !== -1) {
23+
name = name.slice(dot + 1);
24+
}
25+
return ' in ' + name + (/\d/.test(m) ? ' (at **)' : '');
26+
})
27+
);
28+
}
29+
1630
describe('ReactServer', () => {
1731
beforeEach(() => {
1832
jest.resetModules();
1933

34+
act = require('internal-test-utils').act;
2035
React = require('react');
2136
ReactNoopServer = require('react-noop-renderer/server');
2237
});
@@ -32,4 +47,43 @@ describe('ReactServer', () => {
3247
const result = ReactNoopServer.render(<div>hello world</div>);
3348
expect(result.root).toEqual(div('hello world'));
3449
});
50+
51+
it('has Owner Stacks in DEV when aborted', async () => {
52+
function Component({promise}) {
53+
React.use(promise);
54+
return <div>Hello, Dave!</div>;
55+
}
56+
function App({promise}) {
57+
return <Component promise={promise} />;
58+
}
59+
60+
let caughtError;
61+
let componentStack;
62+
let ownerStack;
63+
const result = ReactNoopServer.render(
64+
<App promise={new Promise(() => {})} />,
65+
{
66+
onError: (error, errorInfo) => {
67+
caughtError = error;
68+
componentStack = errorInfo.componentStack;
69+
ownerStack = __DEV__ ? React.captureOwnerStack() : null;
70+
},
71+
},
72+
);
73+
74+
await act(async () => {
75+
result.abort();
76+
});
77+
expect(caughtError).toEqual(
78+
expect.objectContaining({
79+
message: 'The render was aborted by the server without a reason.',
80+
}),
81+
);
82+
expect(normalizeCodeLocInfo(componentStack)).toEqual(
83+
'\n in Component (at **)' + '\n in App (at **)',
84+
);
85+
expect(normalizeCodeLocInfo(ownerStack)).toEqual(
86+
__DEV__ ? '\n in App (at **)' : null,
87+
);
88+
});
3589
});

0 commit comments

Comments
 (0)