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>
}

Gut zu wissen: Bei Formularen mit mehreren Feldern können Sie die entries()-Methode mit JavaScripts Object.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>
  )
}

Die Server-Funktion erhält userId als zusätzliches Argument:

'use server'

export async function updateUser(userId: string, formData: 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 und type="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
}

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>
  )
}

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>
  )
}

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:

'use client'

import { useFormStatus } from 'react-dom'

export function SubmitButton() {
  const { pending } = useFormStatus()

  return (
    <button disabled={pending} type="submit">
      Registrieren
    </button>
  )
}

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>
  )
}

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 der pending-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>
  )
}

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>
  )
}

Dies löst die Übermittlung des nächstgelegenen <form>-Vorfahren aus, wodurch die Server-Funktion aufgerufen wird.

On this page