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 27, 2024
1 parent 9138d7d commit 770b97d
Show file tree
Hide file tree
Showing 18 changed files with 58 additions and 39 deletions.
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)
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
@@ -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
@@ -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
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
Expand Up @@ -506,6 +506,7 @@ impl ReactServerComponentValidator {
"useSyncExternalStore",
"useTransition",
"useOptimistic",
"useActionState"
],
),
(
Expand Down
@@ -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
@@ -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
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
@@ -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
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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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 770b97d

Please sign in to comment.