Repository: facebook/react
Pull request: DevTools: Add support for useOptimistic Hook



## Summary

Add support for `useOptimistic` Hook fixing "Unsupported hook in the react-debug-tools package: Missing method in Dispatcher: useOptimistic" when inspecting components using `useOptimistic`

## How did you test this change?

- Added test following the same pattern as for `useDeferredValue`


Changed files:
filename: packages/react-debug-tools/src/ReactDebugHooks.js
patch: @@ -80,6 +80,11 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
         // This type check is for Flow only.
         Dispatcher.useMemoCache(0);
       }
+
+      if (typeof Dispatcher.useOptimistic === 'function') {
+        // This type check is for Flow only.
+        Dispatcher.useOptimistic(null, (s: mixed, a: mixed) => s);
+      }
     } finally {
       readHookLog = hookLog;
       hookLog = [];
@@ -348,6 +353,25 @@ function useMemoCache(size: number): Array<any> {
   return data;
 }
 
+function useOptimistic<S, A>(
+  passthrough: S,
+  reducer: ?(S, A) => S,
+): [S, (A) => void] {
+  const hook = nextHook();
+  let state;
+  if (hook !== null) {
+    state = hook.memoizedState;
+  } else {
+    state = passthrough;
+  }
+  hookLog.push({
+    primitive: 'Optimistic',
+    stackError: new Error(),
+    value: state,
+  });
+  return [state, (action: A) => {}];
+}
+
 const Dispatcher: DispatcherType = {
   use,
   readContext,
@@ -361,6 +385,7 @@ const Dispatcher: DispatcherType = {
   useInsertionEffect,
   useMemo,
   useMemoCache,
+  useOptimistic,
   useReducer,
   useRef,
   useState,

filename: packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
patch: @@ -1106,4 +1106,42 @@ describe('ReactHooksInspectionIntegration', () => {
       },
     ]);
   });
+
+  // @gate enableAsyncActions
+  it('should support useOptimistic hook', () => {
+    const useOptimistic = React.useOptimistic;
+    function Foo() {
+      const [value] = useOptimistic('abc', currentState => currentState);
+      React.useMemo(() => 'memo', []);
+      React.useMemo(() => 'not used', []);
+      return value;
+    }
+
+    const renderer = ReactTestRenderer.create(<Foo />);
+    const childFiber = renderer.root.findByType(Foo)._currentFiber();
+    const tree = ReactDebugTools.inspectHooksOfFiber(childFiber);
+    expect(tree).toEqual([
+      {
+        id: 0,
+        isStateEditable: false,
+        name: 'Optimistic',
+        value: 'abc',
+        subHooks: [],
+      },
+      {
+        id: 1,
+        isStateEditable: false,
+        name: 'Memo',
+        value: 'memo',
+        subHooks: [],
+      },
+      {
+        id: 2,
+        isStateEditable: false,
+        name: 'Memo',
+        value: 'not used',
+        subHooks: [],
+      },
+    ]);
+  });
 });

filename: packages/react-devtools-shell/src/app/InspectableElements/CustomHooks.js
patch: @@ -17,6 +17,7 @@ import {
   useContext,
   useDebugValue,
   useEffect,
+  useOptimistic,
   useState,
 } from 'react';
 
@@ -73,6 +74,7 @@ function FunctionWithHooks(props: any, ref: React$Ref<any>) {
   const [count, updateCount] = useState(0);
   // eslint-disable-next-line no-unused-vars
   const contextValueA = useContext(ContextA);
+  useOptimistic<number, mixed>(1);
 
   // eslint-disable-next-line no-unused-vars
   const [_, __] = useState(object);