Skip to content

Commit 014138d

Browse files
authored
[DevTools] Fix a crash when rendering a new class Component when simulating errored state (facebook#35985)
1 parent 4610359 commit 014138d

3 files changed

Lines changed: 111 additions & 2 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type Store from 'react-devtools-shared/src/devtools/store';
11+
12+
import {getVersionedRenderImplementation} from './utils';
13+
14+
describe('Store forcing errors', () => {
15+
let React;
16+
let agent;
17+
let store: Store;
18+
let utils;
19+
let actAsync;
20+
21+
beforeEach(() => {
22+
agent = global.agent;
23+
store = global.store;
24+
store.collapseNodesByDefault = false;
25+
store.componentFilters = [];
26+
store.recordChangeDescriptions = true;
27+
28+
React = require('react');
29+
utils = require('./utils');
30+
31+
actAsync = utils.actAsync;
32+
});
33+
34+
const {render} = getVersionedRenderImplementation();
35+
36+
// @reactVersion >= 18.0
37+
it('resets forced error and fallback states when filters are changed', async () => {
38+
class AnyClassComponent extends React.Component {
39+
render() {
40+
return this.props.children;
41+
}
42+
}
43+
44+
class ErrorBoundary extends React.Component {
45+
state = {hasError: false};
46+
47+
static getDerivedStateFromError() {
48+
return {hasError: true};
49+
}
50+
51+
render() {
52+
if (this.state.hasError) {
53+
return (
54+
<AnyClassComponent key="fallback">
55+
<div key="did-error" />
56+
</AnyClassComponent>
57+
);
58+
}
59+
return this.props.children;
60+
}
61+
}
62+
63+
function App() {
64+
return (
65+
<ErrorBoundary key="content">
66+
<div key="error-content" />
67+
</ErrorBoundary>
68+
);
69+
}
70+
71+
await actAsync(async () => {
72+
render(<App />);
73+
});
74+
const rendererID = utils.getRendererID();
75+
await actAsync(() => {
76+
agent.overrideError({
77+
id: store.getElementIDAtIndex(2),
78+
rendererID,
79+
forceError: true,
80+
});
81+
});
82+
83+
expect(store).toMatchInlineSnapshot(`
84+
[root]
85+
▾ <App>
86+
▾ <ErrorBoundary key="content">
87+
▾ <AnyClassComponent key="fallback">
88+
<div key="did-error">
89+
`);
90+
91+
await actAsync(() => {
92+
agent.overrideError({
93+
id: store.getElementIDAtIndex(2),
94+
rendererID,
95+
forceError: false,
96+
});
97+
});
98+
99+
expect(store).toMatchInlineSnapshot(`
100+
[root]
101+
▾ <App>
102+
▾ <ErrorBoundary key="content">
103+
<div key="error-content">
104+
`);
105+
});
106+
});

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7898,7 +7898,7 @@ export function attach(
78987898
// Map of Fiber and its force error status: true (error), false (toggled off)
78997899
const forceErrorForFibers = new Map<Fiber, boolean>();
79007900
7901-
function shouldErrorFiberAccordingToMap(fiber: any): boolean {
7901+
function shouldErrorFiberAccordingToMap(fiber: any): boolean | null {
79027902
if (typeof setErrorHandler !== 'function') {
79037903
throw new Error(
79047904
'Expected overrideError() to not get called for earlier React versions.',
@@ -7934,7 +7934,7 @@ export function attach(
79347934
}
79357935
}
79367936
if (status === undefined) {
7937-
return false;
7937+
return null;
79387938
}
79397939
return status;
79407940
}

packages/react-reconciler/src/ReactFiberBeginWork.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,9 @@ function updateClassComponent(
15841584
// This is used by DevTools to force a boundary to error.
15851585
switch (shouldError(workInProgress)) {
15861586
case false: {
1587+
// We previously simulated an error on this boundary
1588+
// so the instance must have been constructed in a previous
1589+
// commit.
15871590
const instance = workInProgress.stateNode;
15881591
const ctor = workInProgress.type;
15891592
// TODO This way of resetting the error boundary state is a hack.

0 commit comments

Comments
 (0)