diff --git a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap index 3611f67652aa4..9724472b31cb7 100644 --- a/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap +++ b/packages/react-devtools-shared/src/__tests__/__snapshots__/profilingCache-test.js.snap @@ -4543,245 +4543,6 @@ Object { } `; -exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 0 1`] = ` -Object { - "changeDescriptions": Map { - 4 => Object { - "context": null, - "didHooksChange": false, - "isFirstMount": true, - "props": null, - "state": null, - }, - }, - "duration": 0, - "effectDuration": 0, - "fiberActualDurations": Map { - 1 => 0, - 2 => 0, - 3 => 0, - 4 => 0, - }, - "fiberSelfDurations": Map { - 1 => 0, - 2 => 0, - 3 => 0, - 4 => 0, - }, - "passiveEffectDuration": 0, - "priorityLevel": "Immediate", - "timestamp": 0, - "updaters": Array [ - Object { - "displayName": "render()", - "hocDisplayNames": null, - "id": 1, - "key": null, - "type": 11, - }, - ], -} -`; - -exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 1 1`] = ` -Object { - "changeDescriptions": Map { - 4 => Object { - "context": false, - "didHooksChange": false, - "hooks": Array [], - "isFirstMount": false, - "props": Array [ - "count", - ], - "state": null, - }, - }, - "duration": 0, - "effectDuration": null, - "fiberActualDurations": Map { - 4 => 0, - 3 => 0, - 2 => 0, - 1 => 0, - }, - "fiberSelfDurations": Map { - 4 => 0, - 3 => 0, - 2 => 0, - 1 => 0, - }, - "passiveEffectDuration": null, - "priorityLevel": "Immediate", - "timestamp": 0, - "updaters": Array [ - Object { - "displayName": "render()", - "hocDisplayNames": null, - "id": 1, - "key": null, - "type": 11, - }, - ], -} -`; - -exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 2 1`] = ` -Object { - "changeDescriptions": Map { - 4 => Object { - "context": false, - "didHooksChange": true, - "hooks": Array [ - 1, - ], - "isFirstMount": false, - "props": Array [], - "state": null, - }, - }, - "duration": 0, - "effectDuration": null, - "fiberActualDurations": Map { - 4 => 0, - }, - "fiberSelfDurations": Map { - 4 => 0, - }, - "passiveEffectDuration": null, - "priorityLevel": "Immediate", - "timestamp": 0, - "updaters": Array [ - Object { - "displayName": "Component", - "hocDisplayNames": null, - "id": 4, - "key": null, - "type": 5, - }, - ], -} -`; - -exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 3 1`] = ` -Object { - "changeDescriptions": Map { - 4 => Object { - "context": false, - "didHooksChange": true, - "hooks": Array [ - 0, - ], - "isFirstMount": false, - "props": Array [], - "state": null, - }, - }, - "duration": 0, - "effectDuration": null, - "fiberActualDurations": Map { - 4 => 0, - }, - "fiberSelfDurations": Map { - 4 => 0, - }, - "passiveEffectDuration": null, - "priorityLevel": "Immediate", - "timestamp": 0, - "updaters": Array [ - Object { - "displayName": "Component", - "hocDisplayNames": null, - "id": 4, - "key": null, - "type": 5, - }, - ], -} -`; - -exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 4 1`] = ` -Object { - "changeDescriptions": Map { - 4 => Object { - "context": true, - "didHooksChange": false, - "hooks": Array [], - "isFirstMount": false, - "props": Array [], - "state": null, - }, - }, - "duration": 0, - "effectDuration": null, - "fiberActualDurations": Map { - 4 => 0, - 3 => 0, - 2 => 0, - 1 => 0, - }, - "fiberSelfDurations": Map { - 4 => 0, - 3 => 0, - 2 => 0, - 1 => 0, - }, - "passiveEffectDuration": null, - "priorityLevel": "Immediate", - "timestamp": 0, - "updaters": Array [ - Object { - "displayName": "render()", - "hocDisplayNames": null, - "id": 1, - "key": null, - "type": 11, - }, - ], -} -`; - -exports[`ProfilingCache should properly detect changed hooks: CommitDetails commitIndex: 5 1`] = ` -Object { - "changeDescriptions": Map { - 4 => Object { - "context": true, - "didHooksChange": false, - "hooks": Array [], - "isFirstMount": false, - "props": Array [], - "state": null, - }, - }, - "duration": 0, - "effectDuration": null, - "fiberActualDurations": Map { - 4 => 0, - 3 => 0, - 2 => 0, - 1 => 0, - }, - "fiberSelfDurations": Map { - 4 => 0, - 3 => 0, - 2 => 0, - 1 => 0, - }, - "passiveEffectDuration": null, - "priorityLevel": "Immediate", - "timestamp": 0, - "updaters": Array [ - Object { - "displayName": "render()", - "hocDisplayNames": null, - "id": 1, - "key": null, - "type": 11, - }, - ], -} -`; - exports[`ProfilingCache should properly detect changed hooks: imported data 1`] = ` Object { "dataForRoots": Array [ @@ -4790,7 +4551,7 @@ Object { Object { "changeDescriptions": Array [ Array [ - 4, + 3, Object { "context": null, "didHooksChange": false, @@ -4815,10 +4576,6 @@ Object { 3, 0, ], - Array [ - 4, - 0, - ], ], "fiberSelfDurations": Array [ Array [ @@ -4833,10 +4590,6 @@ Object { 3, 0, ], - Array [ - 4, - 0, - ], ], "passiveEffectDuration": 0, "priorityLevel": "Immediate", @@ -4854,7 +4607,7 @@ Object { Object { "changeDescriptions": Array [ Array [ - 4, + 3, Object { "context": false, "didHooksChange": false, @@ -4868,12 +4621,8 @@ Object { ], ], "duration": 0, - "effectDuration": null, + "effectDuration": 0, "fiberActualDurations": Array [ - Array [ - 4, - 0, - ], Array [ 3, 0, @@ -4888,10 +4637,6 @@ Object { ], ], "fiberSelfDurations": Array [ - Array [ - 4, - 0, - ], Array [ 3, 0, @@ -4905,7 +4650,7 @@ Object { 0, ], ], - "passiveEffectDuration": null, + "passiveEffectDuration": 0, "priorityLevel": "Immediate", "timestamp": 0, "updaters": Array [ @@ -4921,7 +4666,7 @@ Object { Object { "changeDescriptions": Array [ Array [ - 4, + 3, Object { "context": false, "didHooksChange": true, @@ -4935,27 +4680,27 @@ Object { ], ], "duration": 0, - "effectDuration": null, + "effectDuration": 0, "fiberActualDurations": Array [ Array [ - 4, + 3, 0, ], ], "fiberSelfDurations": Array [ Array [ - 4, + 3, 0, ], ], - "passiveEffectDuration": null, + "passiveEffectDuration": 0, "priorityLevel": "Immediate", "timestamp": 0, "updaters": Array [ Object { "displayName": "Component", "hocDisplayNames": null, - "id": 4, + "id": 3, "key": null, "type": 5, }, @@ -4964,7 +4709,7 @@ Object { Object { "changeDescriptions": Array [ Array [ - 4, + 3, Object { "context": false, "didHooksChange": true, @@ -4978,27 +4723,27 @@ Object { ], ], "duration": 0, - "effectDuration": null, + "effectDuration": 0, "fiberActualDurations": Array [ Array [ - 4, + 3, 0, ], ], "fiberSelfDurations": Array [ Array [ - 4, + 3, 0, ], ], - "passiveEffectDuration": null, + "passiveEffectDuration": 0, "priorityLevel": "Immediate", "timestamp": 0, "updaters": Array [ Object { "displayName": "Component", "hocDisplayNames": null, - "id": 4, + "id": 3, "key": null, "type": 5, }, @@ -5007,7 +4752,7 @@ Object { Object { "changeDescriptions": Array [ Array [ - 4, + 3, Object { "context": true, "didHooksChange": false, @@ -5019,12 +4764,8 @@ Object { ], ], "duration": 0, - "effectDuration": null, + "effectDuration": 0, "fiberActualDurations": Array [ - Array [ - 4, - 0, - ], Array [ 3, 0, @@ -5039,10 +4780,6 @@ Object { ], ], "fiberSelfDurations": Array [ - Array [ - 4, - 0, - ], Array [ 3, 0, @@ -5056,7 +4793,7 @@ Object { 0, ], ], - "passiveEffectDuration": null, + "passiveEffectDuration": 0, "priorityLevel": "Immediate", "timestamp": 0, "updaters": Array [ @@ -5072,11 +4809,13 @@ Object { Object { "changeDescriptions": Array [ Array [ - 4, + 3, Object { - "context": true, - "didHooksChange": false, - "hooks": Array [], + "context": false, + "didHooksChange": true, + "hooks": Array [ + 2, + ], "isFirstMount": false, "props": Array [], "state": null, @@ -5084,53 +4823,29 @@ Object { ], ], "duration": 0, - "effectDuration": null, + "effectDuration": 0, "fiberActualDurations": Array [ - Array [ - 4, - 0, - ], Array [ 3, 0, ], - Array [ - 2, - 0, - ], - Array [ - 1, - 0, - ], ], "fiberSelfDurations": Array [ - Array [ - 4, - 0, - ], Array [ 3, 0, ], - Array [ - 2, - 0, - ], - Array [ - 1, - 0, - ], ], - "passiveEffectDuration": null, + "passiveEffectDuration": 0, "priorityLevel": "Immediate", "timestamp": 0, "updaters": Array [ Object { - "displayName": "render()", + "displayName": "Component", "hocDisplayNames": null, - "id": 1, + "id": 3, "key": null, - "type": 11, + "type": 5, }, ], }, @@ -5188,23 +4903,13 @@ Object { 0, 1, 3, - 2, - 2, - 0, - 1, - 0, - 4, - 3, - 0, - 1, - 4, 5, - 3, + 2, 0, 2, 0, 4, - 4, + 3, 0, ], Array [ @@ -5312,6 +5017,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 2, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 2, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, ], ], Array [ @@ -5341,6 +5062,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 3, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 3, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, ], ], Array [ @@ -5370,6 +5107,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 4, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 4, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, ], ], Array [ @@ -5397,7 +5150,23 @@ Object { "duration": 0, "lanes": "0b0000000000000000000000000000001", "timestamp": 10, - "type": "commit", + "type": "commit", + }, + Object { + "batchUID": 5, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 5, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", }, ], ], @@ -5428,6 +5197,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 6, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 6, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, ], ], ], @@ -5446,6 +5231,48 @@ Object { "type": "layout-effect-mount", "warning": null, }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, Object { "componentName": "Component", "duration": 0, @@ -5460,6 +5287,34 @@ Object { "type": "render", "warning": null, }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, Object { "componentName": "Component", "duration": 0, @@ -5467,6 +5322,20 @@ Object { "type": "render", "warning": null, }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, Object { "componentName": "Component", "duration": 0, @@ -5474,6 +5343,20 @@ Object { "type": "render", "warning": null, }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, Object { "componentName": "Component", "duration": 0, @@ -5481,6 +5364,20 @@ Object { "type": "render", "warning": null, }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, Object { "componentName": "Component", "duration": 0, @@ -5488,6 +5385,27 @@ Object { "type": "render", "warning": null, }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "layout-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, + Object { + "componentName": "Component", + "duration": 0, + "timestamp": 10, + "type": "passive-effect-mount", + "warning": null, + }, ], "duration": 20, "flamechart": Array [], @@ -5686,6 +5604,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 2, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 2, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, Object { "batchUID": 3, "depth": 0, @@ -5710,6 +5644,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 3, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 3, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, Object { "batchUID": 4, "depth": 0, @@ -5734,6 +5684,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 4, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 4, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, Object { "batchUID": 5, "depth": 0, @@ -5758,6 +5724,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 5, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 5, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, Object { "batchUID": 6, "depth": 0, @@ -5782,6 +5764,22 @@ Object { "timestamp": 10, "type": "commit", }, + Object { + "batchUID": 6, + "depth": 1, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "layout-effects", + }, + Object { + "batchUID": 6, + "depth": 0, + "duration": 0, + "lanes": "0b0000000000000000000000000000001", + "timestamp": 10, + "type": "passive-effects", + }, ], ], Array [ @@ -5942,12 +5940,6 @@ Object { "type": "schedule-render", "warning": null, }, - Object { - "lanes": "0b0000000000000000000000000000001", - "timestamp": 10, - "type": "schedule-render", - "warning": null, - }, ], "snapshotHeight": 0, "snapshots": Array [], @@ -6039,10 +6031,8 @@ Object { "changeDescriptions": Map { 5 => Object { "context": null, - "didHooksChange": true, - "hooks": Array [ - 0, - ], + "didHooksChange": false, + "hooks": Array [], "isFirstMount": false, "props": Array [ "count", @@ -6059,10 +6049,8 @@ Object { }, 7 => Object { "context": null, - "didHooksChange": true, - "hooks": Array [ - 0, - ], + "didHooksChange": false, + "hooks": Array [], "isFirstMount": false, "props": Array [ "count", @@ -6506,10 +6494,8 @@ Object { 5, Object { "context": null, - "didHooksChange": true, - "hooks": Array [ - 0, - ], + "didHooksChange": false, + "hooks": Array [], "isFirstMount": false, "props": Array [ "count", @@ -6532,10 +6518,8 @@ Object { 7, Object { "context": null, - "didHooksChange": true, - "hooks": Array [ - 0, - ], + "didHooksChange": false, + "hooks": Array [], "isFirstMount": false, "props": Array [ "count", diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js index c1d17fd0f6a3f..b5a5d35da23bd 100644 --- a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js +++ b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js @@ -313,7 +313,6 @@ describe('ProfilingCache', () => { it('should properly detect changed hooks', () => { const Context = React.createContext(0); - const Context2 = React.createContext(0); function reducer(state, action) { switch (action.type) { @@ -324,6 +323,19 @@ describe('ProfilingCache', () => { } } + let snapshot = 0; + function getServerSnapshot() { + return snapshot; + } + function getClientSnapshot() { + return snapshot; + } + + let syncExternalStoreCallback; + function subscribe(callback) { + syncExternalStoreCallback = callback; + } + let dispatch = null; let setState = null; @@ -331,20 +343,31 @@ describe('ProfilingCache', () => { // These hooks may change and initiate re-renders. setState = React.useState('abc')[1]; dispatch = React.useReducer(reducer, {value: true})[1]; + React.useSyncExternalStore( + subscribe, + getClientSnapshot, + getServerSnapshot, + ); // This hook's return value may change between renders, // but the hook itself isn't stateful. React.useContext(Context); - React.useContext(Context2); - // These hooks and their dependencies may not change between renders. - // We're using them to ensure that they don't trigger false positives. + // These hooks never change in a way that schedules an update. React.useCallback(() => () => {}, [string]); React.useMemo(() => string, [string]); + React.useCallback(() => () => {}, [count]); + React.useMemo(() => count, [count]); + React.useCallback(() => () => {}); + React.useMemo(() => string); - // These hooks never "change". + // These hooks never change in a way that schedules an update. React.useEffect(() => {}, [string]); React.useLayoutEffect(() => {}, [string]); + React.useEffect(() => {}, [count]); + React.useLayoutEffect(() => {}, [count]); + React.useEffect(() => {}); + React.useLayoutEffect(() => {}); return null; }; @@ -355,9 +378,7 @@ describe('ProfilingCache', () => { utils.act(() => legacyRender( - - - + , container, ), @@ -367,87 +388,173 @@ describe('ProfilingCache', () => { utils.act(() => legacyRender( - - - + , container, ), ); - // Third render has a changed reducer hook + // Third render has a changed reducer hook. utils.act(() => dispatch({type: 'invert'})); - // Fourth render has a changed state hook + // Fourth render has a changed state hook. utils.act(() => setState('def')); - // Fifth render has a changed context value for context 1, but no changed hook. + // Fifth render has a changed context value, but no changed hook. utils.act(() => legacyRender( - - - + , container, ), ); - // Sixth render has another changed context value for context 2, but no changed hook. - utils.act(() => - legacyRender( - - - - - , - container, - ), - ); + // 6th renderer is triggered by a sync external store change. + utils.act(() => { + snapshot++; + syncExternalStoreCallback(); + }); + utils.act(() => store.profilerStore.stopProfiling()); - const allCommitData = []; + const rootID = store.roots[0]; - function Validator({commitIndex, previousCommitDetails, rootID}) { - const commitData = store.profilerStore.getCommitData(rootID, commitIndex); - if (previousCommitDetails != null) { - expect(commitData).toEqual(previousCommitDetails); - } else { - allCommitData.push(commitData); - expect(commitData).toMatchSnapshot( - `CommitDetails commitIndex: ${commitIndex}`, + const allChangeDescriptions = []; + + function getChangeDescriptions(commitIndex, label) { + let changeDescriptions; + + function Validator() { + const commitData = store.profilerStore.getCommitData( + rootID, + commitIndex, ); - } - return null; - } - const rootID = store.roots[0]; + changeDescriptions = commitData.changeDescriptions; + + allChangeDescriptions.push(changeDescriptions); + + return null; + } - for (let commitIndex = 0; commitIndex < 6; commitIndex++) { utils.act(() => { - TestRenderer.create( - , - ); + TestRenderer.create(); }); + + return changeDescriptions; } - expect(allCommitData).toHaveLength(6); + // 1st render: No change + expect(getChangeDescriptions(0)).toMatchInlineSnapshot(` + Map { + 3 => Object { + "context": null, + "didHooksChange": false, + "isFirstMount": true, + "props": null, + "state": null, + }, + } + `); + + // 2nd render: Changed props + expect(getChangeDescriptions(1)).toMatchInlineSnapshot(` + Map { + 3 => Object { + "context": false, + "didHooksChange": false, + "hooks": Array [], + "isFirstMount": false, + "props": Array [ + "count", + ], + "state": null, + }, + } + `); + + // 3rd render: Changed useReducer + expect(getChangeDescriptions(2)).toMatchInlineSnapshot(` + Map { + 3 => Object { + "context": false, + "didHooksChange": true, + "hooks": Array [ + 1, + ], + "isFirstMount": false, + "props": Array [], + "state": null, + }, + } + `); + + // 4th render: Changed useState + expect(getChangeDescriptions(3)).toMatchInlineSnapshot(` + Map { + 3 => Object { + "context": false, + "didHooksChange": true, + "hooks": Array [ + 0, + ], + "isFirstMount": false, + "props": Array [], + "state": null, + }, + } + `); + + // 5th render: Changed context + expect(getChangeDescriptions(4)).toMatchInlineSnapshot(` + Map { + 3 => Object { + "context": true, + "didHooksChange": false, + "hooks": Array [], + "isFirstMount": false, + "props": Array [], + "state": null, + }, + } + `); + + // 6th render: Sync external store + expect(getChangeDescriptions(5)).toMatchInlineSnapshot(` + Map { + 3 => Object { + "context": false, + "didHooksChange": true, + "hooks": Array [ + 2, + ], + "isFirstMount": false, + "props": Array [], + "state": null, + }, + } + `); + + expect(allChangeDescriptions).toHaveLength(6); // Export and re-import profile data and make sure it is retained. utils.exportImportHelper(bridge, store); + function ExportImportValidator({commitIndex}) { + const commitData = store.profilerStore.getCommitData(rootID, commitIndex); + + expect(commitData.changeDescriptions).toEqual( + allChangeDescriptions[commitIndex], + ); + + return null; + } + for (let commitIndex = 0; commitIndex < 6; commitIndex++) { utils.act(() => { TestRenderer.create( - , + , ); }); } diff --git a/packages/react-devtools-shared/src/backend/renderer.js b/packages/react-devtools-shared/src/backend/renderer.js index 3c18ce4083099..50ba03d964caf 100644 --- a/packages/react-devtools-shared/src/backend/renderer.js +++ b/packages/react-devtools-shared/src/backend/renderer.js @@ -93,7 +93,6 @@ import { enableStyleXFeatures, } from 'react-devtools-feature-flags'; import is from 'shared/objectIs'; -import isArray from 'shared/isArray'; import hasOwnProperty from 'shared/hasOwnProperty'; import {getStyleXData} from './StyleX/utils'; import {createProfilingHooks} from './profilingHooks'; @@ -1444,50 +1443,41 @@ export function attach( return null; } - function areHookInputsEqual( - nextDeps: Array, - prevDeps: Array | null, - ) { - if (prevDeps === null) { + function isHookThatCanScheduleUpdate(hookObject: any) { + const queue = hookObject.queue; + if (!queue) { return false; } - for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { - if (is(nextDeps[i], prevDeps[i])) { - continue; - } - return false; - } - return true; - } + const boundHasOwnProperty = hasOwnProperty.bind(queue); - function isEffect(memoizedState) { - if (memoizedState === null || typeof memoizedState !== 'object') { - return false; - } - const {deps} = memoizedState; - const boundHasOwnProperty = hasOwnProperty.bind(memoizedState); - return ( - boundHasOwnProperty('create') && - boundHasOwnProperty('destroy') && - boundHasOwnProperty('deps') && - boundHasOwnProperty('next') && - boundHasOwnProperty('tag') && - (deps === null || isArray(deps)) - ); + // Detect the shape of useState() or useReducer() + // using the attributes that are unique to these hooks + // but also stable (e.g. not tied to current Lanes implementation) + const isStateOrReducer = + boundHasOwnProperty('pending') && + boundHasOwnProperty('dispatch') && + typeof queue.dispatch === 'function'; + + // Detect useSyncExternalStore() + const isSyncExternalStore = + boundHasOwnProperty('value') && + boundHasOwnProperty('getSnapshot') && + typeof queue.getSnapshot === 'function'; + + // These are the only types of hooks that can schedule an update. + return isStateOrReducer || isSyncExternalStore; } - function didHookChange(prev: any, next: any): boolean { + function didStatefulHookChange(prev: any, next: any): boolean { const prevMemoizedState = prev.memoizedState; const nextMemoizedState = next.memoizedState; - if (isEffect(prevMemoizedState) && isEffect(nextMemoizedState)) { - return ( - prevMemoizedState !== nextMemoizedState && - !areHookInputsEqual(nextMemoizedState.deps, prevMemoizedState.deps) - ); + if (isHookThatCanScheduleUpdate(prev)) { + return prevMemoizedState !== nextMemoizedState; } - return nextMemoizedState !== prevMemoizedState; + + return false; } function didHooksChange(prev: any, next: any): boolean { @@ -1503,7 +1493,7 @@ export function attach( next.hasOwnProperty('queue') ) { while (next !== null) { - if (didHookChange(prev, next)) { + if (didStatefulHookChange(prev, next)) { return true; } else { next = next.next; @@ -1530,7 +1520,7 @@ export function attach( next.hasOwnProperty('queue') ) { while (next !== null) { - if (didHookChange(prev, next)) { + if (didStatefulHookChange(prev, next)) { indices.push(index); } next = next.next;