Capítulo 42 de 456

Forms

Core Idea

Como construir formulários em Next.js usando Server Actions ligadas ao atributo action do <form>, cobrindo argumentos extras, validação, estados de pending, updates otimistas e submissão programática.

Key Concepts

  • action={serverFunction}: React estende <form> para invocar Server Functions; o FormData da submissão é passado automaticamente como argumento.
  • FormData.get(key): extrai campo individual; Object.fromEntries(formData) extrai todos de uma vez (mas inclui propriedades extras prefixadas $ACTION_).
  • .bind(null, arg): anexa argumento adicional (ex.: userId) a uma Server Function antes de usá-la como action; funciona em Server e Client Components e preserva progressive enhancement.
  • useActionState(action, initialState): hook React que retorna [state, formAction, pending]; muda a assinatura da Server Function para receber prevState/initialState como primeiro argumento.
  • useFormStatus(): hook react-dom que expõe pending (e em React 19 também data, method, action) para um componente filho do <form> mostrar estado de carregamento sem precisar de prop drilling.
  • useOptimistic(state, updateFn): atualiza a UI otimisticamente antes da Server Function responder.
  • useOffline (experimental): mantém a Server Action pendente durante queda de conexão e completa quando a rede volta, sem perder a submissão.
  • formAction prop em elementos aninhados: <button>, <input type="submit">, <input type="image"> podem apontar para uma Server Action diferente da do <form> pai, útil para "salvar rascunho" vs. "publicar".
  • requestSubmit(): método nativo de HTMLFormElement para disparar submissão programaticamente (ex.: atalho ⌘+Enter).

Code Examples

// app/invoices/page.tsx — Server Action inline com auth check
import { auth } from '@/lib/auth'

export default function Page() {
  async function createInvoice(formData: FormData) {
    'use server'
    const session = await auth()
    if (!session?.user) throw new Error('Unauthorized')

    const rawFormData = {
      customerId: formData.get('customerId'),
      amount: formData.get('amount'),
      status: formData.get('status'),
    }
    // mutate data / revalidate cache
  }

  return <form action={createInvoice}>...</form>
}
  • O que demonstra: Server Action inline recebendo FormData automaticamente e reautenticando antes de mutar, seguindo a exigência do guia de Data Security.
// app/ui/signup.tsx — validação + pending state com useActionState
'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">Email</label>
      <input type="text" id="email" name="email" required />
      <p aria-live="polite">{state?.message}</p>
      <button disabled={pending}>Sign up</button>
    </form>
  )
}
  • O que demonstra: useActionState liga estado de erro/mensagem e pending ao mesmo hook, evitando gerenciar isso manualmente.
// app/entry.tsx — submissão programática via atalho de teclado
'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 <textarea name="entry" rows={20} required onKeyDown={handleKeyDown} />
}
  • O que demonstra: requestSubmit() dispara o <form> ancestral mais próximo sem precisar de ref explícita ao form.

Anti-patterns

  • Confiar em hidden input pra dado sensível (<input type="hidden" name="userId" value={userId} />): valor fica visível no HTML renderizado e não é encoded; prefira .bind().
  • Assumir que a página autenticada protege a Server Action: cada action é endpoint próprio; sempre validar auth() dentro dela, mesmo se só usada em página logada.
  • Usar <Link> pra ações que devem mudar estado: Next.js faz prefetch de <Link> por padrão; use <form method="GET"> quando GET precisa dessa garantia de não disparar cedo.

Key Takeaways

  1. Server Functions em <form action={fn}> recebem FormData automaticamente; .bind() é o jeito seguro de anexar argumentos extras.
  2. useActionState cobre estado de erro/mensagem + pending juntos; useFormStatus é a alternativa quando o botão de submit precisa estar em componente separado do form.
  3. Validação client (required, type="email") é UX; validação real (Zod/Valibot) precisa acontecer na Server Function, sempre.
  4. useOptimistic atualiza a UI antes da resposta do servidor; combine com formAction async que chama a Server Function em paralelo.
  5. Elementos com formAction dentro do mesmo <form> permitem múltiplas Server Actions (ex.: rascunho vs. publicar) sem duplicar o form.

Connects To

  • Data Security (ch037): toda Server Action usada em formulário deve seguir as regras de reautorização e validação de input descritas lá.
  • Draft Mode (ch040): o exit flow de Draft Mode é um <form action={exitPreview}> usando exatamente este padrão.
  • Server Actions and Mutations (guide relacionado): cobre comportamento de Server Action além de forms (single-roundtrip, dispatch sequencial, segurança, cache).