Skip to content

Commit 0e68d3c

Browse files
committed
Call life-cycles with a react-stack-bottom-frame stack frame
Most hooks and such are called inside renders which already have these on the stack but life-cycles that call out on them are useful to cut off too. Typically we don't create JSX in here so they wouldn't be part of owner stacks anyway but they can be apart of plain stacks such as the ones prefixes to console logs or printed by error dialogs.
1 parent 7a614e2 commit 0e68d3c

File tree

3 files changed

+226
-59
lines changed

3 files changed

+226
-59
lines changed

packages/react-devtools-shared/src/__tests__/console-test.js

+10-11
Original file line numberDiff line numberDiff line change
@@ -254,12 +254,12 @@ describe('console', () => {
254254
</Intermediate>
255255
);
256256
const Child = ({children}) => {
257-
React.useLayoutEffect(() => {
257+
React.useLayoutEffect(function Child_useLayoutEffect() {
258258
fakeConsole.error('active error');
259259
fakeConsole.log('active log');
260260
fakeConsole.warn('active warn');
261261
});
262-
React.useEffect(() => {
262+
React.useEffect(function Child_useEffect() {
263263
fakeConsole.error('passive error');
264264
fakeConsole.log('passive log');
265265
fakeConsole.warn('passive warn');
@@ -279,30 +279,29 @@ describe('console', () => {
279279
expect(mockWarn.mock.calls[0][0]).toBe('active warn');
280280
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
281281
supportsOwnerStacks
282-
? // TODO: It would be nice to have a Child stack frame here since it's just the effect function.
283-
'\n in Parent (at **)'
282+
? '\n in Child_useLayoutEffect (at **)\n in Parent (at **)'
284283
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
285284
);
286285
expect(mockWarn.mock.calls[1]).toHaveLength(2);
287286
expect(mockWarn.mock.calls[1][0]).toBe('passive warn');
288287
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
289288
supportsOwnerStacks
290-
? '\n in Parent (at **)'
289+
? '\n in Child_useEffect (at **)\n in Parent (at **)'
291290
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
292291
);
293292
expect(mockError).toHaveBeenCalledTimes(2);
294293
expect(mockError.mock.calls[0]).toHaveLength(2);
295294
expect(mockError.mock.calls[0][0]).toBe('active error');
296295
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
297296
supportsOwnerStacks
298-
? '\n in Parent (at **)'
297+
? '\n in Child_useLayoutEffect (at **)\n in Parent (at **)'
299298
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
300299
);
301300
expect(mockError.mock.calls[1]).toHaveLength(2);
302301
expect(mockError.mock.calls[1][0]).toBe('passive error');
303302
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
304303
supportsOwnerStacks
305-
? '\n in Parent (at **)'
304+
? '\n in Child_useEffect (at **)\n in Parent (at **)'
306305
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
307306
);
308307
});
@@ -346,29 +345,29 @@ describe('console', () => {
346345
expect(mockWarn.mock.calls[0][0]).toBe('didMount warn');
347346
expect(normalizeCodeLocInfo(mockWarn.mock.calls[0][1])).toEqual(
348347
supportsOwnerStacks
349-
? '\n in Parent (at **)'
348+
? '\n in Child.componentDidMount (at **)\n in Parent (at **)'
350349
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
351350
);
352351
expect(mockWarn.mock.calls[1]).toHaveLength(2);
353352
expect(mockWarn.mock.calls[1][0]).toBe('didUpdate warn');
354353
expect(normalizeCodeLocInfo(mockWarn.mock.calls[1][1])).toEqual(
355354
supportsOwnerStacks
356-
? '\n in Parent (at **)'
355+
? '\n in Child.componentDidUpdate (at **)\n in Parent (at **)'
357356
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
358357
);
359358
expect(mockError).toHaveBeenCalledTimes(2);
360359
expect(mockError.mock.calls[0]).toHaveLength(2);
361360
expect(mockError.mock.calls[0][0]).toBe('didMount error');
362361
expect(normalizeCodeLocInfo(mockError.mock.calls[0][1])).toBe(
363362
supportsOwnerStacks
364-
? '\n in Parent (at **)'
363+
? '\n in Child.componentDidMount (at **)\n in Parent (at **)'
365364
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
366365
);
367366
expect(mockError.mock.calls[1]).toHaveLength(2);
368367
expect(mockError.mock.calls[1][0]).toBe('didUpdate error');
369368
expect(normalizeCodeLocInfo(mockError.mock.calls[1][1])).toBe(
370369
supportsOwnerStacks
371-
? '\n in Parent (at **)'
370+
? '\n in Child.componentDidUpdate (at **)\n in Parent (at **)'
372371
: '\n in Child (at **)\n in Intermediate (at **)\n in Parent (at **)',
373372
);
374373
});

packages/react-reconciler/src/ReactFiberCallUserSpace.js

+123
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
* @flow
88
*/
99

10+
import type {Fiber} from './ReactInternalTypes';
1011
import type {LazyComponent} from 'react/src/ReactLazy';
12+
import type {Effect} from './ReactFiberHooks';
1113

1214
import {isRendering, setIsRendering} from './ReactCurrentFiber';
15+
import {captureCommitPhaseError} from './ReactFiberWorkLoop';
1316

1417
// These indirections exists so we can exclude its stack frame in DEV (and anything below it).
1518
// TODO: Consider marking the whole bundle instead of these boundaries.
@@ -42,6 +45,13 @@ export const callComponentInDEV: <Props, Arg, R>(
4245

4346
interface ClassInstance<R> {
4447
render(): R;
48+
componentDidMount(): void;
49+
componentDidUpdate(
50+
prevProps: Object,
51+
prevState: Object,
52+
snaphot: Object,
53+
): void;
54+
componentWillUnmount(): void;
4555
}
4656

4757
const callRender = {
@@ -63,6 +73,119 @@ export const callRenderInDEV: <R>(instance: ClassInstance<R>) => R => R =
6373
(callRender['react-stack-bottom-frame'].bind(callRender): any)
6474
: (null: any);
6575

76+
const callComponentDidMount = {
77+
'react-stack-bottom-frame': function (
78+
finishedWork: Fiber,
79+
instance: ClassInstance<any>,
80+
): void {
81+
try {
82+
instance.componentDidMount();
83+
} catch (error) {
84+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
85+
}
86+
},
87+
};
88+
89+
export const callComponentDidMountInDEV: (
90+
finishedWork: Fiber,
91+
instance: ClassInstance<any>,
92+
) => void = __DEV__
93+
? // We use this technique to trick minifiers to preserve the function name.
94+
(callComponentDidMount['react-stack-bottom-frame'].bind(
95+
callComponentDidMount,
96+
): any)
97+
: (null: any);
98+
99+
const callComponentDidUpdate = {
100+
'react-stack-bottom-frame': function (
101+
finishedWork: Fiber,
102+
instance: ClassInstance<any>,
103+
prevProps: Object,
104+
prevState: Object,
105+
snapshot: Object,
106+
): void {
107+
try {
108+
instance.componentDidUpdate(prevProps, prevState, snapshot);
109+
} catch (error) {
110+
captureCommitPhaseError(finishedWork, finishedWork.return, error);
111+
}
112+
},
113+
};
114+
115+
export const callComponentDidUpdateInDEV: (
116+
finishedWork: Fiber,
117+
instance: ClassInstance<any>,
118+
prevProps: Object,
119+
prevState: Object,
120+
snaphot: Object,
121+
) => void = __DEV__
122+
? // We use this technique to trick minifiers to preserve the function name.
123+
(callComponentDidUpdate['react-stack-bottom-frame'].bind(
124+
callComponentDidUpdate,
125+
): any)
126+
: (null: any);
127+
128+
const callComponentWillUnmount = {
129+
'react-stack-bottom-frame': function (
130+
current: Fiber,
131+
instance: ClassInstance<any>,
132+
): void {
133+
try {
134+
instance.componentWillUnmount();
135+
} catch (error) {
136+
captureCommitPhaseError(current, current.return, error);
137+
}
138+
},
139+
};
140+
141+
export const callComponentWillUnmountInDEV: (
142+
current: Fiber,
143+
instance: ClassInstance<any>,
144+
) => void = __DEV__
145+
? // We use this technique to trick minifiers to preserve the function name.
146+
(callComponentWillUnmount['react-stack-bottom-frame'].bind(
147+
callComponentWillUnmount,
148+
): any)
149+
: (null: any);
150+
151+
const callCreate = {
152+
'react-stack-bottom-frame': function (effect: Effect): (() => void) | void {
153+
const create = effect.create;
154+
const inst = effect.inst;
155+
const destroy = create();
156+
inst.destroy = destroy;
157+
return destroy;
158+
},
159+
};
160+
161+
export const callCreateInDEV: (effect: Effect) => (() => void) | void = __DEV__
162+
? // We use this technique to trick minifiers to preserve the function name.
163+
(callCreate['react-stack-bottom-frame'].bind(callCreate): any)
164+
: (null: any);
165+
166+
const callDestroy = {
167+
'react-stack-bottom-frame': function (
168+
current: Fiber,
169+
nearestMountedAncestor: Fiber | null,
170+
destroy: () => void,
171+
): void {
172+
try {
173+
destroy();
174+
} catch (error) {
175+
captureCommitPhaseError(current, nearestMountedAncestor, error);
176+
}
177+
},
178+
};
179+
180+
export const callDestroyInDEV: (
181+
current: Fiber,
182+
nearestMountedAncestor: Fiber | null,
183+
destroy: () => void,
184+
) => void = __DEV__
185+
? // We use this technique to trick minifiers to preserve the function name.
186+
(callDestroy['react-stack-bottom-frame'].bind(callDestroy): any)
187+
: (null: any);
188+
66189
const callLazyInit = {
67190
'react-stack-bottom-frame': function (lazy: LazyComponent<any, any>): any {
68191
const payload = lazy._payload;

0 commit comments

Comments
 (0)