diff --git a/packages/react-devtools-shared/src/__tests__/setupTests.js b/packages/react-devtools-shared/src/__tests__/setupTests.js
index 610191fb2cdb8..aad3fbc00eaae 100644
--- a/packages/react-devtools-shared/src/__tests__/setupTests.js
+++ b/packages/react-devtools-shared/src/__tests__/setupTests.js
@@ -116,6 +116,16 @@ function shouldIgnoreConsoleErrorOrWarn(args) {
return false;
}
+ const maybeError = args[1];
+ if (
+ maybeError !== null &&
+ typeof maybeError === 'object' &&
+ maybeError.message === 'Simulated error coming from DevTools'
+ ) {
+ // Error from forcing an error boundary.
+ return true;
+ }
+
return global._ignoredErrorOrWarningMessages.some(errorOrWarningMessage => {
return firstArg.indexOf(errorOrWarningMessage) !== -1;
});
diff --git a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
index 439315dc85836..ce423e94895bb 100644
--- a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
+++ b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
@@ -18,12 +18,14 @@ import {
describe('Store component filters', () => {
let React;
let Types;
+ let agent;
let bridge: FrontendBridge;
let store: Store;
let utils;
let actAsync;
beforeEach(() => {
+ agent = global.agent;
bridge = global.bridge;
store = global.store;
store.collapseNodesByDefault = false;
@@ -740,4 +742,80 @@ describe('Store component filters', () => {
`);
});
});
+
+ // @reactVersion >= 16.6
+ it('resets forced error and fallback states when filters are changed', async () => {
+ store.componentFilters = [];
+ class ErrorBoundary extends React.Component {
+ state = {hasError: false};
+
+ static getDerivedStateFromError() {
+ return {hasError: true};
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return
;
+ }
+ return this.props.children;
+ }
+ }
+
+ function App() {
+ return (
+ <>
+ }>
+
+
+
+
+
+ >
+ );
+ }
+
+ await actAsync(async () => {
+ render();
+ });
+ const rendererID = utils.getRendererID();
+ await actAsync(() => {
+ agent.overrideSuspense({
+ id: store.getElementIDAtIndex(2),
+ rendererID,
+ forceFallback: true,
+ });
+ agent.overrideError({
+ id: store.getElementIDAtIndex(4),
+ rendererID,
+ forceError: true,
+ });
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+ ▾
+
+ ▾
+
+ [suspense-root] rects={[]}
+
+ `);
+
+ await actAsync(() => {
+ store.componentFilters = [
+ utils.createElementTypeFilter(Types.ElementTypeFunction, true),
+ ];
+ });
+
+ expect(store).toMatchInlineSnapshot(`
+ [root]
+ ▾
+
+ ▾
+
+ [suspense-root] rects={[]}
+
+ `);
+ });
});
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 8cdc731058f83..ddafd014e7ff6 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -1512,6 +1512,11 @@ export function attach(
throw Error('Cannot modify filter preferences while profiling');
}
+ const previousForcedFallbacks =
+ forceFallbackForFibers.size > 0 ? new Set(forceFallbackForFibers) : null;
+ const previousForcedErrors =
+ forceErrorForFibers.size > 0 ? new Map(forceErrorForFibers) : null;
+
// Recursively unmount all roots.
hook.getFiberRoots(rendererID).forEach(root => {
const rootInstance = rootToFiberInstanceMap.get(root);
@@ -1532,6 +1537,41 @@ export function attach(
// Reset pseudo counters so that new path selections will be persisted.
rootDisplayNameCounter.clear();
+ // We just cleared all the forced states. Schedule updates on the affected Fibers
+ // so that we get their initial states again according to the new filters.
+ if (typeof scheduleUpdate === 'function') {
+ if (previousForcedFallbacks !== null) {
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+ for (const fiber of previousForcedFallbacks) {
+ if (typeof scheduleRetry === 'function') {
+ scheduleRetry(fiber);
+ } else {
+ scheduleUpdate(fiber);
+ }
+ }
+ }
+ if (
+ previousForcedErrors !== null &&
+ typeof setErrorHandler === 'function'
+ ) {
+ // Unlike for Suspense, disabling the forced error state requires setting
+ // the status to false first. `shouldErrorFiberAccordingToMap` will clear
+ // the Fibers later.
+ setErrorHandler(shouldErrorFiberAccordingToMap);
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+ for (const [fiber, shouldError] of previousForcedErrors) {
+ forceErrorForFibers.set(fiber, false);
+ if (shouldError) {
+ if (typeof scheduleRetry === 'function') {
+ scheduleRetry(fiber);
+ } else {
+ scheduleUpdate(fiber);
+ }
+ }
+ }
+ }
+ }
+
// Recursively re-mount all roots with new filter criteria applied.
hook.getFiberRoots(rendererID).forEach(root => {
const current = root.current;