[Fizz] Add Owner Stacks when render is aborted (#32735)
Sebastian "Sebbie" Silbermann committed
Jun 2, 2025 at 19:27 UTC
4a1f29079ccc61659e026bbcf205bc8d53780927
3 files changed
+81
-1
packages/react-noop-renderer/src/ReactNoopServer.js
+1
@@ -364,6 +364,7 @@ function render(children: React$Element<any>, options?: Options): Destination {
364
children,
365
null,
366
null,
367
+ null,
368
options ? options.progressiveChunkSize : undefined,
369
options ? options.onError : undefined,
370
options ? options.onAllReady : undefined,
packages/react-server/src/ReactFizzServer.js
+26
-1
@@ -4762,6 +4762,27 @@ function abortTask(task: Task, request: Request, error: mixed): void {
4762
}
4763
}
4764
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
+
4786
function safelyEmitEarlyPreloads(
4787
request: Request,
4788
shellComplete: boolean,
@@ -6111,7 +6132,11 @@ export function abort(request: Request, reason: mixed): void {
6132
// This error isn't necessarily fatal in this case but we need to stash it
6133
// so we can use it to abort any pending work
6134
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
+ }
6140
abortableTasks.clear();
6141
}
6142
if (request.destination !== null) {
packages/react-server/src/__tests__/ReactServer-test.js
+54
@@ -10,13 +10,28 @@
10
11
'use strict';
12
13
+let act;
14
let React;
15
let ReactNoopServer;
16
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
+
30
describe('ReactServer', () => {
31
beforeEach(() => {
32
jest.resetModules();
33
34
+ act = require('internal-test-utils').act;
35
React = require('react');
36
ReactNoopServer = require('react-noop-renderer/server');
37
});
@@ -32,4 +47,43 @@ describe('ReactServer', () => {
47
const result = ReactNoopServer.render(<div>hello world</div>);
48
expect(result.root).toEqual(div('hello world'));
49
});
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
+ });
89
});