Skip to content

Commit c9ddee7

Browse files
authored
[DevTools] Reset forced states when changing component filters (#34929)
1 parent 6fb7754 commit c9ddee7

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

‎packages/react-devtools-shared/src/__tests__/setupTests.js‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,16 @@ function shouldIgnoreConsoleErrorOrWarn(args) {
116116
return false;
117117
}
118118

119+
const maybeError = args[1];
120+
if (
121+
maybeError !== null &&
122+
typeof maybeError === 'object' &&
123+
maybeError.message === 'Simulated error coming from DevTools'
124+
) {
125+
// Error from forcing an error boundary.
126+
return true;
127+
}
128+
119129
return global._ignoredErrorOrWarningMessages.some(errorOrWarningMessage => {
120130
return firstArg.indexOf(errorOrWarningMessage) !== -1;
121131
});

‎packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ import {
1818
describe('Store component filters', () => {
1919
let React;
2020
let Types;
21+
let agent;
2122
let bridge: FrontendBridge;
2223
let store: Store;
2324
let utils;
2425
let actAsync;
2526

2627
beforeEach(() => {
28+
agent = global.agent;
2729
bridge = global.bridge;
2830
store = global.store;
2931
store.collapseNodesByDefault = false;
@@ -740,4 +742,80 @@ describe('Store component filters', () => {
740742
`);
741743
});
742744
});
745+
746+
// @reactVersion >= 16.6
747+
it('resets forced error and fallback states when filters are changed', async () => {
748+
store.componentFilters = [];
749+
class ErrorBoundary extends React.Component {
750+
state = {hasError: false};
751+
752+
static getDerivedStateFromError() {
753+
return {hasError: true};
754+
}
755+
756+
render() {
757+
if (this.state.hasError) {
758+
return <div key="did-error" />;
759+
}
760+
return this.props.children;
761+
}
762+
}
763+
764+
function App() {
765+
return (
766+
<>
767+
<React.Suspense fallback={<div key="loading" />}>
768+
<div key="suspense-content" />
769+
</React.Suspense>
770+
<ErrorBoundary>
771+
<div key="error-content" />
772+
</ErrorBoundary>
773+
</>
774+
);
775+
}
776+
777+
await actAsync(async () => {
778+
render(<App />);
779+
});
780+
const rendererID = utils.getRendererID();
781+
await actAsync(() => {
782+
agent.overrideSuspense({
783+
id: store.getElementIDAtIndex(2),
784+
rendererID,
785+
forceFallback: true,
786+
});
787+
agent.overrideError({
788+
id: store.getElementIDAtIndex(4),
789+
rendererID,
790+
forceError: true,
791+
});
792+
});
793+
794+
expect(store).toMatchInlineSnapshot(`
795+
[root]
796+
▾ <App>
797+
▾ <Suspense>
798+
<div key="loading">
799+
▾ <ErrorBoundary>
800+
<div key="did-error">
801+
[suspense-root] rects={[]}
802+
<Suspense name="App" rects={[]}>
803+
`);
804+
805+
await actAsync(() => {
806+
store.componentFilters = [
807+
utils.createElementTypeFilter(Types.ElementTypeFunction, true),
808+
];
809+
});
810+
811+
expect(store).toMatchInlineSnapshot(`
812+
[root]
813+
▾ <Suspense>
814+
<div key="suspense-content">
815+
▾ <ErrorBoundary>
816+
<div key="error-content">
817+
[suspense-root] rects={[]}
818+
<Suspense name="Unknown" rects={[]}>
819+
`);
820+
});
743821
});

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,11 @@ export function attach(
15121512
throw Error('Cannot modify filter preferences while profiling');
15131513
}
15141514
1515+
const previousForcedFallbacks =
1516+
forceFallbackForFibers.size > 0 ? new Set(forceFallbackForFibers) : null;
1517+
const previousForcedErrors =
1518+
forceErrorForFibers.size > 0 ? new Map(forceErrorForFibers) : null;
1519+
15151520
// Recursively unmount all roots.
15161521
hook.getFiberRoots(rendererID).forEach(root => {
15171522
const rootInstance = rootToFiberInstanceMap.get(root);
@@ -1532,6 +1537,41 @@ export function attach(
15321537
// Reset pseudo counters so that new path selections will be persisted.
15331538
rootDisplayNameCounter.clear();
15341539
1540+
// We just cleared all the forced states. Schedule updates on the affected Fibers
1541+
// so that we get their initial states again according to the new filters.
1542+
if (typeof scheduleUpdate === 'function') {
1543+
if (previousForcedFallbacks !== null) {
1544+
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
1545+
for (const fiber of previousForcedFallbacks) {
1546+
if (typeof scheduleRetry === 'function') {
1547+
scheduleRetry(fiber);
1548+
} else {
1549+
scheduleUpdate(fiber);
1550+
}
1551+
}
1552+
}
1553+
if (
1554+
previousForcedErrors !== null &&
1555+
typeof setErrorHandler === 'function'
1556+
) {
1557+
// Unlike for Suspense, disabling the forced error state requires setting
1558+
// the status to false first. `shouldErrorFiberAccordingToMap` will clear
1559+
// the Fibers later.
1560+
setErrorHandler(shouldErrorFiberAccordingToMap);
1561+
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
1562+
for (const [fiber, shouldError] of previousForcedErrors) {
1563+
forceErrorForFibers.set(fiber, false);
1564+
if (shouldError) {
1565+
if (typeof scheduleRetry === 'function') {
1566+
scheduleRetry(fiber);
1567+
} else {
1568+
scheduleUpdate(fiber);
1569+
}
1570+
}
1571+
}
1572+
}
1573+
}
1574+
15351575
// Recursively re-mount all roots with new filter criteria applied.
15361576
hook.getFiberRoots(rendererID).forEach(root => {
15371577
const current = root.current;

0 commit comments

Comments
 (0)