Repository: facebook/react
Pull request: Warn when using useFormState

## Overview

useFormState has been replaced with useActionState. Warn when it's used.

Also removes the `experimental_useFormState` warnings.

Changed files:
filename: packages/react-dom/index.experimental.js
patch: @@ -25,31 +25,3 @@ export {
   preinitModule,
   version,
 } from './src/client/ReactDOM';
-
-import type {Awaited} from 'shared/ReactTypes';
-import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
-import {useFormStatus, useFormState} from './src/client/ReactDOM';
-
-export function experimental_useFormStatus(): FormStatus {
-  if (__DEV__) {
-    console.error(
-      'useFormStatus is now in canary. Remove the experimental_ prefix. ' +
-        'The prefixed alias will be removed in an upcoming release.',
-    );
-  }
-  return useFormStatus();
-}
-
-export function experimental_useFormState<S, P>(
-  action: (Awaited<S>, P) => S,
-  initialState: Awaited<S>,
-  permalink?: string,
-): [Awaited<S>, (P) => void, boolean] {
-  if (__DEV__) {
-    console.error(
-      'useFormState is now in canary. Remove the experimental_ prefix. ' +
-        'The prefixed alias will be removed in an upcoming release.',
-    );
-  }
-  return useFormState(action, initialState, permalink);
-}

filename: packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
patch: @@ -100,6 +100,16 @@ describe('ReactDOMFizzServer', () => {
     }
     PropTypes = require('prop-types');
     if (__VARIANT__) {
+      const originalConsoleError = console.error;
+      console.error = (error, ...args) => {
+        if (
+          typeof error !== 'string' ||
+          error.indexOf('ReactDOM.useFormState has been deprecated') === -1
+        ) {
+          originalConsoleError(error, ...args);
+        }
+      };
+
       // Remove after API is deleted.
       useActionState = ReactDOM.useFormState;
     } else {

filename: packages/react-dom/src/__tests__/ReactDOMForm-test.js
patch: @@ -62,6 +62,15 @@ describe('ReactDOMForm', () => {
     textCache = new Map();
 
     if (__VARIANT__) {
+      const originalConsoleError = console.error;
+      console.error = (error, ...args) => {
+        if (
+          typeof error !== 'string' ||
+          error.indexOf('ReactDOM.useFormState has been deprecated') === -1
+        ) {
+          originalConsoleError(error, ...args);
+        }
+      };
       // Remove after API is deleted.
       useActionState = ReactDOM.useFormState;
     } else {

filename: packages/react-reconciler/src/ReactFiberHooks.js
patch: @@ -178,10 +178,12 @@ let didWarnAboutMismatchedHooksForComponent;
 let didWarnUncachedGetSnapshot: void | true;
 let didWarnAboutUseWrappedInTryCatch;
 let didWarnAboutAsyncClientComponent;
+let didWarnAboutUseFormState;
 if (__DEV__) {
   didWarnAboutMismatchedHooksForComponent = new Set<string | null>();
   didWarnAboutUseWrappedInTryCatch = new Set<string | null>();
   didWarnAboutAsyncClientComponent = new Set<string | null>();
+  didWarnAboutUseFormState = new Set<string | null>();
 }
 
 export type Hook = {
@@ -386,6 +388,21 @@ function warnOnHookMismatchInDev(currentHookName: HookType): void {
   }
 }
 
+function warnOnUseFormStateInDev(): void {
+  if (__DEV__) {
+    const componentName = getComponentNameFromFiber(currentlyRenderingFiber);
+    if (!didWarnAboutUseFormState.has(componentName)) {
+      didWarnAboutUseFormState.add(componentName);
+
+      console.error(
+        'ReactDOM.useFormState has been deprecated and replaced by ' +
+          'React.useActionState. Please update %s to use React.useActionState.',
+        componentName,
+      );
+    }
+  }
+}
+
 function warnIfAsyncClientComponent(Component: Function) {
   if (__DEV__) {
     // This dev-only check only works for detecting native async functions,
@@ -4000,6 +4017,7 @@ if (__DEV__) {
       ): [Awaited<S>, (P) => void, boolean] {
         currentHookNameInDev = 'useFormState';
         updateHookTypesDev();
+        warnOnUseFormStateInDev();
         return mountActionState(action, initialState, permalink);
       };
     (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useActionState =
@@ -4182,6 +4200,7 @@ if (__DEV__) {
       ): [Awaited<S>, (P) => void, boolean] {
         currentHookNameInDev = 'useFormState';
         updateHookTypesDev();
+        warnOnUseFormStateInDev();
         return updateActionState(action, initialState, permalink);
       };
     (HooksDispatcherOnUpdateInDEV: Dispatcher).useActionState =
@@ -4364,6 +4383,7 @@ if (__DEV__) {
       ): [Awaited<S>, (P) => void, boolean] {
         currentHookNameInDev = 'useFormState';
         updateHookTypesDev();
+        warnOnUseFormStateInDev();
         return rerenderActionState(action, initialState, permalink);
       };
     (HooksDispatcherOnRerenderInDEV: Dispatcher).useActionState =