Capítulo 42 de 456
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.
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).// 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>
}
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>
)
}
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} />
}
requestSubmit() dispara o <form> ancestral mais próximo sem precisar de ref explícita ao form.<input type="hidden" name="userId" value={userId} />): valor fica visível no HTML renderizado e não é encoded; prefira .bind().auth() dentro dela, mesmo se só usada em página logada.<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.<form action={fn}> recebem FormData automaticamente; .bind() é o jeito seguro de anexar argumentos extras.useActionState cobre estado de erro/mensagem + pending juntos; useFormStatus é a alternativa quando o botão de submit precisa estar em componente separado do form.required, type="email") é UX; validação real (Zod/Valibot) precisa acontecer na Server Function, sempre.useOptimistic atualiza a UI antes da resposta do servidor; combine com formAction async que chama a Server Function em paralelo.formAction dentro do mesmo <form> permitem múltiplas Server Actions (ex.: rascunho vs. publicar) sem duplicar o form.<form action={exitPreview}> usando exatamente este padrão.