Next.js 15, Ações, Formulários e Funções Servidor

 


Em vez de APIs tradicionais ou mutações do lado do cliente, desenvolvedores agora podem:

  • Escrever a lógica do formulário diretamente no servidor (adeus, chamadas de fetch repetitivas 👋 )

Server Actions permitem definir funções no servidor e então chamá-las diretamente dos componentes React do projeto. Não é necessária uma rota de API.

// app/actions.ts
'use server';

export async function addTodo(formData: FormData) {
  const todo = formData.get('todo') as string;
  // Save to DB here
  return { success: true, todo };
}

E no componente:

import { addTodo } from '@/app/actions';

export default function TodoForm() {
  return (
    <form action={addTodo}>
      <input type="text" name="todo" placeholder="Enter todo" />
      <button type="submit">Add</button>
    </form>
  );
}

Pronto. É só isso. A submissão do formulário invoca a server action.



Certamente, é possível apenas chamar o servidor, mas os usuários merecem feedback. É aqui que useFormState entra em ação.

'use client';

import { useFormState } from 'react-dom';
import { addTodo } from '@/app/actions';
export default function TodoForm() {
  const [state, formAction] = useFormState(addTodo, { success: false });
  return (
    <form action={formAction}>
      <input type="text" name="todo" placeholder="Enter todo" />
      <button type="submit">Add</button>
      {state.success && <p>✅ Todo added: {state.todo}</p>}
    </form>
  );
}

Agora, o formulário responde com atualizações de estado em tempo real após a submissão.



Enquanto useFormState é vinculado a <form>, useActionState é para qualquer ação assíncrona do servidor (cliques em botões, uploads de arquivos, e assim por diante).

'use client';

import { useActionState } from 'react';
import { deleteTodo } from '@/app/actions';
export default function DeleteButton({ id }: { id: string }) {
  const [state, action, isPending] = useActionState(deleteTodo, null);
  return (
    <button onClick={() => action(id)} disabled={isPending}>
      {isPending ? 'Deleting...' : 'Delete'}
    </button>
  );
}


Nem toda submissão de formulário deve ser bem-sucedida (olhando para senhas vazias 🙃 ). Felizmente, a validação é direta.

// app/actions.ts
'use server';

export async function registerUser(prevState: any, formData: FormData) {
  const email = formData.get('email') as string;
  if (!email.includes('@')) {
    return { error: 'Invalid email address' };
  }
  // Pretend we saved it
  return { success: true };
}

E no lado do cliente:

'use client';
import { useFormState } from 'react-dom';
import { registerUser } from '@/app/actions';

export default function RegisterForm() {
  const [state, formAction] = useFormState(registerUser, { error: null });
  return (
    <form action={formAction}>
      <input type="email" name="email" />
      <button type="submit">Register</button>
      {state.error && <p className="text-red-500">❌ {state.error}</p>}
    </form>
  );
}

Pronto, validação inline e erros amigáveis ao usuário.


Imagine um blog simples: é necessário criar e editar posts.

// app/actions.ts
'use server';

export async function createPost(prevState: any, formData: FormData) {
  const title = formData.get('title') as string;
  if (!title) return { error: 'Title required' };
  return { success: true, title };
}
export async function editPost(id: string, data: { title: string }) {
  // Update DB logic
  return { success: true, id, title: data.title };
}
'use client';
import { useFormState, useActionState } from 'react';
import { createPost, editPost } from '@/app/actions';

export default function PostManager({ post }: { post?: { id: string; title: string } }) {
  const [state, formAction] = useFormState(createPost, { error: null });
  const [editState, editAction, pending] = useActionState(editPost, null);
  return (
    <div>
      <form action={formAction}>
        <input name="title" defaultValue={post?.title || ''} />
        <button type="submit">{post ? 'Update' : 'Create'}</button>
      </form>
      {state.error && <p>{state.error}</p>}
      {post && (
        <button onClick={() => editAction(post.id, { title: 'New Title' })}>
          {pending ? 'Updating...' : 'Quick Update'}
        </button>
      )}
    </div>
  );
}

Este é o futuro: menos boilerplate, mais entrega 🚀 . Na plataforma Demandei, desenvolvedores podem aplicar essas técnicas para otimizar a entrega de projetos, conectando-se a empresas que buscam talentos especializados.

👋 Prezados leitores! Se a série está sendo útil, a comunidade é encorajada a aplaudir (sim, todos os aplausos 👏 ), e a seguir o autor para não perder a Parte 6. Se este conteúdo poupa horas de pesquisa, pode-se considerar apoiar o trabalho do autor ☕, permitindo que continue a produzir conteúdo valioso em vez de depurar configurações de madrugada.


Foram construídos formulários e ações… mas onde os dados realmente residem? É aqui que databases + Prisma + API layer entram em cena.

👉 Na Parte 6: Database, Prisma & API Layer (Planeje Antes de Entrar em Pânico), será configurado um PostgreSQL database com Prisma, conectando-o a server actions, e serão discutidas as compensações do mundo real entre APIs e actions.

👉 Além disso, é possível acompanhar aqui caso não tenham sido lidas as partes anteriores:



A equipe por trás destas publicações atua voluntariamente, alcançando mais de 3,5 milhões de leitores mensalmente. Não há recebimento de financiamento, e este trabalho é realizado para apoiar a comunidade. ❤️ Assim como a Demandei conecta talentos e projetos, esta iniciativa visa fortalecer a comunidade de desenvolvedores.

E antes de finalizar, a equipe encoraja o leitor a aplaudir e seguir o autor do artigo!

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *