Repository: facebook/react
Pull request: [devtools] Restore all Transitions for Tree updates

Stacked on https://github.com/facebook/react/pull/33039

## Summary

Restores all the Transitions removed in https://github.com/facebook/react/pull/33039. We now kick of the fetch when we change the Owner instead of kicking it off in an Effect (you-might-not-need-an-effect).



## How did you test this change?

- Shell and double click on "Dehydrateable props"


Changed files:
filename: packages/react-devtools-shared/src/devtools/views/Components/Element.js
patch: @@ -23,6 +23,7 @@ import type {Element as ElementType} from 'react-devtools-shared/src/frontend/ty
 
 import styles from './Element.css';
 import Icon from '../Icon';
+import {useChangeOwnerAction} from './OwnersListContext';
 
 type Props = {
   data: ItemData,
@@ -66,9 +67,10 @@ export default function Element({data, index, style}: Props): React.Node {
     warningCount: number,
   }>(errorsAndWarningsSubscription);
 
+  const changeOwnerAction = useChangeOwnerAction();
   const handleDoubleClick = () => {
     if (id !== null) {
-      dispatch({type: 'SELECT_OWNER', payload: id});
+      changeOwnerAction(id);
     }
   };
 

filename: packages/react-devtools-shared/src/devtools/views/Components/OwnersListContext.js
patch: @@ -13,7 +13,7 @@ import * as React from 'react';
 import {createContext, useCallback, useContext, useEffect} from 'react';
 import {createResource} from '../../cache';
 import {BridgeContext, StoreContext} from '../context';
-import {TreeStateContext} from './TreeContext';
+import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
 import {backendToFrontendSerializedElementMapper} from 'react-devtools-shared/src/utils';
 
 import type {OwnersList} from 'react-devtools-shared/src/backend/types';
@@ -70,6 +70,43 @@ type Props = {
   children: React$Node,
 };
 
+function useChangeOwnerAction(): (nextOwnerID: number) => void {
+  const bridge = useContext(BridgeContext);
+  const store = useContext(StoreContext);
+  const treeAction = useContext(TreeDispatcherContext);
+
+  return useCallback(
+    function changeOwnerAction(nextOwnerID: number) {
+      treeAction({type: 'SELECT_OWNER', payload: nextOwnerID});
+
+      const element = store.getElementByID(nextOwnerID);
+      if (element !== null) {
+        if (!inProgressRequests.has(element)) {
+          let resolveFn:
+            | ResolveFn
+            | ((
+                result:
+                  | Promise<Array<SerializedElement>>
+                  | Array<SerializedElement>,
+              ) => void) = ((null: any): ResolveFn);
+          const promise = new Promise(resolve => {
+            resolveFn = resolve;
+          });
+
+          // $FlowFixMe[incompatible-call] found when upgrading Flow
+          inProgressRequests.set(element, {promise, resolveFn});
+        }
+
+        const rendererID = store.getRendererIDForElement(nextOwnerID);
+        if (rendererID !== null) {
+          bridge.send('getOwnersList', {id: nextOwnerID, rendererID});
+        }
+      }
+    },
+    [bridge, store],
+  );
+}
+
 function OwnersListContextController({children}: Props): React.Node {
   const bridge = useContext(BridgeContext);
   const store = useContext(StoreContext);
@@ -95,8 +132,6 @@ function OwnersListContextController({children}: Props): React.Node {
       if (element !== null) {
         const request = inProgressRequests.get(element);
         if (request != null) {
-          inProgressRequests.delete(element);
-
           request.resolveFn(
             ownersList.owners === null
               ? null
@@ -129,4 +164,4 @@ function OwnersListContextController({children}: Props): React.Node {
   );
 }
 
-export {OwnersListContext, OwnersListContextController};
+export {OwnersListContext, OwnersListContextController, useChangeOwnerAction};

filename: packages/react-devtools-shared/src/devtools/views/Components/OwnersStack.js
patch: @@ -20,7 +20,7 @@ import Button from '../Button';
 import ButtonIcon from '../ButtonIcon';
 import Toggle from '../Toggle';
 import ElementBadges from './ElementBadges';
-import {OwnersListContext} from './OwnersListContext';
+import {OwnersListContext, useChangeOwnerAction} from './OwnersListContext';
 import {TreeDispatcherContext, TreeStateContext} from './TreeContext';
 import {useIsOverflowing} from '../hooks';
 import {StoreContext} from '../context';
@@ -81,6 +81,7 @@ export default function OwnerStack(): React.Node {
   const read = useContext(OwnersListContext);
   const {ownerID} = useContext(TreeStateContext);
   const treeDispatch = useContext(TreeDispatcherContext);
+  const changeOwnerAction = useChangeOwnerAction();
 
   const [state, dispatch] = useReducer<State, State, Action>(dialogReducer, {
     ownerID: null,
@@ -116,7 +117,7 @@ export default function OwnerStack(): React.Node {
           type: 'UPDATE_SELECTED_INDEX',
           selectedIndex: index >= 0 ? index : 0,
         });
-        treeDispatch({type: 'SELECT_OWNER', payload: owner.id});
+        changeOwnerAction(owner.id);
       } else {
         dispatch({
           type: 'UPDATE_SELECTED_INDEX',

filename: packages/react-devtools-shared/src/devtools/views/Components/Tree.js
patch: @@ -38,6 +38,7 @@ import ButtonIcon from '../ButtonIcon';
 import Button from '../Button';
 import {logEvent} from 'react-devtools-shared/src/Logger';
 import {useExtensionComponentsPanelVisibility} from 'react-devtools-shared/src/frontend/hooks/useExtensionComponentsPanelVisibility';
+import {useChangeOwnerAction} from './OwnersListContext';
 
 // Never indent more than this number of pixels (even if we have the room).
 const DEFAULT_INDENTATION_SIZE = 12;
@@ -217,13 +218,14 @@ export default function Tree(): React.Node {
   const handleBlur = useCallback(() => setTreeFocused(false), []);
   const handleFocus = useCallback(() => setTreeFocused(true), []);
 
+  const changeOwnerAction = useChangeOwnerAction();
   const handleKeyPress = useCallback(
     (event: $FlowFixMe) => {
       switch (event.key) {
         case 'Enter':
         case ' ':
           if (inspectedElementID !== null) {
-            dispatch({type: 'SELECT_OWNER', payload: inspectedElementID});
+            changeOwnerAction(inspectedElementID);
           }
           break;
         default:

filename: packages/react-devtools-shared/src/devtools/views/Components/TreeContext.js
patch: @@ -148,6 +148,7 @@ const TreeStateContext: ReactContext<StateContext> =
   createContext<StateContext>(((null: any): StateContext));
 TreeStateContext.displayName = 'TreeStateContext';
 
+// TODO: `dispatch` is an Action and should be named accordingly.
 const TreeDispatcherContext: ReactContext<DispatcherContext> =
   createContext<DispatcherContext>(((null: any): DispatcherContext));
 TreeDispatcherContext.displayName = 'TreeDispatcherContext';
@@ -953,7 +954,7 @@ function TreeContextController({
 
   return (
     <TreeStateContext.Provider value={state}>
-      <TreeDispatcherContext.Provider value={dispatch}>
+      <TreeDispatcherContext.Provider value={transitionDispatch}>
         {children}
       </TreeDispatcherContext.Provider>
     </TreeStateContext.Provider>