diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js index 00605d857a153..67db757461a6d 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 setChildUnmounted; + function Child() { + const [, setState] = React.useState(false); + + React.useLayoutEffect(() => { + return () => setState(true); + }); + + return null; + } + + function App() { + const [childUnmounted, _setChildUnmounted] = React.useState(false); + setChildUnmounted = _setChildUnmounted; + return <>{!childUnmounted && }; + } + + const root = ReactDOM.createRoot(document.createElement('div')); + utils.act(() => root.render()); + utils.act(() => store.profilerStore.startProfiling()); + utils.act(() => setChildUnmounted(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', () => { + let setChildUnmounted; + function Child() { + return ; + } + + function GrandChild() { + const [, setState] = React.useState(false); + + React.useLayoutEffect(() => { + return () => setState(true); + }); + + return null; + } + + function App() { + const [childUnmounted, _setChildUnmounted] = React.useState(false); + setChildUnmounted = _setChildUnmounted; + return <>{!childUnmounted && }; + } + + const root = ReactDOM.createRoot(document.createElement('div')); + utils.act(() => root.render()); + utils.act(() => store.profilerStore.startProfiling()); + utils.act(() => setChildUnmounted(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 setChildUnmounted; + function Child() { + const [, setState] = React.useState(false); + React.useEffect(() => { + return () => setState(true); + }); + + return null; + } + + function App() { + const [childUnmounted, _setChildUnmounted] = React.useState(false); + setChildUnmounted = _setChildUnmounted; + return <>{!childUnmounted && }; + } + + const root = ReactDOM.createRoot(document.createElement('div')); + utils.act(() => root.render()); + utils.act(() => store.profilerStore.startProfiling()); + utils.act(() => setChildUnmounted(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 daa4d4e1030c3..0f7b15cd019b9 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -2586,7 +2586,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; }