//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Defina funções assíncronas como ações de store. Use set para gerenciar estados de carregamento e erro junto com a operação assíncrona. Nenhuma middleware especial é necessária.
import { create } from "zustand";
interface User {
id: string;
name: string;
email: string;
}
interface UserStore {
users: User[];
isLoading: boolean;
error: string | null;
fetchUsers: () => Promise<void>;
}
export const useUserStore = create<UserStore>((set) => ({
users: [],
isLoading: false,
error: null,
fetchUsers: async () => {
set({ isLoading: true, error: null });
try {
const res = await fetch("/api/users");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const users = await res.json();
set({ users, isLoading: false });
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
}
},
}));// stores/product-store.ts
import { create } from "zustand";
interface Product {
id: string;
name: string;
price: number;
stock: number;
}
interface ProductStore {
products: Product[];
selectedProduct: Product | null;
isLoading: boolean;
error: string | null;
fetchProducts: () => Promise<void>;
fetchProduct: (id: string) => Promise<void>;
createProduct: (input: Omit<Product, "id">) => Promise<Product>;
updateStock: (id: string, delta: number) => Promise<void>;
}
export const useProductStore = create<ProductStore>((set, get) => ({
products: [],
selectedProduct: null,
isLoading: false,
error: null,
fetchProducts: async () => {
set({ isLoading: true, error: null });
try {
const res = await fetch("/api/products");
const products = await res.json();
set({ products, isLoading: false });
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
}
},
fetchProduct: async (id) => {
set({ isLoading: true, error: null });
try {
const res = await fetch(`/api/products/${id}`);
const product = await res.json();
set({ selectedProduct: product, isLoading: false });
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
}
},
createProduct: async (input) => {
set({ isLoading: true, error: null });
try {
const res = await fetch("/api/products", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
});
const newProduct = await res.json();
set((state) => ({
products: [...state.products, newProduct],
isLoading: false,
}));
return newProduct;
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
throw err;
}
},
updateStock: async (id, delta) => {
// Atualização otimista
const previousProducts = get().products;
set((state) => ({
products: state.products.map((p) =>
p.id === id ? { ...p, stock: p.stock + delta } : p
),
}));
try {
await fetch(`/api/products/${id}/stock`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ delta }),
});
} catch (err) {
// Reversão em caso de falha
set({ products: previousProducts, error: (err as Error).message });
}
},
}));// components/product-list.tsx
"use client";
import { useEffect } from "react";
import { useProductStore } from "@/stores/product-store";
export function ProductList() {
const products = useProductStore((s) => s.products);
const isLoading = useProductStore((s) => s.isLoading);
const error = useProductStore((s) => s.error);
const fetchProducts = useProductStore((s) => s.fetchProducts);
useEffect(() => {
fetchProducts();
}, [fetchProducts]);
if (isLoading) return <div>Carregando produtos...</div>;
if (error) return <div>Erro: {error}</div>;
return (
<div>
{products.map((p) => (
<div key={p.id}>
<h3>{p.name}</h3>
<p>${p.price} - {p.stock} em estoque</p>
</div>
))}
</div>
);
}set pode ser chamado múltiplas vezes em uma ação assíncrona: uma para definir o carregamento, outra para definir os dados ou o erro.set dispara uma atualização de estado síncrona. O React 18+ agrupa renders, então chamadas sequenciais rápidas de set podem ser agrupadas.get() para capturar o estado atual antes da operação assíncrona, e depois revertem em caso de falha.Helper genérico de ação assíncrona:
function asyncAction<T>(
set: any,
fn: () => Promise<T>,
onSuccess: (data: T) => Partial<any>
) {
set({ isLoading: true, error: null });
return fn()
.then((data) => {
set({ ...onSuccess(data), isLoading: false });
return data;
})
.catch((err) => {
set({ error: (err as Error).message, isLoading: false });
throw err;
});
}
// Uso
const useStore = create((set) => ({
items: [],
isLoading: false,
error: null,
fetchItems: () =>
asyncAction(set, () => fetch("/api/items").then((r) => r.json()), (items) => ({ items })),
}));Estados de carregamento por ação:
interface Store {
data: Record<string, unknown>;
loading: Record<string, boolean>;
setLoading: (key: string, value: boolean) => void;
}
const useStore = create<Store>((set) => ({
data: {},
loading: {},
setLoading: (key, value) =>
set((s) => ({ loading: { ...s.loading, [key]: value } })),
}));Controlador de abortagem para cancelamento:
const useStore = create((set) => {
let controller: AbortController | null = null;
return {
data: null,
fetchData: async () => {
controller?.abort();
controller = new AbortController();
try {
const res = await fetch("/api/data", { signal: controller.signal });
set({ data: await res.json() });
} catch (err) {
if ((err as Error).name !== "AbortError") {
set({ error: (err as Error).message });
}
}
},
};
});Promise<void> ou Promise<T> e devem ser tipificadas de acordo.(err as Error).message é comum, já que blocos catch recebem unknown.interface AsyncState {
isLoading: boolean;
error: string | null;
}
interface WithAsync<T> extends AsyncState {
data: T | null;
fetch: () => Promise<void>;
}isLoading compartilhado entre múltiplas ações assíncronas pode conflitar. Se fetchProducts e fetchProduct usarem o mesmo isLoading, um pode sobrescrever o outro.set ainda são disparadas. Use controladores de abortagem ou ignore respostas obsoletas.set dentro de um try/catch com código assíncrono não reverte automaticamente em caso de erro. Você precisa lidar com a lógica de reversão manualmente.set ainda atualizará o store (sem erro, mas o efeito do componente antigo foi limpo).| Abordagem | Prós | Contras |
|---|---|---|
| Ações assíncronas inline | Simples, sem middleware | Gerenciamento manual de estado de carregamento/erro |
| SWR ou React Query para fetching | Cache automático, dedup, retry | Separado do store Zustand |
| Redux Toolkit createAsyncThunk | Ciclo de vida estruturado (pending, fulfilled, rejected) | Ecossistema Redux necessário |
| Middleware assíncrono customizado | Reutilizável entre ações | Complexidade adicionada |
async que chama set em diferentes pontos (carregamento, sucesso, erro).fetchUsers: async () => {
set({ isLoading: true, error: null });
try {
const res = await fetch("/api/users");
const users = await res.json();
set({ users, isLoading: false });
} catch (err) {
set({ error: (err as Error).message, isLoading: false });
}
},get() para capturar o estado atual antes da operação assíncrona.set.set com o snapshot.Promise<T>.await no resultado, por exemplo, para navegar após uma criação bem-sucedida.fetchProducts e fetchProduct ambos definem isLoading, um pode sobrescrever o estado de carregamento do outro.loading: Record<string, boolean>) para evitar isso.AbortController para cancelar a requisição anterior em andamento.AbortError no bloco catch e ignore-o.controller?.abort();
controller = new AbortController();
const res = await fetch(url, { signal: controller.signal });set ainda atualiza o store -- stores Zustand vivem fora do React.interface UserStore {
users: User[];
isLoading: boolean;
error: string | null;
fetchUsers: () => Promise<void>;
createUser: (input: Omit<User, "id">) => Promise<User>;
}Promise<void> para ações "fire-and-forget" e Promise<T> ao retornar dados.catch recebem unknown em TypeScript.Error explicitamente: (err as Error).message.set({ isLoading: true }) / try / catch / set({ isLoading: false }) em uma função reutilizável.Revisado por Chris St. John·Última atualização: 10 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥