Skip to content

Commit 39c6545

Browse files
authored
Fix indices of hooks in devtools when using useSyncExternalStore (#34547)
## Summary This PR updates getChangedHooksIndices to account for the fact that useSyncExternalStore internally mounts two hooks, while DevTools should treat it as a single user-facing hook. It introduces a helper isUseSyncExternalStoreHook to detect this case and adjust iteration so the extra internal hook is skipped when counting changes. Before: https://github.com/user-attachments/assets/0db72a4e-21f7-44c7-ba02-669a272631e5 After: https://github.com/user-attachments/assets/4da71392-0396-408d-86a7-6fbc82d8c4f5 ## How did you test this change? I used this component to reproduce this issue locally (I followed instructions in `packages/react-devtools/CONTRIBUTING.md`). ```ts function Test() { // 1 React.useSyncExternalStore( () => {}, () => {}, () => {}, ); // 2 const [state, setState] = useState('test'); return ( <> <div onClick={() => setState(Math.random())} style={{backgroundColor: 'red'}}> {state} </div> </> ); } ```
1 parent 613cf80 commit 39c6545

File tree

1 file changed

+23
-6
lines changed
  • packages/react-devtools-shared/src/backend/fiber

1 file changed

+23
-6
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,20 @@ export function attach(
19131913
return false;
19141914
}
19151915
1916+
function isUseSyncExternalStoreHook(hookObject: any): boolean {
1917+
const queue = hookObject.queue;
1918+
if (!queue) {
1919+
return false;
1920+
}
1921+
1922+
const boundHasOwnProperty = hasOwnProperty.bind(queue);
1923+
return (
1924+
boundHasOwnProperty('value') &&
1925+
boundHasOwnProperty('getSnapshot') &&
1926+
typeof queue.getSnapshot === 'function'
1927+
);
1928+
}
1929+
19161930
function isHookThatCanScheduleUpdate(hookObject: any) {
19171931
const queue = hookObject.queue;
19181932
if (!queue) {
@@ -1929,12 +1943,7 @@ export function attach(
19291943
return true;
19301944
}
19311945
1932-
// Detect useSyncExternalStore()
1933-
return (
1934-
boundHasOwnProperty('value') &&
1935-
boundHasOwnProperty('getSnapshot') &&
1936-
typeof queue.getSnapshot === 'function'
1937-
);
1946+
return isUseSyncExternalStoreHook(hookObject);
19381947
}
19391948
19401949
function didStatefulHookChange(prev: any, next: any): boolean {
@@ -1955,10 +1964,18 @@ export function attach(
19551964
19561965
const indices = [];
19571966
let index = 0;
1967+
19581968
while (next !== null) {
19591969
if (didStatefulHookChange(prev, next)) {
19601970
indices.push(index);
19611971
}
1972+
1973+
// useSyncExternalStore creates 2 internal hooks, but we only count it as 1 user-facing hook
1974+
if (isUseSyncExternalStoreHook(next)) {
1975+
next = next.next;
1976+
prev = prev.next;
1977+
}
1978+
19621979
next = next.next;
19631980
prev = prev.next;
19641981
index++;

0 commit comments

Comments
 (0)