Repository: facebook/react
Pull request: devtools: Use context displayName for context hook name


## Summary

When inspecting the hooks of an element, the display name of the passed context is now used for the context hook (with a fallback to the previous "Context" name).

Before:
![Screenshot from 2023-01-01 19-07-45](https://user-images.githubusercontent.com/12292047/210181524-539a0917-ec30-44c7-a47a-ebb8b1272c66.png)


After:

![Screenshot from 2023-01-01 19-40-32](https://user-images.githubusercontent.com/12292047/210181526-b1146b44-b03b-4aac-98f6-7571fa87d214.png)

## How did you test this change?

- [x] added tests
- [x] react-devtools-shell (see attached screenshots) 

Changed files:
filename: packages/react-debug-tools/src/ReactDebugHooks.js
patch: @@ -39,6 +39,7 @@ type CurrentDispatcherRef = typeof ReactSharedInternals.ReactCurrentDispatcher;
 // Used to track hooks called during a render
 
 type HookLogEntry = {
+  displayName: string | null,
   primitive: string,
   stackError: Error,
   value: mixed,
@@ -171,6 +172,7 @@ function use<T>(usable: Usable<T>): T {
         case 'fulfilled': {
           const fulfilledValue: T = thenable.value;
           hookLog.push({
+            displayName: null,
             primitive: 'Promise',
             stackError: new Error(),
             value: fulfilledValue,
@@ -187,6 +189,7 @@ function use<T>(usable: Usable<T>): T {
       // If this was an uncached Promise we have to abandon this attempt
       // but we can still emit anything up until this point.
       hookLog.push({
+        displayName: null,
         primitive: 'Unresolved',
         stackError: new Error(),
         value: thenable,
@@ -199,6 +202,7 @@ function use<T>(usable: Usable<T>): T {
       const value = readContext(context);
 
       hookLog.push({
+        displayName: context.displayName || 'Context',
         primitive: 'Context (use)',
         stackError: new Error(),
         value,
@@ -215,6 +219,7 @@ function use<T>(usable: Usable<T>): T {
 
 function useContext<T>(context: ReactContext<T>): T {
   hookLog.push({
+    displayName: context.displayName || null,
     primitive: 'Context',
     stackError: new Error(),
     value: context._currentValue,
@@ -235,6 +240,7 @@ function useState<S>(
         initialState()
       : initialState;
   hookLog.push({
+    displayName: null,
     primitive: 'State',
     stackError: new Error(),
     value: state,
@@ -256,6 +262,7 @@ function useReducer<S, I, A>(
     state = init !== undefined ? init(initialArg) : ((initialArg: any): S);
   }
   hookLog.push({
+    displayName: null,
     primitive: 'Reducer',
     stackError: new Error(),
     value: state,
@@ -268,6 +275,7 @@ function useRef<T>(initialValue: T): {current: T} {
   const hook = nextHook();
   const ref = hook !== null ? hook.memoizedState : {current: initialValue};
   hookLog.push({
+    displayName: null,
     primitive: 'Ref',
     stackError: new Error(),
     value: ref.current,
@@ -279,6 +287,7 @@ function useRef<T>(initialValue: T): {current: T} {
 function useCacheRefresh(): () => void {
   const hook = nextHook();
   hookLog.push({
+    displayName: null,
     primitive: 'CacheRefresh',
     stackError: new Error(),
     value: hook !== null ? hook.memoizedState : function refresh() {},
@@ -293,6 +302,7 @@ function useLayoutEffect(
 ): void {
   nextHook();
   hookLog.push({
+    displayName: null,
     primitive: 'LayoutEffect',
     stackError: new Error(),
     value: create,
@@ -306,6 +316,7 @@ function useInsertionEffect(
 ): void {
   nextHook();
   hookLog.push({
+    displayName: null,
     primitive: 'InsertionEffect',
     stackError: new Error(),
     value: create,
@@ -319,6 +330,7 @@ function useEffect(
 ): void {
   nextHook();
   hookLog.push({
+    displayName: null,
     primitive: 'Effect',
     stackError: new Error(),
     value: create,
@@ -341,6 +353,7 @@ function useImperativeHandle<T>(
     instance = ref.current;
   }
   hookLog.push({
+    displayName: null,
     primitive: 'ImperativeHandle',
     stackError: new Error(),
     value: instance,
@@ -350,6 +363,7 @@ function useImperativeHandle<T>(
 
 function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
   hookLog.push({
+    displayName: null,
     primitive: 'DebugValue',
     stackError: new Error(),
     value: typeof formatterFn === 'function' ? formatterFn(value) : value,
@@ -360,6 +374,7 @@ function useDebugValue(value: any, formatterFn: ?(value: any) => any) {
 function useCallback<T>(callback: T, inputs: Array<mixed> | void | null): T {
   const hook = nextHook();
   hookLog.push({
+    displayName: null,
     primitive: 'Callback',
     stackError: new Error(),
     value: hook !== null ? hook.memoizedState[0] : callback,
@@ -375,6 +390,7 @@ function useMemo<T>(
   const hook = nextHook();
   const value = hook !== null ? hook.memoizedState[0] : nextCreate();
   hookLog.push({
+    displayName: null,
     primitive: 'Memo',
     stackError: new Error(),
     value,
@@ -395,6 +411,7 @@ function useSyncExternalStore<T>(
   nextHook(); // Effect
   const value = getSnapshot();
   hookLog.push({
+    displayName: null,
     primitive: 'SyncExternalStore',
     stackError: new Error(),
     value,
@@ -413,6 +430,7 @@ function useTransition(): [
   nextHook(); // State
   nextHook(); // Callback
   hookLog.push({
+    displayName: null,
     primitive: 'Transition',
     stackError: new Error(),
     value: undefined,
@@ -424,6 +442,7 @@ function useTransition(): [
 function useDeferredValue<T>(value: T, initialValue?: T): T {
   const hook = nextHook();
   hookLog.push({
+    displayName: null,
     primitive: 'DeferredValue',
     stackError: new Error(),
     value: hook !== null ? hook.memoizedState : value,
@@ -436,6 +455,7 @@ function useId(): string {
   const hook = nextHook();
   const id = hook !== null ? hook.memoizedState : '';
   hookLog.push({
+    displayName: null,
     primitive: 'Id',
     stackError: new Error(),
     value: id,
@@ -485,6 +505,7 @@ function useOptimistic<S, A>(
     state = passthrough;
   }
   hookLog.push({
+    displayName: null,
     primitive: 'Optimistic',
     stackError: new Error(),
     value: state,
@@ -507,6 +528,7 @@ function useFormState<S, P>(
     state = initialState;
   }
   hookLog.push({
+    displayName: null,
     primitive: 'FormState',
     stackError: new Error(),
     value: state,
@@ -780,7 +802,7 @@ function buildTree(
       }
       prevStack = stack;
     }
-    const {primitive, debugInfo} = hook;
+    const {displayName, primitive, debugInfo} = hook;
 
     // For now, the "id" of stateful hooks is just the stateful hook index.
     // Custom hooks have no ids, nor do non-stateful native hooks (e.g. Context, DebugValue).
@@ -795,7 +817,7 @@ function buildTree(
 
     // For the time being, only State and Reducer hooks support runtime overrides.
     const isStateEditable = primitive === 'Reducer' || primitive === 'State';
-    const name = primitive === 'Context (use)' ? 'Context' : primitive;
+    const name = displayName || primitive;
     const levelChild: HooksNode = {
       id,
       isStateEditable,

filename: packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
patch: @@ -772,8 +772,11 @@ describe('ReactHooksInspectionIntegration', () => {
 
   it('should inspect the value of the current provider in useContext', () => {
     const MyContext = React.createContext('default');
+    const ThemeContext = React.createContext('default');
+    ThemeContext.displayName = 'Theme';
     function Foo(props) {
       const value = React.useContext(MyContext);
+      React.useContext(ThemeContext);
       return <div>{value}</div>;
     }
     const renderer = ReactTestRenderer.create(
@@ -799,6 +802,20 @@ describe('ReactHooksInspectionIntegration', () => {
           "subHooks": [],
           "value": "contextual",
         },
+        {
+          "debugInfo": null,
+          "hookSource": {
+            "columnNumber": 0,
+            "fileName": "**",
+            "functionName": "Foo",
+            "lineNumber": 0,
+          },
+          "id": null,
+          "isStateEditable": false,
+          "name": "Theme",
+          "subHooks": [],
+          "value": "default",
+        },
       ]
     `);
   });