Repository: facebook/react
Pull request: fix[react-devtools/ReactDebugHooks]: support unstable prefixes in hooks and useContextWithBailout

Related - https://github.com/facebook/react/pull/30407.

This is experimental-only and FB-only hook. Without these changes, inspecting an element that is using this hook will throw an error, because this hook is missing in Dispatcher implementation from React DevTools, which overrides the original one to build the hook tree.

![Screenshot 2024-08-28 at 18 42 55](https://github.com/user-attachments/assets/e3bccb92-74fb-4e4a-8181-03d13f8512c0)

One nice thing from it is that in case of any potential regressions related to this experiment, we can quickly triage which implementation of `useContext` is used by inspecting an element in React DevTools.

Ideally, I should've added some component that is using this hook to `react-devtools-shell`, so it can be manually tested, but I can't do it without rewriting the infra for it. This is because this hook is only available from fb-www builds, and not experimental.


Changed files:
filename: packages/react-debug-tools/src/ReactDebugHooks.js
patch: @@ -20,6 +20,7 @@ import type {
   Dependencies,
   Fiber,
   Dispatcher as DispatcherType,
+  ContextDependencyWithSelect,
 } from 'react-reconciler/src/ReactInternalTypes';
 import type {TransitionStatus} from 'react-reconciler/src/ReactFiberConfig';
 
@@ -37,7 +38,6 @@ import {
   REACT_CONTEXT_TYPE,
 } from 'shared/ReactSymbols';
 import hasOwnProperty from 'shared/hasOwnProperty';
-import type {ContextDependencyWithSelect} from '../../react-reconciler/src/ReactInternalTypes';
 
 type CurrentDispatcherRef = typeof ReactSharedInternals;
 
@@ -76,6 +76,13 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
     try {
       // Use all hooks here to add them to the hook log.
       Dispatcher.useContext(({_currentValue: null}: any));
+      if (typeof Dispatcher.unstable_useContextWithBailout === 'function') {
+        // This type check is for Flow only.
+        Dispatcher.unstable_useContextWithBailout(
+          ({_currentValue: null}: any),
+          null,
+        );
+      }
       Dispatcher.useState(null);
       Dispatcher.useReducer((s: mixed, a: mixed) => s, null);
       Dispatcher.useRef(null);
@@ -280,6 +287,22 @@ function useContext<T>(context: ReactContext<T>): T {
   return value;
 }
 
+function unstable_useContextWithBailout<T>(
+  context: ReactContext<T>,
+  select: (T => Array<mixed>) | null,
+): T {
+  const value = readContext(context);
+  hookLog.push({
+    displayName: context.displayName || null,
+    primitive: 'ContextWithBailout',
+    stackError: new Error(),
+    value: value,
+    debugInfo: null,
+    dispatcherHookName: 'ContextWithBailout',
+  });
+  return value;
+}
+
 function useState<S>(
   initialState: (() => S) | S,
 ): [S, Dispatch<BasicStateAction<S>>] {
@@ -753,6 +776,7 @@ const Dispatcher: DispatcherType = {
   useCacheRefresh,
   useCallback,
   useContext,
+  unstable_useContextWithBailout,
   useEffect,
   useImperativeHandle,
   useDebugValue,
@@ -954,6 +978,11 @@ function parseHookName(functionName: void | string): string {
   } else {
     startIndex += 1;
   }
+
+  if (functionName.slice(startIndex).startsWith('unstable_')) {
+    startIndex += 'unstable_'.length;
+  }
+
   if (functionName.slice(startIndex, startIndex + 3) === 'use') {
     if (functionName.length - startIndex === 3) {
       return 'Use';