();
- function shouldSuspendFiberAccordingToSet(fiber: any) {
- let fiberInstance = fiberToFiberInstanceMap.get(fiber);
- if (fiberInstance === undefined && fiber.alternate !== null) {
- fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate);
- }
+ function shouldSuspendFiberAccordingToSet(fiber: Fiber): boolean {
return (
- fiberInstance !== undefined &&
- (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0
+ forceFallbackForFibers.has(fiber) ||
+ (fiber.alternate !== null && forceFallbackForFibers.has(fiber.alternate))
);
}
@@ -5515,33 +5527,36 @@ export function attach(
if (devtoolsInstance === undefined) {
return;
}
+ const nearestFiber = getNearestFiber(devtoolsInstance);
+ if (nearestFiber === null) {
+ return;
+ }
+ let fiber = nearestFiber;
+ while (fiber.tag !== SuspenseComponent) {
+ if (fiber.return === null) {
+ return;
+ }
+ fiber = fiber.return;
+ }
+ if (fiber.alternate !== null) {
+ // We only need one of the Fibers in the set.
+ forceFallbackForFibers.delete(fiber.alternate);
+ }
if (forceFallback) {
- if ((devtoolsInstance.flags & FORCE_SUSPENSE_FALLBACK) === 0) {
- devtoolsInstance.flags |= FORCE_SUSPENSE_FALLBACK;
- forceFallbackCount++;
- if (forceFallbackCount === 1) {
- // First override is added. Switch React to slower path.
- setSuspenseHandler(shouldSuspendFiberAccordingToSet);
- }
+ forceFallbackForFibers.add(fiber);
+ if (forceFallbackForFibers.size === 1) {
+ // First override is added. Switch React to slower path.
+ setSuspenseHandler(shouldSuspendFiberAccordingToSet);
}
} else {
- if ((devtoolsInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0) {
- devtoolsInstance.flags &= ~FORCE_SUSPENSE_FALLBACK;
- forceFallbackCount--;
- if (forceFallbackCount === 0) {
- // Last override is gone. Switch React back to fast path.
- setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
- }
+ forceFallbackForFibers.delete(fiber);
+ if (forceFallbackForFibers.size === 0) {
+ // Last override is gone. Switch React back to fast path.
+ setSuspenseHandler(shouldSuspendFiberAlwaysFalse);
}
}
-
- if (devtoolsInstance.kind === FIBER_INSTANCE) {
- const fiber = devtoolsInstance.data;
- scheduleUpdate(fiber);
- } else {
- // TODO: Handle VirtualInstance.
- }
+ scheduleUpdate(fiber);
}
// Remember if we're trying to restore the selection after reload.
diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js
index fa29d007a681d..828d05fc27f81 100644
--- a/packages/react-devtools-shared/src/backend/legacy/renderer.js
+++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js
@@ -826,7 +826,6 @@ export function attach(
// Toggle error boundary did not exist in legacy versions
canToggleError: false,
isErrored: false,
- targetErrorBoundaryID: null,
// Suspense did not exist in legacy versions
canToggleSuspense: false,
diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js
index 982426f2ffb19..3ceec410803e3 100644
--- a/packages/react-devtools-shared/src/backend/types.js
+++ b/packages/react-devtools-shared/src/backend/types.js
@@ -252,7 +252,6 @@ export type InspectedElement = {
// Is this Error, and can its value be overridden now?
canToggleError: boolean,
isErrored: boolean,
- targetErrorBoundaryID: ?number,
// Is this Suspense, and can its value be overridden now?
canToggleSuspense: boolean,
diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js
index d0d5c7951683a..7a8bb801445c1 100644
--- a/packages/react-devtools-shared/src/backendAPI.js
+++ b/packages/react-devtools-shared/src/backendAPI.js
@@ -221,7 +221,6 @@ export function convertInspectedElementBackendToFrontend(
canEditHooksAndRenamePaths,
canToggleError,
isErrored,
- targetErrorBoundaryID,
canToggleSuspense,
canViewSource,
hasLegacyContext,
@@ -251,7 +250,6 @@ export function convertInspectedElementBackendToFrontend(
canEditHooksAndRenamePaths,
canToggleError,
isErrored,
- targetErrorBoundaryID,
canToggleSuspense,
canViewSource,
hasLegacyContext,
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/CannotSuspendWarningMessage.js b/packages/react-devtools-shared/src/devtools/views/Components/CannotSuspendWarningMessage.js
deleted file mode 100644
index 71fce90b5b80b..0000000000000
--- a/packages/react-devtools-shared/src/devtools/views/Components/CannotSuspendWarningMessage.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import * as React from 'react';
-import {useContext} from 'react';
-import {StoreContext} from '../context';
-import {
- ComponentFilterElementType,
- ElementTypeSuspense,
-} from 'react-devtools-shared/src/frontend/types';
-
-export default function CannotSuspendWarningMessage(): React.Node {
- const store = useContext(StoreContext);
- const areSuspenseElementsHidden = !!store.componentFilters.find(
- filter =>
- filter.type === ComponentFilterElementType &&
- filter.value === ElementTypeSuspense &&
- filter.isEnabled,
- );
-
- // Has the user filtered out Suspense nodes from the tree?
- // If so, the selected element might actually be in a Suspense tree after all.
- if (areSuspenseElementsHidden) {
- return (
-
- Suspended state cannot be toggled while Suspense components are hidden.
- Disable the filter and try again.
-
- );
- } else {
- return (
-
- The selected element is not within a Suspense container. Suspending it
- would cause an error.
-
- );
- }
-}
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
index 5dd887ba59666..de9f3490190d9 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js
@@ -9,15 +9,13 @@
import * as React from 'react';
import {useCallback, useContext, useSyncExternalStore} from 'react';
-import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
+import {TreeStateContext} from './TreeContext';
import {BridgeContext, StoreContext, OptionsContext} from '../context';
import Button from '../Button';
import ButtonIcon from '../ButtonIcon';
import Icon from '../Icon';
-import {ModalDialogContext} from '../ModalDialog';
import Toggle from '../Toggle';
import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types';
-import CannotSuspendWarningMessage from './CannotSuspendWarningMessage';
import InspectedElementView from './InspectedElementView';
import {InspectedElementContext} from './InspectedElementContext';
import {getOpenInEditorURL} from '../../../utils';
@@ -38,7 +36,6 @@ export type Props = {};
export default function InspectedElementWrapper(_: Props): React.Node {
const {inspectedElementID} = useContext(TreeStateContext);
- const dispatch = useContext(TreeDispatcherContext);
const bridge = useContext(BridgeContext);
const store = useContext(StoreContext);
const {
@@ -47,7 +44,6 @@ export default function InspectedElementWrapper(_: Props): React.Node {
hideLogAction,
hideViewSourceAction,
} = useContext(OptionsContext);
- const {dispatch: modalDialogDispatch} = useContext(ModalDialogContext);
const {hookNames, inspectedElement, parseHookNames, toggleParseHookNames} =
useContext(InspectedElementContext);
@@ -105,8 +101,6 @@ export default function InspectedElementWrapper(_: Props): React.Node {
}, [bridge, inspectedElementID, store]);
const isErrored = inspectedElement != null && inspectedElement.isErrored;
- const targetErrorBoundaryID =
- inspectedElement != null ? inspectedElement.targetErrorBoundaryID : null;
const isSuspended =
element !== null &&
@@ -137,79 +131,43 @@ export default function InspectedElementWrapper(_: Props): React.Node {
);
const toggleErrored = useCallback(() => {
- if (inspectedElement == null || targetErrorBoundaryID == null) {
+ if (inspectedElement == null) {
return;
}
- const rendererID = store.getRendererIDForElement(targetErrorBoundaryID);
+ const rendererID = store.getRendererIDForElement(inspectedElement.id);
if (rendererID !== null) {
- if (targetErrorBoundaryID !== inspectedElement.id) {
- // Update tree selection so that if we cause a component to error,
- // the nearest error boundary will become the newly selected thing.
- dispatch({
- type: 'SELECT_ELEMENT_BY_ID',
- payload: targetErrorBoundaryID,
- });
- }
-
// Toggle error.
+ // Because triggering an error will always delete the children, we'll
+ // automatically select the nearest still mounted instance which will be
+ // the error boundary.
bridge.send('overrideError', {
- id: targetErrorBoundaryID,
+ id: inspectedElement.id,
rendererID,
forceError: !isErrored,
});
}
- }, [bridge, dispatch, isErrored, targetErrorBoundaryID]);
+ }, [bridge, store, isErrored, inspectedElement]);
// TODO (suspense toggle) Would be nice to eventually use a two setState pattern here as well.
const toggleSuspended = useCallback(() => {
- let nearestSuspenseElement = null;
- let currentElement = element;
- while (currentElement !== null) {
- if (currentElement.type === ElementTypeSuspense) {
- nearestSuspenseElement = currentElement;
- break;
- } else if (currentElement.parentID > 0) {
- currentElement = store.getElementByID(currentElement.parentID);
- } else {
- currentElement = null;
- }
+ if (inspectedElement == null) {
+ return;
}
- // If we didn't find a Suspense ancestor, we can't suspend.
- // Instead we can show a warning to the user.
- if (nearestSuspenseElement === null) {
- modalDialogDispatch({
- id: 'InspectedElement',
- type: 'SHOW',
- content: ,
- });
- } else {
- const nearestSuspenseElementID = nearestSuspenseElement.id;
-
- // If we're suspending from an arbitrary (non-Suspense) component, select the nearest Suspense element in the Tree.
- // This way when the fallback UI is shown and the current element is hidden, something meaningful is selected.
- if (nearestSuspenseElement !== element) {
- dispatch({
- type: 'SELECT_ELEMENT_BY_ID',
- payload: nearestSuspenseElementID,
- });
- }
-
- const rendererID = store.getRendererIDForElement(
- nearestSuspenseElementID,
- );
-
+ const rendererID = store.getRendererIDForElement(inspectedElement.id);
+ if (rendererID !== null) {
// Toggle suspended
- if (rendererID !== null) {
- bridge.send('overrideSuspense', {
- id: nearestSuspenseElementID,
- rendererID,
- forceFallback: !isSuspended,
- });
- }
+ // Because suspending or unsuspending always delete the children or fallback,
+ // we'll automatically select the nearest still mounted instance which will be
+ // the Suspense boundary.
+ bridge.send('overrideSuspense', {
+ id: inspectedElement.id,
+ rendererID,
+ forceFallback: !isSuspended,
+ });
}
- }, [bridge, dispatch, element, isSuspended, modalDialogDispatch, store]);
+ }, [bridge, store, isSuspended, inspectedElement]);
if (element === null) {
return (
diff --git a/packages/react-devtools-shared/src/frontend/types.js b/packages/react-devtools-shared/src/frontend/types.js
index 459015d0500b8..6216283301bf5 100644
--- a/packages/react-devtools-shared/src/frontend/types.js
+++ b/packages/react-devtools-shared/src/frontend/types.js
@@ -219,7 +219,6 @@ export type InspectedElement = {
// Is this Error, and can its value be overridden now?
isErrored: boolean,
canToggleError: boolean,
- targetErrorBoundaryID: ?number,
// Is this Suspense, and can its value be overridden now?
canToggleSuspense: boolean,