[devtools] Restore all Transitions for Tree updates (#33042)
Sebastian "Sebbie" Silbermann committed
Apr 30, 2025 at 19:51 UTC
fa8e3a251e7840fa220a1649dec616e72658473e
5 files changed
+50
-9
packages/react-devtools-shared/src/devtools/views/Components/Element.js
+3
-1
@@ -23,6 +23,7 @@ import type {Element as ElementType} from 'react-devtools-shared/src/frontend/ty
23
24
import styles from './Element.css';
25
import Icon from '../Icon';
26
+import {useChangeOwnerAction} from './OwnersListContext';
27
28
type Props = {
29
data: ItemData,
@@ -66,9 +67,10 @@ export default function Element({data, index, style}: Props): React.Node {
67
warningCount: number,
68
}>(errorsAndWarningsSubscription);
69
70
+ const changeOwnerAction = useChangeOwnerAction();
71
const handleDoubleClick = () => {
72
if (id !== null) {
71
- dispatch({type: 'SELECT_OWNER', payload: id});
73
+ changeOwnerAction(id);
74
}
75
};
76
packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js
+39
-4
@@ -13,7 +13,7 @@ import * as React from 'react';
13
import {createContext, useCallback, useContext, useEffect} from 'react';
14
import {createResource} from '../../cache';
15
import {BridgeContext, StoreContext} from '../context';
16
-import {TreeStateContext} from './TreeContext';
16
+import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
17
import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils';
18
19
import type {OwnersList} from 'react-devtools-shared/src/backend/types';
@@ -70,6 +70,43 @@ type Props = {
70
children: React$Node,
71
};
72
73
+function useChangeOwnerAction(): (nextOwnerID: number) => void {
74
+ const bridge = useContext(BridgeContext);
75
+ const store = useContext(StoreContext);
76
+ const treeAction = useContext(TreeDispatcherContext);
77
+
78
+ return useCallback(
79
+ function changeOwnerAction(nextOwnerID: number) {
80
+ treeAction({type: 'SELECT_OWNER', payload: nextOwnerID});
81
+
82
+ const element = store.getElementByID(nextOwnerID);
83
+ if (element !== null) {
84
+ if (!inProgressRequests.has(element)) {
85
+ let resolveFn:
86
+ | ResolveFn
87
+ | ((
88
+ result:
89
+ | Promise<Array<SerializedElement>>
90
+ | Array<SerializedElement>,
91
+ ) => void) = ((null: any): ResolveFn);
92
+ const promise = new Promise(resolve => {
93
+ resolveFn = resolve;
94
+ });
95
+
96
+ // $FlowFixMe[incompatible-call] found when upgrading Flow
97
+ inProgressRequests.set(element, {promise, resolveFn});
98
+ }
99
+
100
+ const rendererID = store.getRendererIDForElement(nextOwnerID);
101
+ if (rendererID !== null) {
102
+ bridge.send('getOwnersList', {id: nextOwnerID, rendererID});
103
+ }
104
+ }
105
+ },
106
+ [bridge, store],
107
+ );
108
+}
109
+
110
function OwnersListContextController({children}: Props): React.Node {
111
const bridge = useContext(BridgeContext);
112
const store = useContext(StoreContext);
@@ -95,8 +132,6 @@ function OwnersListContextController({children}: Props): React.Node {
132
if (element !== null) {
133
const request = inProgressRequests.get(element);
134
if (request != null) {
98
- inProgressRequests.delete(element);
99
-
135
request.resolveFn(
136
ownersList.owners === null
137
? null
@@ -129,4 +164,4 @@ function OwnersListContextController({children}: Props): React.Node {
164
);
165
}
166
132
-export {OwnersListContext, OwnersListContextController};
167
+export {OwnersListContext, OwnersListContextController, useChangeOwnerAction};
packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js
+3
-2
@@ -20,7 +20,7 @@ import Button from '../Button';
20
import ButtonIcon from '../ButtonIcon';
21
import Toggle from '../Toggle';
22
import ElementBadges from './ElementBadges';
23
-import {OwnersListContext} from './OwnersListContext';
23
+import {OwnersListContext, useChangeOwnerAction} from './OwnersListContext';
24
import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
25
import {useIsOverflowing} from '../hooks';
26
import {StoreContext} from '../context';
@@ -81,6 +81,7 @@ export default function OwnerStack(): React.Node {
81
const read = useContext(OwnersListContext);
82
const {ownerID} = useContext(TreeStateContext);
83
const treeDispatch = useContext(TreeDispatcherContext);
84
+ const changeOwnerAction = useChangeOwnerAction();
85
86
const [state, dispatch] = useReducer<State, State, Action>(dialogReducer, {
87
ownerID: null,
@@ -116,7 +117,7 @@ export default function OwnerStack(): React.Node {
117
type: 'UPDATE_SELECTED_INDEX',
118
selectedIndex: index >= 0 ? index : 0,
119
});
119
- treeDispatch({type: 'SELECT_OWNER', payload: owner.id});
120
+ changeOwnerAction(owner.id);
121
} else {
122
dispatch({
123
type: 'UPDATE_SELECTED_INDEX',
packages/react-devtools-shared/src/devtools/views/Components/Tree.js
+3
-1
@@ -38,6 +38,7 @@ import ButtonIcon from '../ButtonIcon';
38
import Button from '../Button';
39
import {logEvent} from 'react-devtools-shared/src/Logger';
40
import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/frontend/hooks/useExtensionComponentsPanelVisibility';
41
+import {useChangeOwnerAction} from './OwnersListContext';
42
43
// Never indent more than this number of pixels (even if we have the room).
44
const DEFAULT_INDENTATION_SIZE = 12;
@@ -217,13 +218,14 @@ export default function Tree(): React.Node {
218
const handleBlur = useCallback(() => setTreeFocused(false), []);
219
const handleFocus = useCallback(() => setTreeFocused(true), []);
220
221
+ const changeOwnerAction = useChangeOwnerAction();
222
const handleKeyPress = useCallback(
223
(event: $FlowFixMe) => {
224
switch (event.key) {
225
case 'Enter':
226
case ' ':
227
if (inspectedElementID !== null) {
226
- dispatch({type: 'SELECT_OWNER', payload: inspectedElementID});
228
+ changeOwnerAction(inspectedElementID);
229
}
230
break;
231
default:
packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js
+2
-1
@@ -148,6 +148,7 @@ const TreeStateContext: ReactContext<StateContext> =
148
createContext<StateContext>(((null: any): StateContext));
149
TreeStateContext.displayName = 'TreeStateContext';
150
151
+// TODO: `dispatch` is an Action and should be named accordingly.
152
const TreeDispatcherContext: ReactContext<DispatcherContext> =
153
createContext<DispatcherContext>(((null: any): DispatcherContext));
154
TreeDispatcherContext.displayName = 'TreeDispatcherContext';
@@ -953,7 +954,7 @@ function TreeContextController({
954
955
return (
956
<TreeStateContext.Provider value={state}>
956
- <TreeDispatcherContext.Provider value={dispatch}>
957
+ <TreeDispatcherContext.Provider value={transitionDispatch}>
958
{children}
959
</TreeDispatcherContext.Provider>
960
</TreeStateContext.Provider>