@samitouri / QOS-React / commits / 014138df87

[DevTools] Fix a crash when rendering a new class Component when simulating errored state (#35985)

Sebastian "Sebbie" Silbermann committed Mar 10, 2026 at 18:05 UTC 014138df87869b174956f90a33fd6cf66e160114
3 files changed +111 -2
packages/react-devtools-shared/src/__tests__/storeForceError-test.js new
+106
@@ -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
+2 -2
@@ -7898,7 +7898,7 @@ export function attach(
7898 // Map of Fiber and its force error status: true (error), false (toggled off)
7899 const forceErrorForFibers = new Map<Fiber, boolean>();
7900
7901 - function shouldErrorFiberAccordingToMap(fiber: any): boolean {
7901 + function shouldErrorFiberAccordingToMap(fiber: any): boolean | null {
7902 if (typeof setErrorHandler !== 'function') {
7903 throw new Error(
7904 'Expected overrideError() to not get called for earlier React versions.',
@@ -7934,7 +7934,7 @@ export function attach(
7934 }
7935 }
7936 if (status === undefined) {
7937 - return false;
7937 + return null;
7938 }
7939 return status;
7940 }
packages/react-reconciler/src/ReactFiberBeginWork.js
+3
@@ -1584,6 +1584,9 @@ function updateClassComponent(
1584 // This is used by DevTools to force a boundary to error.
1585 switch (shouldError(workInProgress)) {
1586 case false: {
1587 + // We previously simulated an error on this boundary
1588 + // so the instance must have been constructed in a previous
1589 + // commit.
1590 const instance = workInProgress.stateNode;
1591 const ctor = workInProgress.type;
1592 // TODO This way of resetting the error boundary state is a hack.