From 16e77a3858354fbeb492d980b89af1fbcfd496b8 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Sat, 20 Sep 2025 22:23:56 +0200 Subject: [PATCH 1/2] Fix indices of hooks ind evtools when using useSyncExternalStore --- .../src/backend/fiber/renderer.js | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 33786a41877b8..072da6e1a8170 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -1943,6 +1943,20 @@ export function attach( return false; } + function isUseSyncExternalStoreHook(hookObject: any): boolean { + const queue = hookObject.queue; + if (!queue) { + return false; + } + + const boundHasOwnProperty = hasOwnProperty.bind(queue); + return ( + boundHasOwnProperty('value') && + boundHasOwnProperty('getSnapshot') && + typeof queue.getSnapshot === 'function' + ); + } + function getChangedHooksIndices(prev: any, next: any): null | Array { if (prev == null || next == null) { return null; @@ -1950,13 +1964,30 @@ export function attach( const indices = []; let index = 0; - while (next !== null) { - if (didStatefulHookChange(prev, next)) { - indices.push(index); + + if ( + next.hasOwnProperty('baseState') && + next.hasOwnProperty('memoizedState') && + next.hasOwnProperty('next') && + next.hasOwnProperty('queue') + ) { + while (next !== null) { + if (didStatefulHookChange(prev, next)) { + indices.push(index); + } + + // useSyncExternalStore creates 2 internal hooks, but we only count it as 1 user-facing hook + if (isUseSyncExternalStoreHook(next)) { + if (next.next !== null) { + next = next.next; + prev = prev.next; + } + } + + index++; + next = next.next; + prev = prev.next; } - next = next.next; - prev = prev.next; - index++; } return indices; From 0dd8c608286e17fe0c17c7c485f09e9f7011f8ba Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Sat, 20 Sep 2025 22:57:07 +0200 Subject: [PATCH 2/2] Clean the code a bit --- .../src/backend/fiber/renderer.js | 68 ++++++++----------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 072da6e1a8170..10ab1dbce19f2 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -1908,6 +1908,20 @@ export function attach( return false; } + function isUseSyncExternalStoreHook(hookObject: any): boolean { + const queue = hookObject.queue; + if (!queue) { + return false; + } + + const boundHasOwnProperty = hasOwnProperty.bind(queue); + return ( + boundHasOwnProperty('value') && + boundHasOwnProperty('getSnapshot') && + typeof queue.getSnapshot === 'function' + ); + } + function isHookThatCanScheduleUpdate(hookObject: any) { const queue = hookObject.queue; if (!queue) { @@ -1924,12 +1938,7 @@ export function attach( return true; } - // Detect useSyncExternalStore() - return ( - boundHasOwnProperty('value') && - boundHasOwnProperty('getSnapshot') && - typeof queue.getSnapshot === 'function' - ); + return isUseSyncExternalStoreHook(hookObject); } function didStatefulHookChange(prev: any, next: any): boolean { @@ -1943,20 +1952,6 @@ export function attach( return false; } - function isUseSyncExternalStoreHook(hookObject: any): boolean { - const queue = hookObject.queue; - if (!queue) { - return false; - } - - const boundHasOwnProperty = hasOwnProperty.bind(queue); - return ( - boundHasOwnProperty('value') && - boundHasOwnProperty('getSnapshot') && - typeof queue.getSnapshot === 'function' - ); - } - function getChangedHooksIndices(prev: any, next: any): null | Array { if (prev == null || next == null) { return null; @@ -1965,29 +1960,22 @@ export function attach( const indices = []; let index = 0; - if ( - next.hasOwnProperty('baseState') && - next.hasOwnProperty('memoizedState') && - next.hasOwnProperty('next') && - next.hasOwnProperty('queue') - ) { - while (next !== null) { - if (didStatefulHookChange(prev, next)) { - indices.push(index); - } + while (next !== null) { + if (didStatefulHookChange(prev, next)) { + indices.push(index); + } - // useSyncExternalStore creates 2 internal hooks, but we only count it as 1 user-facing hook - if (isUseSyncExternalStoreHook(next)) { - if (next.next !== null) { - next = next.next; - prev = prev.next; - } + // useSyncExternalStore creates 2 internal hooks, but we only count it as 1 user-facing hook + if (isUseSyncExternalStoreHook(next)) { + if (next.next !== null) { + next = next.next; + prev = prev.next; } - - index++; - next = next.next; - prev = prev.next; } + + index++; + next = next.next; + prev = prev.next; } return indices;