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!