diff --git a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js index cd777f8333763..65c7e12bccf34 100644 --- a/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js +++ b/packages/react-devtools-shared/src/__tests__/inspectedElement-test.js @@ -2975,16 +2975,12 @@ describe('InspectedElement', () => { // Inspect and see that we cannot toggle error state // on error boundary itself let inspectedElement = await inspect(0); - expect(inspectedElement.canToggleError).toBe(false); - expect(inspectedElement.targetErrorBoundaryID).toBe(null); + expect(inspectedElement.canToggleError).toBe(true); // Inspect inspectedElement = await inspect(1); expect(inspectedElement.canToggleError).toBe(true); expect(inspectedElement.isErrored).toBe(false); - expect(inspectedElement.targetErrorBoundaryID).toBe( - targetErrorBoundaryID, - ); // Suppress expected error and warning. const consoleErrorMock = jest @@ -3009,10 +3005,6 @@ describe('InspectedElement', () => { inspectedElement = await inspect(0); expect(inspectedElement.canToggleError).toBe(true); expect(inspectedElement.isErrored).toBe(true); - // its error boundary ID is itself because it's caught the error - expect(inspectedElement.targetErrorBoundaryID).toBe( - targetErrorBoundaryID, - ); await toggleError(false); @@ -3020,9 +3012,6 @@ describe('InspectedElement', () => { inspectedElement = await inspect(1); expect(inspectedElement.canToggleError).toBe(true); expect(inspectedElement.isErrored).toBe(false); - expect(inspectedElement.targetErrorBoundaryID).toBe( - targetErrorBoundaryID, - ); }); }); diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 5e5167557f20c..4fada3907f0ee 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -148,11 +148,6 @@ const FIBER_INSTANCE = 0; const VIRTUAL_INSTANCE = 1; const FILTERED_FIBER_INSTANCE = 2; -// Flags -const FORCE_SUSPENSE_FALLBACK = /* */ 0b001; -const FORCE_ERROR = /* */ 0b010; -const FORCE_ERROR_RESET = /* */ 0b100; - // This type represents a stateful instance of a Client Component i.e. a Fiber pair. // These instances also let us track stateful DevTools meta data like id and warnings. type FiberInstance = { @@ -161,7 +156,6 @@ type FiberInstance = { parent: null | DevToolsInstance, firstChild: null | DevToolsInstance, nextSibling: null | DevToolsInstance, - flags: number, // Force Error/Suspense source: null | string | Error | Source, // source location of this component function, or owned child stack errors: null | Map, // error messages and count warnings: null | Map, // warning messages and count @@ -176,7 +170,6 @@ function createFiberInstance(fiber: Fiber): FiberInstance { parent: null, firstChild: null, nextSibling: null, - flags: 0, source: null, errors: null, warnings: null, @@ -193,7 +186,6 @@ type FilteredFiberInstance = { parent: null | DevToolsInstance, firstChild: null | DevToolsInstance, nextSibling: null | DevToolsInstance, - flags: number, // Force Error/Suspense source: null | string | Error | Source, // always null here. errors: null, // error messages and count warnings: null, // warning messages and count @@ -209,7 +201,6 @@ function createFilteredFiberInstance(fiber: Fiber): FilteredFiberInstance { parent: null, firstChild: null, nextSibling: null, - flags: 0, componentStack: null, errors: null, warnings: null, @@ -229,7 +220,6 @@ type VirtualInstance = { parent: null | DevToolsInstance, firstChild: null | DevToolsInstance, nextSibling: null | DevToolsInstance, - flags: number, source: null | string | Error | Source, // source location of this server component, or owned child stack // Errors and Warnings happen per ReactComponentInfo which can appear in // multiple places but we track them per stateful VirtualInstance so @@ -251,7 +241,6 @@ function createVirtualInstance( parent: null, firstChild: null, nextSibling: null, - flags: 0, source: null, errors: null, warnings: null, @@ -1080,12 +1069,12 @@ export function attach( args: $ReadOnlyArray, ): void { if (type === 'error') { - let fiberInstance = fiberToFiberInstanceMap.get(fiber); - if (fiberInstance === undefined && fiber.alternate !== null) { - fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate); - } // if this is an error simulated by us to trigger error boundary, ignore - if (fiberInstance !== undefined && fiberInstance.flags & FORCE_ERROR) { + if ( + forceErrorForFibers.get(fiber) === true || + (fiber.alternate !== null && + forceErrorForFibers.get(fiber.alternate) === true) + ) { return; } } @@ -1577,6 +1566,26 @@ export function attach( // Removes a Fiber (and its alternate) from the Maps used to track their id. // This method should always be called when a Fiber is unmounting. function untrackFiber(nearestInstance: DevToolsInstance, fiber: Fiber) { + if (forceErrorForFibers.size > 0) { + forceErrorForFibers.delete(fiber); + if (fiber.alternate) { + forceErrorForFibers.delete(fiber.alternate); + } + if (forceErrorForFibers.size === 0 && setErrorHandler != null) { + setErrorHandler(shouldErrorFiberAlwaysNull); + } + } + + if (forceFallbackForFibers.size > 0) { + forceFallbackForFibers.delete(fiber); + if (fiber.alternate) { + forceFallbackForFibers.delete(fiber.alternate); + } + if (forceFallbackForFibers.size === 0 && setSuspenseHandler != null) { + setSuspenseHandler(shouldSuspendFiberAlwaysFalse); + } + } + // TODO: Consider using a WeakMap instead. The only thing where that doesn't work // is React Native Paper which tracks tags but that support is eventually going away // and can use the old findFiberByHostInstance strategy. @@ -2465,21 +2474,6 @@ export function attach( fiberInstance.warnings = null; } - if (fiberInstance.flags & FORCE_ERROR) { - fiberInstance.flags &= ~FORCE_ERROR; - forceErrorCount--; - if (forceErrorCount === 0 && setErrorHandler != null) { - setErrorHandler(shouldErrorFiberAlwaysNull); - } - } - if (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) { - fiberInstance.flags &= ~FORCE_SUSPENSE_FALLBACK; - forceFallbackCount--; - if (forceFallbackCount === 0 && setSuspenseHandler != null) { - setSuspenseHandler(shouldSuspendFiberAlwaysFalse); - } - } - if (fiberToFiberInstanceMap.get(fiber) === fiberInstance) { fiberToFiberInstanceMap.delete(fiber); } @@ -4208,18 +4202,6 @@ export function attach( } } - function getNearestErrorBoundaryID(fiber: Fiber): number | null { - let parent = fiber.return; - while (parent !== null) { - if (isErrorBoundary(parent)) { - // TODO: If this boundary is filtered it won't have an ID. - return getFiberIDUnsafe(parent); - } - parent = parent.return; - } - return null; - } - function inspectElementRaw(id: number): InspectedElement | null { const devtoolsInstance = idToDevToolsInstanceMap.get(id); if (devtoolsInstance === undefined) { @@ -4374,9 +4356,6 @@ export function attach( const owners: null | Array = getOwnersListFromInstance(fiberInstance); - const isTimedOutSuspense = - tag === SuspenseComponent && memoizedState !== null; - let hooks = null; if (usesHooks) { const originalConsoleMethods: {[string]: $FlowFixMe} = {}; @@ -4406,16 +4385,26 @@ export function attach( let rootType = null; let current = fiber; + let hasErrorBoundary = false; + let hasSuspenseBoundary = false; while (current.return !== null) { + const temp = current; current = current.return; + if (temp.tag === SuspenseComponent) { + hasSuspenseBoundary = true; + } else if (isErrorBoundary(temp)) { + hasErrorBoundary = true; + } } const fiberRoot = current.stateNode; if (fiberRoot != null && fiberRoot._debugRootType !== null) { rootType = fiberRoot._debugRootType; } + const isTimedOutSuspense = + tag === SuspenseComponent && memoizedState !== null; + let isErrored = false; - let targetErrorBoundaryID; if (isErrorBoundary(fiber)) { // if the current inspected element is an error boundary, // either that we want to use it to toggle off error state @@ -4428,12 +4417,9 @@ export function attach( const DidCapture = 0b000000000000000000010000000; isErrored = (fiber.flags & DidCapture) !== 0 || - (fiberInstance.flags & FORCE_ERROR) !== 0; - targetErrorBoundaryID = isErrored - ? fiberInstance.id - : getNearestErrorBoundaryID(fiber); - } else { - targetErrorBoundaryID = getNearestErrorBoundaryID(fiber); + forceErrorForFibers.get(fiber) === true || + (fiber.alternate !== null && + forceErrorForFibers.get(fiber.alternate) === true); } const plugins: Plugins = { @@ -4468,18 +4454,20 @@ export function attach( canEditFunctionPropsRenamePaths: typeof overridePropsRenamePath === 'function', - canToggleError: supportsTogglingError && targetErrorBoundaryID != null, + canToggleError: supportsTogglingError && hasErrorBoundary, // Is this error boundary in error state. isErrored, - targetErrorBoundaryID, canToggleSuspense: supportsTogglingSuspense && + hasSuspenseBoundary && // If it's showing the real content, we can always flip fallback. (!isTimedOutSuspense || // If it's showing fallback because we previously forced it to, // allow toggling it back to remove the fallback override. - (fiberInstance.flags & FORCE_SUSPENSE_FALLBACK) !== 0), + forceFallbackForFibers.has(fiber) || + (fiber.alternate !== null && + forceFallbackForFibers.has(fiber.alternate))), // Can view component source location. canViewSource, @@ -4533,22 +4521,24 @@ export function attach( getOwnersListFromInstance(virtualInstance); let rootType = null; - let targetErrorBoundaryID = null; - let parent = virtualInstance.parent; - while (parent !== null) { - if (parent.kind !== VIRTUAL_INSTANCE) { - targetErrorBoundaryID = getNearestErrorBoundaryID(parent.data); - let current = parent.data; - while (current.return !== null) { - current = current.return; - } - const fiberRoot = current.stateNode; - if (fiberRoot != null && fiberRoot._debugRootType !== null) { - rootType = fiberRoot._debugRootType; + let hasErrorBoundary = false; + let hasSuspenseBoundary = false; + const nearestFiber = getNearestFiber(virtualInstance); + if (nearestFiber !== null) { + let current = nearestFiber; + while (current.return !== null) { + const temp = current; + current = current.return; + if (temp.tag === SuspenseComponent) { + hasSuspenseBoundary = true; + } else if (isErrorBoundary(temp)) { + hasErrorBoundary = true; } - break; } - parent = parent.parent; + const fiberRoot = current.stateNode; + if (fiberRoot != null && fiberRoot._debugRootType !== null) { + rootType = fiberRoot._debugRootType; + } } const plugins: Plugins = { @@ -4566,11 +4556,10 @@ export function attach( canEditFunctionPropsDeletePaths: false, canEditFunctionPropsRenamePaths: false, - canToggleError: supportsTogglingError && targetErrorBoundaryID != null, + canToggleError: supportsTogglingError && hasErrorBoundary, isErrored: false, - targetErrorBoundaryID, - canToggleSuspense: supportsTogglingSuspense, + canToggleSuspense: supportsTogglingSuspense && hasSuspenseBoundary, // Can view component source location. canViewSource, @@ -5406,30 +5395,43 @@ export function attach( ); } + function getNearestFiber(devtoolsInstance: DevToolsInstance): null | Fiber { + if (devtoolsInstance.kind === VIRTUAL_INSTANCE) { + let inst: DevToolsInstance = devtoolsInstance; + while (inst.kind === VIRTUAL_INSTANCE) { + // For virtual instances, we search deeper until we find a Fiber instance. + // Then we search upwards from that Fiber. That's because Virtual Instances + // will always have an Fiber child filtered or not. If we searched its parents + // we might skip through a filtered Error Boundary before we hit a FiberInstance. + if (inst.firstChild === null) { + return null; + } + inst = inst.firstChild; + } + return inst.data.return; + } else { + return devtoolsInstance.data; + } + } + // React will switch between these implementations depending on whether // we have any manually suspended/errored-out Fibers or not. function shouldErrorFiberAlwaysNull() { return null; } - let forceErrorCount = 0; + // Map of Fiber and its force error status: true (error), false (toggled off) + const forceErrorForFibers = new Map(); - function shouldErrorFiberAccordingToMap(fiber: any): null | boolean { + function shouldErrorFiberAccordingToMap(fiber: any): boolean { if (typeof setErrorHandler !== 'function') { throw new Error( 'Expected overrideError() to not get called for earlier React versions.', ); } - let fiberInstance = fiberToFiberInstanceMap.get(fiber); - if (fiberInstance === undefined && fiber.alternate !== null) { - fiberInstance = fiberToFiberInstanceMap.get(fiber.alternate); - } - if (fiberInstance === undefined) { - return null; - } - - if (fiberInstance.flags & FORCE_ERROR_RESET) { + let status = forceErrorForFibers.get(fiber); + if (status === false) { // TRICKY overrideError adds entries to this Map, // so ideally it would be the method that clears them too, // but that would break the functionality of the feature, @@ -5439,18 +5441,27 @@ export function attach( // Technically this is premature and we should schedule it for later, // since the render could always fail without committing the updated error boundary, // but since this is a DEV-only feature, the simplicity is worth the trade off. - forceErrorCount--; - fiberInstance.flags &= ~FORCE_ERROR_RESET; - if (forceErrorCount === 0) { + forceErrorForFibers.delete(fiber); + if (forceErrorForFibers.size === 0) { // Last override is gone. Switch React back to fast path. setErrorHandler(shouldErrorFiberAlwaysNull); } return false; - } else if (fiberInstance.flags & FORCE_ERROR) { - return true; - } else { - return null; } + if (status === undefined && fiber.alternate !== null) { + status = forceErrorForFibers.get(fiber.alternate); + if (status === false) { + forceErrorForFibers.delete(fiber.alternate); + if (forceErrorForFibers.size === 0) { + // Last override is gone. Switch React back to fast path. + setErrorHandler(shouldErrorFiberAlwaysNull); + } + } + } + if (status === undefined) { + return false; + } + return status; } function overrideError(id: number, forceError: boolean) { @@ -5467,38 +5478,39 @@ export function attach( if (devtoolsInstance === undefined) { return; } - if ((devtoolsInstance.flags & (FORCE_ERROR | FORCE_ERROR_RESET)) === 0) { - forceErrorCount++; - if (forceErrorCount === 1) { - // First override is added. Switch React to slower path. - setErrorHandler(shouldErrorFiberAccordingToMap); + const nearestFiber = getNearestFiber(devtoolsInstance); + if (nearestFiber === null) { + return; + } + let fiber = nearestFiber; + while (!isErrorBoundary(fiber)) { + if (fiber.return === null) { + return; } + fiber = fiber.return; } - devtoolsInstance.flags &= forceError ? ~FORCE_ERROR_RESET : ~FORCE_ERROR; - devtoolsInstance.flags |= forceError ? FORCE_ERROR : FORCE_ERROR_RESET; - - if (devtoolsInstance.kind === FIBER_INSTANCE) { - const fiber = devtoolsInstance.data; - scheduleUpdate(fiber); - } else { - // TODO: Handle VirtualInstance. + forceErrorForFibers.set(fiber, forceError); + if (fiber.alternate !== null) { + // We only need one of the Fibers in the set. + forceErrorForFibers.delete(fiber.alternate); + } + if (forceErrorForFibers.size === 1) { + // First override is added. Switch React to slower path. + setErrorHandler(shouldErrorFiberAccordingToMap); } + scheduleUpdate(fiber); } function shouldSuspendFiberAlwaysFalse() { return false; } - let forceFallbackCount = 0; + const forceFallbackForFibers = new Set(); - 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,