Repository: facebook/react
Pull request: DevTools: support useEffectEvent and forward-fix experimental prefix support

- Adds support for `experimental_useEffectEvent`, now DevTools will be able to display this hook for inspected element
- Added a use case to DevTools shell, couldn't add case, because we are using ReactTestRenderer, which has the corresponding flag disabled.
- Forward-fix logic for handling `experimental` prefix that was added in https://github.com/facebook/react/pull/32088.

![Screenshot 2025-01-16 at 21 24 12](https://github.com/user-attachments/assets/6fb8ff2a-be47-47b5-bbfc-73d3a586657c)


Changed files:
filename: packages/react-debug-tools/src/ReactDebugHooks.js
patch: @@ -127,6 +127,13 @@ function getPrimitiveStackCache(): Map<string, Array<any>> {
       }
 
       Dispatcher.useId();
+
+      if (typeof Dispatcher.useResourceEffect === 'function') {
+        Dispatcher.useResourceEffect(() => ({}), []);
+      }
+      if (typeof Dispatcher.useEffectEvent === 'function') {
+        Dispatcher.useEffectEvent((args: empty) => {});
+      }
     } finally {
       readHookLog = hookLog;
       hookLog = [];
@@ -749,6 +756,20 @@ function useResourceEffect(
   });
 }
 
+function useEffectEvent<Args, F: (...Array<Args>) => mixed>(callback: F): F {
+  nextHook();
+  hookLog.push({
+    displayName: null,
+    primitive: 'EffectEvent',
+    stackError: new Error(),
+    value: callback,
+    debugInfo: null,
+    dispatcherHookName: 'EffectEvent',
+  });
+
+  return callback;
+}
+
 const Dispatcher: DispatcherType = {
   use,
   readContext,
@@ -773,6 +794,7 @@ const Dispatcher: DispatcherType = {
   useFormState,
   useActionState,
   useHostTransitionStatus,
+  useEffectEvent,
   useResourceEffect,
 };
 
@@ -962,7 +984,7 @@ function parseHookName(functionName: void | string): string {
     startIndex += 'unstable_'.length;
   }
 
-  if (functionName.slice(startIndex).startsWith('unstable_')) {
+  if (functionName.slice(startIndex).startsWith('experimental_')) {
     startIndex += 'experimental_'.length;
   }
 

filename: packages/react-devtools-shell/src/app/InspectableElements/InspectableElements.js
patch: @@ -19,6 +19,7 @@ import NestedProps from './NestedProps';
 import SimpleValues from './SimpleValues';
 import SymbolKeys from './SymbolKeys';
 import UseMemoCache from './UseMemoCache';
+import UseEffectEvent from './UseEffectEvent';
 
 // TODO Add Immutable JS example
 
@@ -36,6 +37,7 @@ export default function InspectableElements(): React.Node {
       <CircularReferences />
       <SymbolKeys />
       <UseMemoCache />
+      <UseEffectEvent />
     </Fragment>
   );
 }

filename: packages/react-devtools-shell/src/app/InspectableElements/UseEffectEvent.js
patch: @@ -0,0 +1,32 @@
+import * as React from 'react';
+
+const {experimental_useEffectEvent, useState, useEffect} = React;
+
+export default function UseEffectEvent(): React.Node {
+  return (
+    <>
+      <SingleHookCase />
+      <HookTreeCase />
+    </>
+  );
+}
+
+function SingleHookCase() {
+  const onClick = experimental_useEffectEvent(() => {});
+
+  return <div onClick={onClick} />;
+}
+
+function useCustomHook() {
+  const [state, setState] = useState();
+  const onClick = experimental_useEffectEvent(() => {});
+  useEffect(() => {});
+
+  return [state, setState, onClick];
+}
+
+function HookTreeCase() {
+  const onClick = useCustomHook();
+
+  return <div onClick={onClick} />;
+}