diff --git a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx index 9284955029dde..336e9f1135671 100644 --- a/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx +++ b/docs/02-app/01-building-your-application/02-data-fetching/02-server-actions-and-mutations.mdx @@ -358,10 +358,10 @@ export default async function createsUser(formData) { } ``` -Once the fields have been validated on the server, you can return a serializable object in your action and use the React [`useFormState`](https://react.dev/reference/react-dom/hooks/useFormState) hook to show a message to the user. +Once the fields have been validated on the server, you can return a serializable object in your action and use the React [`useActionState`](https://react.dev/reference/react-dom/hooks/useActionState) hook to show a message to the user. -- By passing the action to `useFormState`, the action's function signature changes to receive a new `prevState` or `initialState` parameter as its first argument. -- `useFormState` is a React hook and therefore must be used in a Client Component. +- By passing the action to `useActionState`, the action's function signature changes to receive a new `prevState` or `initialState` parameter as its first argument. +- `useActionState` is a React hook and therefore must be used in a Client Component. ```tsx filename="app/actions.ts" switcher 'use server' @@ -385,12 +385,12 @@ export async function createUser(prevState, formData) { } ``` -Then, you can pass your action to the `useFormState` hook and use the returned `state` to display an error message. +Then, you can pass your action to the `useActionState` hook and use the returned `state` to display an error message. ```tsx filename="app/ui/signup.tsx" switcher 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { createUser } from '@/app/actions' const initialState = { @@ -398,7 +398,7 @@ const initialState = { } export function Signup() { - const [state, formAction] = useFormState(createUser, initialState) + const [state, formAction] = useActionState(createUser, initialState) return (
@@ -417,7 +417,7 @@ export function Signup() { ```jsx filename="app/ui/signup.js" switcher 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { createUser } from '@/app/actions' const initialState = { @@ -425,7 +425,7 @@ const initialState = { } export function Signup() { - const [state, formAction] = useFormState(createUser, initialState) + const [state, formAction] = useActionState(createUser, initialState) return ( @@ -739,7 +739,7 @@ export async function createTodo(prevState, formData) { > **Good to know:** > -> - Aside from throwing the error, you can also return an object to be handled by `useFormState`. See [Server-side validation and error handling](#server-side-validation-and-error-handling). +> - Aside from throwing the error, you can also return an object to be handled by `useActionState`. See [Server-side validation and error handling](#server-side-validation-and-error-handling). ### Revalidating data @@ -1002,5 +1002,5 @@ For more information on Server Actions, check out the following React docs: - [`"use server"`](https://react.dev/reference/react/use-server) - [``](https://react.dev/reference/react-dom/components/form) - [`useFormStatus`](https://react.dev/reference/react-dom/hooks/useFormStatus) -- [`useFormState`](https://react.dev/reference/react-dom/hooks/useFormState) +- [`useActionState`](https://react.dev/reference/react-dom/hooks/useActionState) - [`useOptimistic`](https://react.dev/reference/react/useOptimistic) diff --git a/docs/02-app/01-building-your-application/09-authentication/index.mdx b/docs/02-app/01-building-your-application/09-authentication/index.mdx index 496ef3be52066..88a1bf64cd542 100644 --- a/docs/02-app/01-building-your-application/09-authentication/index.mdx +++ b/docs/02-app/01-building-your-application/09-authentication/index.mdx @@ -29,7 +29,7 @@ The examples on this page walk through basic username and password auth for educ ### Sign-up and login functionality -You can use the [``](https://react.dev/reference/react-dom/components/form) element with React's [Server Actions](/docs/app/building-your-application/rendering/server-components), [`useFormStatus()`](https://react.dev/reference/react-dom/hooks/useFormStatus), and [`useFormState()`](https://react.dev/reference/react-dom/hooks/useFormState) to capture user credentials, validate form fields, and call your Authentication Provider's API or database. +You can use the [``](https://react.dev/reference/react-dom/components/form) element with React's [Server Actions](/docs/app/building-your-application/rendering/server-components), [`useFormStatus()`](https://react.dev/reference/react-dom/hooks/useFormStatus), and [`useActionState()`](https://react.dev/reference/react/useActionState) to capture user credentials, validate form fields, and call your Authentication Provider's API or database. Since Server Actions always execute on the server, they provide a secure environment for handling authentication logic. @@ -200,16 +200,16 @@ export async function signup(state, formData) { } ``` -Back in your ``, you can use React's `useFormState()` hook to display validation errors to the user: +Back in your ``, you can use React's `useActionState()` hook to display validation errors to the user: ```tsx filename="app/ui/signup-form.tsx" switcher highlight={7,15,21,27-36} 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { signup } from '@/app/actions/auth' export function SignupForm() { - const [state, action] = useFormState(signup, undefined) + const [state, action] = useActionState(signup, undefined) return ( @@ -248,11 +248,11 @@ export function SignupForm() { ```jsx filename="app/ui/signup-form.js" switcher highlight={7,15,21,27-36} 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { signup } from '@/app/actions/auth' export function SignupForm() { - const [state, action] = useFormState(signup, undefined) + const [state, action] = useActionState(signup, undefined) return ( @@ -293,7 +293,8 @@ You can also use the `useFormStatus()` hook to handle the pending state on form ```tsx filename="app/ui/signup-form.tsx" highlight={7} switcher 'use client' -import { useFormStatus, useFormState } from 'react-dom' +import { useActionState } from 'react' +import { useFormStatus } from 'react-dom' export function SignupButton() { const { pending } = useFormStatus() @@ -309,7 +310,8 @@ export function SignupButton() { ```jsx filename="app/ui/signup-form.js" highlight={7} switcher 'use client' -import { useFormStatus, useFormState } from 'react-dom' +import { useActionState } from 'react' +import { useFormStatus } from 'react-dom' export function SignupButton() { const { pending } = useFormStatus() diff --git a/examples/next-forms/app/add-form.tsx b/examples/next-forms/app/add-form.tsx index 1cadaee2b4277..3f19b79ec4bc0 100644 --- a/examples/next-forms/app/add-form.tsx +++ b/examples/next-forms/app/add-form.tsx @@ -1,6 +1,7 @@ "use client"; -import { useFormState, useFormStatus } from "react-dom"; +import { useActionState } from 'react' +import { useFormStatus } from 'react-dom' import { createTodo } from "@/app/actions"; const initialState = { @@ -18,7 +19,7 @@ function SubmitButton() { } export function AddForm() { - const [state, formAction] = useFormState(createTodo, initialState); + const [state, formAction] = useActionState(createTodo, initialState); return ( diff --git a/examples/next-forms/app/delete-form.tsx b/examples/next-forms/app/delete-form.tsx index acc633e972f61..823812715515f 100644 --- a/examples/next-forms/app/delete-form.tsx +++ b/examples/next-forms/app/delete-form.tsx @@ -1,6 +1,7 @@ "use client"; -import { useFormState, useFormStatus } from "react-dom"; +import { useActionState } from 'react' +import { useFormStatus } from 'react-dom' import { deleteTodo } from "@/app/actions"; const initialState = { @@ -18,7 +19,7 @@ function DeleteButton() { } export function DeleteForm({ id, todo }: { id: number; todo: string }) { - const [state, formAction] = useFormState(deleteTodo, initialState); + const [state, formAction] = useActionState(deleteTodo, initialState); return ( diff --git a/examples/with-fauna/components/EntryForm.tsx b/examples/with-fauna/components/EntryForm.tsx index eca9d68d668db..5b4661c974e45 100644 --- a/examples/with-fauna/components/EntryForm.tsx +++ b/examples/with-fauna/components/EntryForm.tsx @@ -3,7 +3,9 @@ import cn from "classnames"; import { createEntryAction } from "@/actions/entry"; // @ts-ignore -import { useFormState, useFormStatus } from "react-dom"; +import { useActionState } from 'react' +// @ts-ignore +import { useFormStatus } from 'react-dom' import LoadingSpinner from "@/components/LoadingSpinner"; import SuccessMessage from "@/components/SuccessMessage"; import ErrorMessage from "@/components/ErrorMessage"; @@ -20,7 +22,7 @@ const initialState = { }; export default function EntryForm() { - const [state, formAction] = useFormState(createEntryAction, initialState); + const [state, formAction] = useActionState(createEntryAction, initialState); const { pending } = useFormStatus(); return ( diff --git a/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs b/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs index 3cc35cc6604ef..f071c04e9df8a 100644 --- a/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs +++ b/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs @@ -506,6 +506,7 @@ impl ReactServerComponentValidator { "useSyncExternalStore", "useTransition", "useOptimistic", + "useActionState" ], ), ( diff --git a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js index 5990a5fc9b4dc..a003a40719177 100644 --- a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js +++ b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/input.js @@ -1,5 +1,7 @@ import { flushSync, unstable_batchedUpdates } from 'react-dom' +import { useActionState } from 'react' + import { useFormStatus, useFormState } from 'react-dom' export default function () { diff --git a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js index f7230635cdcff..2637549fadc85 100644 --- a/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js +++ b/packages/next-swc/crates/next-custom-transforms/tests/errors/react-server-components/server-graph/react-dom-api/output.js @@ -1,4 +1,5 @@ import { flushSync, unstable_batchedUpdates } from 'react-dom'; +import { useActionState } from 'react' import { useFormStatus, useFormState } from 'react-dom'; export default function() { return null; diff --git a/packages/next/src/server/typescript/constant.ts b/packages/next/src/server/typescript/constant.ts index cd0ba22138219..42ddd95ce9eb5 100644 --- a/packages/next/src/server/typescript/constant.ts +++ b/packages/next/src/server/typescript/constant.ts @@ -40,6 +40,7 @@ export const DISALLOWED_SERVER_REACT_APIS: string[] = [ 'createFactory', 'experimental_useOptimistic', 'useOptimistic', + 'useActionState', ] export const DISALLOWED_SERVER_REACT_DOM_APIS: string[] = [ diff --git a/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/react-apis/useActionState/page.js b/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/react-apis/useActionState/page.js new file mode 100644 index 0000000000000..52bdc85f573a0 --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-build-errors/app/server-with-errors/react-apis/useActionState/page.js @@ -0,0 +1,7 @@ +import { useActionState } from 'react' + +console.log({ useActionState }) + +export default function Page() { + return null +} diff --git a/test/development/acceptance-app/rsc-build-errors.test.ts b/test/development/acceptance-app/rsc-build-errors.test.ts index c6b8f1d429d72..17f25b2cbce3e 100644 --- a/test/development/acceptance-app/rsc-build-errors.test.ts +++ b/test/development/acceptance-app/rsc-build-errors.test.ts @@ -262,6 +262,7 @@ describe('Error overlay - RSC build errors', () => { 'useSyncExternalStore', 'useTransition', 'useOptimistic', + 'useActionState', ] for (const api of invalidReactServerApis) { it(`should error when ${api} from react is used in server component`, async () => { diff --git a/test/e2e/app-dir/actions/app-action-form-state.test.ts b/test/e2e/app-dir/actions/app-action-form-state.test.ts index 639c98f1163ca..86a74553a11b1 100644 --- a/test/e2e/app-dir/actions/app-action-form-state.test.ts +++ b/test/e2e/app-dir/actions/app-action-form-state.test.ts @@ -2,7 +2,7 @@ import { nextTestSetup } from 'e2e-utils' import { check } from 'next-test-utils' -describe('app-dir action useFormState', () => { +describe('app-dir action useActionState', () => { const { next } = nextTestSetup({ files: __dirname, dependencies: { diff --git a/test/e2e/app-dir/actions/app/client/action-return-client-component/form.js b/test/e2e/app-dir/actions/app/client/action-return-client-component/form.js index a37de24565676..5ab10b1326611 100644 --- a/test/e2e/app-dir/actions/app/client/action-return-client-component/form.js +++ b/test/e2e/app-dir/actions/app/client/action-return-client-component/form.js @@ -1,8 +1,8 @@ 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' export function Form({ action }) { - const [state, formAction] = useFormState(action, null) + const [state, formAction] = useActionState(action, null) return ( <> diff --git a/test/e2e/app-dir/actions/app/client/form-state/page-2/page.js b/test/e2e/app-dir/actions/app/client/form-state/page-2/page.js index fb8684dec23a5..a1b13e0e48b54 100644 --- a/test/e2e/app-dir/actions/app/client/form-state/page-2/page.js +++ b/test/e2e/app-dir/actions/app/client/form-state/page-2/page.js @@ -1,11 +1,11 @@ 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { appendName } from '../actions' import { useEffect, useState } from 'react' export default function Page() { - const [state, appendNameFormAction] = useFormState( + const [state, appendNameFormAction] = useActionState( appendName, 'initial-state', '/client/form-state' diff --git a/test/e2e/app-dir/actions/app/client/form-state/page.js b/test/e2e/app-dir/actions/app/client/form-state/page.js index 3427f2324c66a..78bf94e0cadec 100644 --- a/test/e2e/app-dir/actions/app/client/form-state/page.js +++ b/test/e2e/app-dir/actions/app/client/form-state/page.js @@ -1,11 +1,11 @@ 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { appendName } from './actions' import { useEffect, useState } from 'react' export default function Page() { - const [state, appendNameFormAction] = useFormState( + const [state, appendNameFormAction] = useActionState( appendName, 'initial-state', '/client/form-state' diff --git a/test/e2e/app-dir/actions/app/server/action-return-client-component/form.js b/test/e2e/app-dir/actions/app/server/action-return-client-component/form.js index a37de24565676..5ab10b1326611 100644 --- a/test/e2e/app-dir/actions/app/server/action-return-client-component/form.js +++ b/test/e2e/app-dir/actions/app/server/action-return-client-component/form.js @@ -1,8 +1,8 @@ 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' export function Form({ action }) { - const [state, formAction] = useFormState(action, null) + const [state, formAction] = useActionState(action, null) return ( <> diff --git a/test/e2e/app-dir/navigation/app/popstate-revalidate/foo/page.tsx b/test/e2e/app-dir/navigation/app/popstate-revalidate/foo/page.tsx index 75723b799c2ea..956aebd349ba0 100644 --- a/test/e2e/app-dir/navigation/app/popstate-revalidate/foo/page.tsx +++ b/test/e2e/app-dir/navigation/app/popstate-revalidate/foo/page.tsx @@ -1,10 +1,10 @@ 'use client' -import { useFormState } from 'react-dom' +import { useActionState } from 'react' import { action } from './action' export default function Page() { - const [submitted, formAction] = useFormState(action, false) + const [submitted, formAction] = useActionState(action, false) if (submitted) { return
Form Submitted.
} diff --git a/test/turbopack-build-tests-manifest.json b/test/turbopack-build-tests-manifest.json index 32a5b2432124d..26a23dced2339 100644 --- a/test/turbopack-build-tests-manifest.json +++ b/test/turbopack-build-tests-manifest.json @@ -95,10 +95,10 @@ "test/e2e/app-dir/actions/app-action-form-state.test.ts": { "passed": [], "failed": [ - "app-dir action useFormState should send the action to the provided permalink with form state when JS disabled", - "app-dir action useFormState should support hydrating the app from progressively enhanced form request", - "app-dir action useFormState should support submitting form state with JS", - "app-dir action useFormState should support submitting form state without JS" + "app-dir action useActionState should send the action to the provided permalink with form state when JS disabled", + "app-dir action useActionState should support hydrating the app from progressively enhanced form request", + "app-dir action useActionState should support submitting form state with JS", + "app-dir action useActionState should support submitting form state without JS" ], "pending": [], "flakey": [],