mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
- [x] Rename page from `forms-and-mutations` to `server-actions-and-mutations` to account for examples that don't use forms. - [x] Split `/pages` and `/app` content to make easier to edit - [x] Add Security Section - [x] Recommend tainting - [x] Closures and encryption - [x] Overwriting encryption keys - [x] CSRF protection | Allowed Origins - [x] Add examples for Server Actions being called outside forms - [x] `useEffect` - [x] Event handlers - [ ] ~3rd party libraries~ - [x] More form examples - [x] Add note on calling actions in `<button>` and `<input> ` - [x] Add `.bind` example | Recommend bind over hidden input field - [x] Recommend `try/catch` for error handling - [x] Create `serverActions` next.config.js page - [x] Document `allowedOrigins` - [x] Document `bodySizeLimit` - [x] Add note on Server Actions flag for < v14 - [x] Update error message links - [x] Remove Server Actions from API reference as it's a React feature. E.g. We don't have API references for Server Components. - [ ] Set up redirects: --------- Co-authored-by: Lee Robinson <[email protected]> Co-authored-by: Shu Ding <[email protected]> Co-authored-by: Michael Novotny <[email protected]>
407 lines
11 KiB
Plaintext
407 lines
11 KiB
Plaintext
---
|
|
title: Forms and Mutations
|
|
nav_title: Forms and Mutations
|
|
description: Learn how to handle form submissions and data mutations with Next.js.
|
|
---
|
|
|
|
Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using **API Routes**.
|
|
|
|
> **Good to know:**
|
|
>
|
|
> - We will soon recommend [incrementally adopting](/docs/app/building-your-application/upgrading/app-router-migration) the App Router and using [Server Actions](/docs/app/building-your-application/data-fetching/server-actions-and-mutations) for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route.
|
|
> - API Routes [do not specify CORS headers](https://developer.mozilla.org/docs/Web/HTTP/CORS), meaning they are same-origin only by default.
|
|
> - Since API Routes run on the server, we're able to use sensitive values (like API keys) through [Environment Variables](/docs/pages/building-your-application/configuring/environment-variables) without exposing them to the client. This is critical for the security of your application.
|
|
|
|
## Examples
|
|
|
|
### Server-only form
|
|
|
|
With the Pages Router, you need to manually create API endpoints to handle securely mutating data on the server.
|
|
|
|
```ts filename="pages/api/submit.ts" switcher
|
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
const data = req.body
|
|
const id = await createItem(data)
|
|
res.status(200).json({ id })
|
|
}
|
|
```
|
|
|
|
```js filename="pages/api/submit.js" switcher
|
|
export default function handler(req, res) {
|
|
const data = req.body
|
|
const id = await createItem(data)
|
|
res.status(200).json({ id })
|
|
}
|
|
```
|
|
|
|
Then, call the API Route from the client with an event handler:
|
|
|
|
```tsx filename="pages/index.tsx" switcher
|
|
import { FormEvent } from 'react'
|
|
|
|
export default function Page() {
|
|
async function onSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault()
|
|
|
|
const formData = new FormData(event.currentTarget)
|
|
const response = await fetch('/api/submit', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
|
|
// Handle response if necessary
|
|
const data = await response.json()
|
|
// ...
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={onSubmit}>
|
|
<input type="text" name="name" />
|
|
<button type="submit">Submit</button>
|
|
</form>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="pages/index.jsx" switcher
|
|
export default function Page() {
|
|
async function onSubmit(event) {
|
|
event.preventDefault()
|
|
|
|
const formData = new FormData(event.target)
|
|
const response = await fetch('/api/submit', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
|
|
// Handle response if necessary
|
|
const data = await response.json()
|
|
// ...
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={onSubmit}>
|
|
<input type="text" name="name" />
|
|
<button type="submit">Submit</button>
|
|
</form>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Form validation
|
|
|
|
We recommend using HTML validation like `required` and `type="email"` for basic client-side form validation.
|
|
|
|
For more advanced server-side validation, you can use a schema validation library like [zod](https://zod.dev/) to validate the form fields before mutating the data:
|
|
|
|
```ts filename="pages/api/submit.ts" switcher
|
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
import { z } from 'zod'
|
|
|
|
const schema = z.object({
|
|
// ...
|
|
})
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
const parsed = schema.parse(req.body)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
```js filename="pages/api/submit.js" switcher
|
|
import { z } from 'zod'
|
|
|
|
const schema = z.object({
|
|
// ...
|
|
})
|
|
|
|
export default async function handler(req, res) {
|
|
const parsed = schema.parse(req.body)
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Error handling
|
|
|
|
You can use React state to show an error message when a form submission fails:
|
|
|
|
```tsx filename="pages/index.tsx" switcher
|
|
import React, { useState, FormEvent } from 'react'
|
|
|
|
export default function Page() {
|
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
async function onSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault()
|
|
setIsLoading(true)
|
|
setError(null) // Clear previous errors when a new request starts
|
|
|
|
try {
|
|
const formData = new FormData(event.currentTarget)
|
|
const response = await fetch('/api/submit', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to submit the data. Please try again.')
|
|
}
|
|
|
|
// Handle response if necessary
|
|
const data = await response.json()
|
|
// ...
|
|
} catch (error) {
|
|
// Capture the error message to display to the user
|
|
setError(error.message)
|
|
console.error(error)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
{error && <div style={{ color: 'red' }}>{error}</div>}
|
|
<form onSubmit={onSubmit}>
|
|
<input type="text" name="name" />
|
|
<button type="submit" disabled={isLoading}>
|
|
{isLoading ? 'Loading...' : 'Submit'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="pages/index.jsx" switcher
|
|
import React, { useState } from 'react'
|
|
|
|
export default function Page() {
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [error, setError] = useState(null)
|
|
|
|
async function onSubmit(event) {
|
|
event.preventDefault()
|
|
setIsLoading(true)
|
|
setError(null) // Clear previous errors when a new request starts
|
|
|
|
try {
|
|
const formData = new FormData(event.currentTarget)
|
|
const response = await fetch('/api/submit', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Failed to submit the data. Please try again.')
|
|
}
|
|
|
|
// Handle response if necessary
|
|
const data = await response.json()
|
|
// ...
|
|
} catch (error) {
|
|
// Capture the error message to display to the user
|
|
setError(error.message)
|
|
console.error(error)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
{error && <div style={{ color: 'red' }}>{error}</div>}
|
|
<form onSubmit={onSubmit}>
|
|
<input type="text" name="name" />
|
|
<button type="submit" disabled={isLoading}>
|
|
{isLoading ? 'Loading...' : 'Submit'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
## Displaying loading state
|
|
|
|
You can use React state to show a loading state when a form is submitting on the server:
|
|
|
|
```tsx filename="pages/index.tsx" switcher
|
|
import React, { useState, FormEvent } from 'react'
|
|
|
|
export default function Page() {
|
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
|
|
|
async function onSubmit(event: FormEvent<HTMLFormElement>) {
|
|
event.preventDefault()
|
|
setIsLoading(true) // Set loading to true when the request starts
|
|
|
|
try {
|
|
const formData = new FormData(event.currentTarget)
|
|
const response = await fetch('/api/submit', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
|
|
// Handle response if necessary
|
|
const data = await response.json()
|
|
// ...
|
|
} catch (error) {
|
|
// Handle error if necessary
|
|
console.error(error)
|
|
} finally {
|
|
setIsLoading(false) // Set loading to false when the request completes
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={onSubmit}>
|
|
<input type="text" name="name" />
|
|
<button type="submit" disabled={isLoading}>
|
|
{isLoading ? 'Loading...' : 'Submit'}
|
|
</button>
|
|
</form>
|
|
)
|
|
}
|
|
```
|
|
|
|
```jsx filename="pages/index.jsx" switcher
|
|
import React, { useState } from 'react'
|
|
|
|
export default function Page() {
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
async function onSubmit(event) {
|
|
event.preventDefault()
|
|
setIsLoading(true) // Set loading to true when the request starts
|
|
|
|
try {
|
|
const formData = new FormData(event.currentTarget)
|
|
const response = await fetch('/api/submit', {
|
|
method: 'POST',
|
|
body: formData,
|
|
})
|
|
|
|
// Handle response if necessary
|
|
const data = await response.json()
|
|
// ...
|
|
} catch (error) {
|
|
// Handle error if necessary
|
|
console.error(error)
|
|
} finally {
|
|
setIsLoading(false) // Set loading to false when the request completes
|
|
}
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={onSubmit}>
|
|
<input type="text" name="name" />
|
|
<button type="submit" disabled={isLoading}>
|
|
{isLoading ? 'Loading...' : 'Submit'}
|
|
</button>
|
|
</form>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Redirecting
|
|
|
|
If you would like to redirect the user to a different route after a mutation, you can [`redirect`](/docs/pages/building-your-application/routing/api-routes#response-helpers) to any absolute or relative URL:
|
|
|
|
```ts filename="pages/api/submit.ts" switcher
|
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
const id = await addPost()
|
|
res.redirect(307, `/post/${id}`)
|
|
}
|
|
```
|
|
|
|
```js filename="pages/api/submit.js" switcher
|
|
export default async function handler(req, res) {
|
|
const id = await addPost()
|
|
res.redirect(307, `/post/${id}`)
|
|
}
|
|
```
|
|
|
|
### Setting cookies
|
|
|
|
You can set cookies inside an API Route using the `setHeader` method on the response:
|
|
|
|
```ts filename="pages/api/cookie.ts" switcher
|
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
|
|
res.status(200).send('Cookie has been set.')
|
|
}
|
|
```
|
|
|
|
```js filename="pages/api/cookie.js" switcher
|
|
export default async function handler(req, res) {
|
|
res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
|
|
res.status(200).send('Cookie has been set.')
|
|
}
|
|
```
|
|
|
|
### Reading cookies
|
|
|
|
You can read cookies inside an API Route using the [`cookies`](/docs/pages/building-your-application/routing/api-routes#request-helpers) request helper:
|
|
|
|
```ts filename="pages/api/cookie.ts" switcher
|
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
const auth = req.cookies.authorization
|
|
// ...
|
|
}
|
|
```
|
|
|
|
```js filename="pages/api/cookie.js" switcher
|
|
export default async function handler(req, res) {
|
|
const auth = req.cookies.authorization
|
|
// ...
|
|
}
|
|
```
|
|
|
|
### Deleting cookies
|
|
|
|
You can delete cookies inside an API Route using the `setHeader` method on the response:
|
|
|
|
```ts filename="pages/api/cookie.ts" switcher
|
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
|
|
res.status(200).send('Cookie has been deleted.')
|
|
}
|
|
```
|
|
|
|
```js filename="pages/api/cookie.js" switcher
|
|
export default async function handler(req, res) {
|
|
res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
|
|
res.status(200).send('Cookie has been deleted.')
|
|
}
|
|
```
|