Skip to content

Commit

Permalink
useFormState -> useActionState
Browse files Browse the repository at this point in the history
Kept the transform support since useFormState still exists.
  • Loading branch information
eps1lon committed Apr 25, 2024
1 parent 6b474dd commit 5c3ce45
Show file tree
Hide file tree
Showing 18 changed files with 58 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -385,20 +385,20 @@ 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 = {
message: '',
}

export function Signup() {
const [state, formAction] = useFormState(createUser, initialState)
const [state, formAction] = useActionState(createUser, initialState)

return (
<form action={formAction}>
Expand All @@ -417,15 +417,15 @@ 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 = {
message: '',
}

export function Signup() {
const [state, formAction] = useFormState(createUser, initialState)
const [state, formAction] = useActionState(createUser, initialState)

return (
<form action={formAction}>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
- [`<form>`](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)
Original file line number Diff line number Diff line change
Expand Up @@ -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 [`<form>`](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 [`<form>`](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.

Expand Down Expand Up @@ -200,16 +200,16 @@ export async function signup(state, formData) {
}
```

Back in your `<SignupForm />`, you can use React's `useFormState()` hook to display validation errors to the user:
Back in your `<SignupForm />`, 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 (
<form action={action}>
Expand Down Expand Up @@ -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 (
<form action={action}>
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down
5 changes: 3 additions & 2 deletions examples/next-forms/app/add-form.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -18,7 +19,7 @@ function SubmitButton() {
}

export function AddForm() {
const [state, formAction] = useFormState(createTodo, initialState);
const [state, formAction] = useActionState(createTodo, initialState);

return (
<form action={formAction}>
Expand Down
5 changes: 3 additions & 2 deletions examples/next-forms/app/delete-form.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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 (
<form action={formAction}>
Expand Down
6 changes: 4 additions & 2 deletions examples/with-fauna/components/EntryForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ impl ReactServerComponentValidator {
"useSyncExternalStore",

Check failure on line 506 in packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs

View workflow job for this annotation

GitHub Actions / rust check / build

Diff in /root/actions-runner/_work/next.js/next.js/packages/next-swc/crates/next-custom-transforms/src/transforms/react_server_components.rs
"useTransition",
"useOptimistic",
"useActionState"
],
),
(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { flushSync, unstable_batchedUpdates } from 'react-dom'

import { useActionState } from 'react'

import { useFormStatus, useFormState } from 'react-dom'

export default function () {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/server/typescript/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const DISALLOWED_SERVER_REACT_APIS: string[] = [
'createFactory',
'experimental_useOptimistic',
'useOptimistic',
'useActionState',
]

export const DISALLOWED_SERVER_REACT_DOM_APIS: string[] = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useActionState } from 'react'

console.log({ useActionState })

export default function Page() {
return null
}
1 change: 1 addition & 0 deletions test/development/acceptance-app/rsc-build-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/actions/app-action-form-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<form action={formAction}>
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/app-dir/actions/app/client/form-state/page-2/page.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/app-dir/actions/app/client/form-state/page.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<>
<form action={formAction}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <div>Form Submitted.</div>
}
Expand Down
8 changes: 4 additions & 4 deletions test/turbopack-build-tests-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": [],
Expand Down

0 comments on commit 5c3ce45

Please sign in to comment.