From 00e2193ade90d26ae1fe35c5732676e9b42c286e Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 4 Aug 2025 15:46:24 -0400 Subject: [PATCH 1/4] Don't bailout for filtered instance Instead, we just continue to collect the unfiltered children. --- packages/react-devtools-shared/src/backend/fiber/renderer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index b6bc24dd01b4..5ec10a4657b5 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -3917,7 +3917,10 @@ export function attach( } } else { // Children are unchanged. - if (fiberInstance !== null) { + if ( + fiberInstance !== null && + fiberInstance.kind !== FILTERED_FIBER_INSTANCE + ) { // All the remaining children will be children of this same fiber so we can just reuse them. // I.e. we just restore them by undoing what we did above. fiberInstance.firstChild = remainingReconcilingChildren; From d23ee492a05fcf554e09b81377a8c13a4c8efd70 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 5 Aug 2025 10:07:41 +0200 Subject: [PATCH 2/4] Test case --- .../src/__tests__/store-test.js | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js index e982ac5ecc5c..1a5a0e6a2607 100644 --- a/packages/react-devtools-shared/src/__tests__/store-test.js +++ b/packages/react-devtools-shared/src/__tests__/store-test.js @@ -17,8 +17,10 @@ describe('Store', () => { let act; let actAsync; let bridge; + let createDisplayNameFilter; let getRendererID; let legacyRender; + let previousComponentFilters; let store; let withErrorsOrWarningsIgnored; @@ -29,6 +31,8 @@ describe('Store', () => { bridge = global.bridge; store = global.store; + previousComponentFilters = store.componentFilters; + React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); @@ -38,9 +42,14 @@ describe('Store', () => { actAsync = utils.actAsync; getRendererID = utils.getRendererID; legacyRender = utils.legacyRender; + createDisplayNameFilter = utils.createDisplayNameFilter; withErrorsOrWarningsIgnored = utils.withErrorsOrWarningsIgnored; }); + afterEach(() => { + store.componentFilters = previousComponentFilters; + }); + const {render, unmount, createContainer} = getVersionedRenderImplementation(); // @reactVersion >= 18.0 @@ -129,6 +138,72 @@ describe('Store', () => { `); }); + it('should handle reorder of filtered elements', async () => { + function IgnoreMePassthrough({children}) { + return children; + } + function PassThrough({children}) { + return children; + } + + await actAsync( + async () => + (store.componentFilters = [createDisplayNameFilter('^IgnoreMe', true)]), + ); + + await act(() => { + render( + + + +

e1

+
+
+ + +
e2
+
+
+
, + ); + }); + + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + ▾ +

+ ▾ +

+ `); + + await act(() => { + render( + + + +
e2
+
+
+ + +

e1

+
+
+
, + ); + }); + + expect(store).toMatchInlineSnapshot(` + [root] + ▾ + ▾ +
+ ▾ +

+ `); + }); + describe('StrictMode compliance', () => { it('should mark strict root elements as strict', async () => { const App = () => ; From c472a34179947e57af58a249d0ce33a945384557 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 5 Aug 2025 11:12:23 -0400 Subject: [PATCH 3/4] Revert "Don't bailout for filtered instance" This reverts commit 00e2193ade90d26ae1fe35c5732676e9b42c286e. --- packages/react-devtools-shared/src/backend/fiber/renderer.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 5ec10a4657b5..b6bc24dd01b4 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -3917,10 +3917,7 @@ export function attach( } } else { // Children are unchanged. - if ( - fiberInstance !== null && - fiberInstance.kind !== FILTERED_FIBER_INSTANCE - ) { + if (fiberInstance !== null) { // All the remaining children will be children of this same fiber so we can just reuse them. // I.e. we just restore them by undoing what we did above. fiberInstance.firstChild = remainingReconcilingChildren; From 4f1675a8acb0463f7e36c30c74be6c023e7be6d7 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 5 Aug 2025 11:44:35 -0400 Subject: [PATCH 4/4] Track whether a filtered Fiber with no instance (not FilteredFiberInstance) reordered --- packages/react-devtools-shared/src/backend/fiber/renderer.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index b6bc24dd01b4..2a41f6e08f09 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -3612,6 +3612,11 @@ export function attach( shouldResetChildren = true; } } else if (prevChild !== null && shouldFilterFiber(nextChild)) { + // The filtered instance could've reordered. + if (prevChild !== prevChildAtSameIndex) { + shouldResetChildren = true; + } + // If this Fiber should be filtered, we need to still update its children. // This relies on an alternate since we don't have an Instance with the previous // child on it. Ideally, the reconciliation wouldn't need previous Fibers that