diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js index 00605d857a153..ae01abaf3c2a2 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js @@ -752,4 +752,97 @@ describe('ProfilingCache', () => { utils.act(() => store.profilerStore.stopProfiling()); expect(container.textContent).toBe('About'); }); + + it('components that were deleted and added to updaters during the layout phase should not crash', () => { + let setBarUnmounted; + function Bar() { + const [, setState] = React.useState(false); + + React.useLayoutEffect(() => { + return () => setState(true); + }); + + return null; + } + + function App() { + const [barUnmounted, _setBarUnmounted] = React.useState(false); + setBarUnmounted = _setBarUnmounted; + return <>{!barUnmounted && }; + } + + const root = ReactDOM.createRoot(document.createElement('div')); + utils.act(() => root.render()); + utils.act(() => store.profilerStore.startProfiling()); + utils.act(() => setBarUnmounted(true)); + utils.act(() => store.profilerStore.stopProfiling()); + + const updaters = store.profilerStore.getCommitData(store.roots[0], 0) + .updaters; + expect(updaters.length).toEqual(1); + expect(updaters[0].displayName).toEqual('App'); + }); + + it('components in a deleted subtree and added to updaters during the layout phase should not crash', () => { + function Foo() { + return ; + } + + let setBarUnmounted; + function Bar() { + const [, setState] = React.useState(false); + + React.useLayoutEffect(() => { + return () => setState(true); + }); + + return null; + } + + function App() { + const [barUnmounted, _setBarUnmounted] = React.useState(false); + setBarUnmounted = _setBarUnmounted; + return <>{!barUnmounted && }; + } + + const root = ReactDOM.createRoot(document.createElement('div')); + utils.act(() => root.render()); + utils.act(() => store.profilerStore.startProfiling()); + utils.act(() => setBarUnmounted(true)); + utils.act(() => store.profilerStore.stopProfiling()); + + const updaters = store.profilerStore.getCommitData(store.roots[0], 0) + .updaters; + expect(updaters.length).toEqual(1); + expect(updaters[0].displayName).toEqual('App'); + }); + + it('components that were deleted should not be added to updaters during the passive phase', () => { + let setBarUnmounted; + function Bar() { + const [, setState] = React.useState(false); + React.useEffect(() => { + return () => setState(true); + }); + + return null; + } + + function App() { + const [barUnmounted, _setBarUnmounted] = React.useState(false); + setBarUnmounted = _setBarUnmounted; + return <>{!barUnmounted && }; + } + + const root = ReactDOM.createRoot(document.createElement('div')); + utils.act(() => root.render()); + utils.act(() => store.profilerStore.startProfiling()); + utils.act(() => setBarUnmounted(true)); + utils.act(() => store.profilerStore.stopProfiling()); + + const updaters = store.profilerStore.getCommitData(store.roots[0], 0) + .updaters; + expect(updaters.length).toEqual(1); + expect(updaters[0].displayName).toEqual('App'); + }); }); diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 631521791a549..3ef4250aaceab 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -2573,7 +2573,9 @@ export function attach( function getUpdatersList(root): Array | null { return root.memoizedUpdaters != null - ? Array.from(root.memoizedUpdaters).map(fiberToSerializedElement) + ? Array.from(root.memoizedUpdaters) + .filter(fiber => getFiberIDUnsafe(fiber) !== null) + .map(fiberToSerializedElement) : null; }