Formulare mit Server Actions erstellen
React Server Actions sind Server-Funktionen, die auf dem Server ausgeführt werden. Sie können in Server- und Client-Komponenten aufgerufen werden, um Formularübermittlungen zu verarbeiten. Diese Anleitung zeigt Ihnen, wie Sie in Next.js Formulare mit Server Actions erstellen.
Funktionsweise
React erweitert das HTML-<form>
-Element, um Server Actions mit dem action
-Attribut aufrufen zu können.
Bei Verwendung in einem Formular erhält die Funktion automatisch das FormData
-Objekt. Sie können die Daten dann mit den nativen FormData
-Methoden extrahieren:
export default function Page() {
async function createInvoice(formData: FormData) {
'use server'
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// Daten mutieren
// Cache neu validieren
}
return <form action={createInvoice}>...</form>
}
export default function Page() {
async function createInvoice(formData) {
'use server'
const rawFormData = {
customerId: formData.get('customerId'),
amount: formData.get('amount'),
status: formData.get('status'),
}
// Daten mutieren
// Cache neu validieren
}
return <form action={createInvoice}>...</form>
}
Gut zu wissen: Bei Formularen mit mehreren Feldern können Sie die
entries()
-Methode mit JavaScriptsObject.fromEntries()
verwenden. Beispiel:const rawFormData = Object.fromEntries(formData)
.
Zusätzliche Argumente übergeben
Neben Formularfeldern können Sie mit der JavaScript-bind
-Methode zusätzliche Argumente an eine Server-Funktion übergeben. Beispielsweise, um das Argument userId
an die updateUser
-Server-Funktion zu übergeben:
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }: { userId: string }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Benutzernamen aktualisieren</button>
</form>
)
}
'use client'
import { updateUser } from './actions'
export function UserProfile({ userId }) {
const updateUserWithId = updateUser.bind(null, userId)
return (
<form action={updateUserWithId}>
<input type="text" name="name" />
<button type="submit">Benutzernamen aktualisieren</button>
</form>
)
}
Die Server-Funktion erhält userId
als zusätzliches Argument:
'use server'
export async function updateUser(userId: string, formData: FormData) {}
'use server'
export async function updateUser(userId, formData) {}
Gut zu wissen:
- Eine Alternative ist die Übergabe von Argumenten als versteckte Eingabefelder im Formular (z.B.
<input type="hidden" name="userId" value={userId} />
). Der Wert ist jedoch Teil des gerenderten HTML und wird nicht kodiert.bind
funktioniert sowohl in Server- als auch Client-Komponenten und unterstützt progressive Verbesserung.
Formularvalidierung
Formulare können client- oder serverseitig validiert werden.
- Für clientseitige Validierung können Sie HTML-Attribute wie
required
undtype="email"
für grundlegende Validierung verwenden. - Für serverseitige Validierung können Sie eine Bibliothek wie zod verwenden. Beispiel:
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Ungültige E-Mail',
}),
})
export default async function createUser(formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// Frühzeitig zurückkehren, wenn die Formulardaten ungültig sind
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Daten mutieren
}
'use server'
import { z } from 'zod'
const schema = z.object({
email: z.string({
invalid_type_error: 'Ungültige E-Mail',
}),
})
export default async function createsUser(formData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// Frühzeitig zurückkehren, wenn die Formulardaten ungültig sind
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
// Daten mutieren
}
Validierungsfehler
Um Validierungsfehler oder -meldungen anzuzeigen, wandeln Sie die Komponente, die das <form>
definiert, in eine Client-Komponente um und verwenden Sie Reacts useActionState
.
Bei Verwendung von useActionState
ändert sich die Signatur der Server-Funktion, um einen neuen prevState
- oder initialState
-Parameter als erstes Argument zu erhalten.
'use server'
import { z } from 'zod'
export async function createUser(initialState: any, formData: FormData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}
'use server'
import { z } from 'zod'
// ...
export async function createUser(initialState, formData) {
const validatedFields = schema.safeParse({
email: formData.get('email'),
})
// ...
}
Sie können dann die Fehlermeldung basierend auf dem state
-Objekt bedingt rendern.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">E-Mail</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Registrieren</button>
</form>
)
}
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
const initialState = {
message: '',
}
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
<label htmlFor="email">E-Mail</label>
<input type="text" id="email" name="email" required />
{/* ... */}
<p aria-live="polite">{state?.message}</p>
<button disabled={pending}>Registrieren</button>
</form>
)
}
Ausstehende Zustände
Der useActionState
-Hook stellt ein pending
-Boolean bereit, das verwendet werden kann, um einen Ladeindikator anzuzeigen oder den Submit-Button zu deaktivieren, während die Action ausgeführt wird.
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
{/* Andere Formularelemente */}
<button disabled={pending}>Registrieren</button>
</form>
)
}
'use client'
import { useActionState } from 'react'
import { createUser } from '@/app/actions'
export function Signup() {
const [state, formAction, pending] = useActionState(createUser, initialState)
return (
<form action={formAction}>
{/* Andere Formularelemente */}
<button disabled={pending}>Registrieren</button>
</form>
)
}
Alternativ können Sie den useFormStatus
-Hook verwenden, um einen Ladeindikator anzuzeigen. Bei Verwendung dieses Hooks müssen Sie eine separate Komponente für den Ladeindikator erstellen. Beispiel, um den Button während der Ausführung zu deaktivieren:
Sie können dann die SubmitButton
-Komponente in das Formular einfügen:
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Andere Formularelemente */}
<SubmitButton />
</form>
)
}
import { SubmitButton } from './button'
import { createUser } from '@/app/actions'
export function Signup() {
return (
<form action={createUser}>
{/* Andere Formularelemente */}
<SubmitButton />
</form>
)
}
Gut zu wissen: In React 19 enthält
useFormStatus
zusätzliche Schlüssel im zurückgegebenen Objekt, wie data, method und action. Wenn Sie nicht React 19 verwenden, ist nur derpending
-Schlüssel verfügbar.
Optimistische Updates
Sie können den React-useOptimistic
-Hook verwenden, um die UI optimistisch zu aktualisieren, bevor die Server-Funktion die Ausführung beendet, anstatt auf die Antwort zu warten:
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
type Message = {
message: string
}
export function Thread({ messages }: { messages: Message[] }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic<
Message[],
string
>(messages, (state, newMessage) => [...state, { message: newMessage }])
const formAction = async (formData: FormData) => {
const message = formData.get('message') as string
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m, i) => (
<div key={i}>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Senden</button>
</form>
</div>
)
}
'use client'
import { useOptimistic } from 'react'
import { send } from './actions'
export function Thread({ messages }) {
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [...state, { message: newMessage }]
)
const formAction = async (formData) => {
const message = formData.get('message')
addOptimisticMessage(message)
await send(message)
}
return (
<div>
{optimisticMessages.map((m) => (
<div>{m.message}</div>
))}
<form action={formAction}>
<input type="text" name="message" />
<button type="submit">Senden</button>
</form>
</div>
)
}
Verschachtelte Formularelemente
Sie können Server Actions in Elementen aufrufen, die in <form>
verschachtelt sind, wie <button>
, <input type="submit">
und <input type="image">
. Diese Elemente akzeptieren das formAction
-Prop oder Event-Handler.
Dies ist nützlich, wenn Sie mehrere Server Actions innerhalb eines Formulars aufrufen möchten. Beispielsweise können Sie einen spezifischen <button>
-Element für das Speichern eines Post-Entwurfs zusätzlich zum Veröffentlichen erstellen. Weitere Informationen finden Sie in den React <form>
-Dokumenten.
Programmgesteuerte Formularübermittlung
Sie können eine Formularübermittlung programmgesteuert mit der requestSubmit()
-Methode auslösen. Beispielsweise, wenn der Benutzer ein Formular mit der Tastenkombination ⌘
+ Enter
absendet, können Sie auf das onKeyDown
-Event hören:
'use client'
export function Entry() {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
'use client'
export function Entry() {
const handleKeyDown = (e) => {
if (
(e.ctrlKey || e.metaKey) &&
(e.key === 'Enter' || e.key === 'NumpadEnter')
) {
e.preventDefault()
e.currentTarget.form?.requestSubmit()
}
}
return (
<div>
<textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
</div>
)
}
Dies löst die Übermittlung des nächstgelegenen <form>
-Vorfahren aus, wodurch die Server-Funktion aufgerufen wird.