Tutorial — Buscando dados
Nossa lista de tarefas precisa vir de algum lugar: um backend. Nesta página você
vai montar os providers no topo do app, criar um cliente HTTP tipado com
createApiClient, organizar as chaves de cache com createQueryKeys, e buscar e
alterar dados com useQuery e useMutation, tratando os estados de carregando e
erro.
Passo 1 — Os providers no topo
Para o React Query funcionar, o app precisa de um provider de cache no topo da
árvore. O <AppProviders> monta esse provider (e o tema, e o error boundary) de
uma vez. Ele já vem no App.tsx gerado:
// src/App.tsx
import { AppProviders, AppRouter } from "tempest-react-sdk";
import { routes } from "@/routes";
export function App() {
return (
<AppProviders errorBoundary={{ fallback: <p>Algo deu errado.</p> }}>
<AppRouter routes={routes} fallback={<p>Carregando…</p>} />
</AppProviders>
);
}
O <AppProviders> aninha de fora pra dentro: ErrorBoundary → QueryProvider →
ThemeProvider → I18nProvider → children. Query e Theme já vêm ligados com os
defaults do SDK — você não configura nada pra ter cache de dados.
Defaults do cache
Quando ligado por padrão, o QueryProvider usa: staleTime de 5 minutos,
gcTime de 30 minutos, retry: 1 e refetchOnWindowFocus: false. Para
ajustar, passe query={{ defaultOptions: { queries: { retry: 3 } } }}.
Passo 2 — O cliente HTTP com createApiClient
createApiClient cria um cliente fetch tipado que cuida de JSON, query params,
bearer token e tratamento de 401. Vamos centralizá-lo em src/lib/api.ts:
// src/lib/api.ts
import { createApiClient, createQueryKeys } from "tempest-react-sdk";
import { useAuth } from "@/stores/auth";
export const api = createApiClient({
baseURL: import.meta.env.VITE_API_URL,
getToken: () => useAuth.getState().token,
onUnauthorized: () => useAuth.getState().logout(),
});
Peça por peça:
baseURLvem do.env(VITE_API_URL) — a base de toda requisição.getTokené chamado a cada requisição; lendo o token viagetState(), cada chamada usa o token atual e manda o headerAuthorization: Bearer ....onUnauthorizeddispara em respostas 401 — aqui deslogamos o usuário.
O cliente expõe get, post, put, patch, delete (todos genéricos no tipo
de retorno) e upload. Cada um devolve o JSON já parseado e tipado.
Lembra do getToken com getState()?
O cliente HTTP roda fora do React, então lê a store de auth com
getState() — exatamente o padrão que você viu na página de
Estado. Sem hook, sem assinatura: só um snapshot do token na hora
da chamada. 💡
Passo 3 — Chaves de cache com createQueryKeys
O React Query identifica cada pedaço de cache por uma query key. Em vez de
espalhar arrays mágicos pelo código, centralize-as com createQueryKeys:
// src/lib/api.ts (continuação)
export const taskKeys = createQueryKeys("tasks", {
all: () => ["all"] as const,
byId: (id: string) => [id] as const,
});
// taskKeys.all() === ["tasks", "all"]
// taskKeys.byId("42") === ["tasks", "42"]
Cada chave é prefixada com o domínio ("tasks"), então não há colisão entre
domínios diferentes.
Passo 4 — Lendo dados com useQuery
Agora a lista de tarefas. useQuery vem do @tanstack/react-query (dependência
direta do SDK, instalada junto — você não precisa instalar nada à parte).
De onde vem o useQuery
Os hooks useQuery e useMutation são importados de "@tanstack/react-query",
não de "tempest-react-sdk". O SDK fornece o provider (QueryProvider,
via AppProviders), as chaves (createQueryKeys) e os presets de tempo;
os hooks você usa direto do React Query. Como ele é dependência direta do SDK,
já está instalado.
// src/pages/Home.tsx
import { useQuery } from "@tanstack/react-query";
import { api, taskKeys } from "@/lib/api";
interface Task {
id: string;
title: string;
done: boolean;
}
export function Home() {
const { data, isLoading, isError, error } = useQuery({
queryKey: taskKeys.all(),
queryFn: () => api.get<Task[]>("/tasks"),
});
if (isLoading) return <p>Carregando tarefas…</p>;
if (isError) return <p>Erro: {(error as Error).message}</p>;
return (
<ul>
{data?.map((task) => (
<li key={task.id}>
{task.done ? "✅" : "⬜"} {task.title}
</li>
))}
</ul>
);
}
Peça por peça:
queryKey: taskKeys.all()identifica este cache — outros componentes que usem a mesma chave compartilham o mesmo dado.queryFnfaz a requisição.api.get<Task[]>("/tasks")devolve umTask[]já tipado.isLoading/isError/errorsão os estados que o React Query mantém pra você — trate-os antes de renderizar os dados.
Passo 5 — Alterando dados com useMutation
Buscar é só metade. Para criar uma tarefa, use useMutation (também do
@tanstack/react-query). Depois do sucesso, invalidamos a query da lista para que
ela seja buscada de novo com a tarefa nova.
// src/components/AddTask.tsx
import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { api, taskKeys } from "@/lib/api";
interface Task {
id: string;
title: string;
done: boolean;
}
export function AddTask() {
const [title, setTitle] = useState("");
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newTitle: string) =>
api.post<Task>("/tasks", { body: { title: newTitle, done: false } }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: taskKeys.all() });
setTitle("");
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
mutation.mutate(title);
}}
>
<input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Nova tarefa" />
<button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? "Salvando…" : "Adicionar"}
</button>
{mutation.isError && <p>Erro ao salvar.</p>}
</form>
);
}
Peça por peça:
mutationFnrecebe o argumento demutation.mutate(...)e faz oPOST. Note o{ body: {...} }— ocreateApiClientserializa obodyem JSON pra você.onSuccessinvalidataskKeys.all(): o React Query re-busca a lista automaticamente, e a<Home>mostra a tarefa nova sem você recarregar a página.mutation.isPendingdesabilita o botão durante o envio;mutation.isErrormostra o erro.
Por que invalidar em vez de inserir na mão
Invalidar a query (invalidateQueries) deixa o servidor ser a fonte de
verdade: você re-busca a lista e ganha o id real, timestamps, qualquer campo
que o backend preencheu. É mais robusto do que tentar inserir o objeto otimista
na lista local. ✅
Recap
- O
<AppProviders>monta oQueryProvider(cache) ligado por padrão — sem config você já tem React Query funcionando. ✅ createApiClient({ baseURL, getToken, onUnauthorized })cria um clientefetchtipado:getTokeninjeta o bearer a cada chamada,onUnauthorizeddispara em 401. Lê a auth store comgetState().createQueryKeys("dominio", {...})centraliza as chaves de cache, prefixadas pelo domínio.useQuery(de@tanstack/react-query, dependência direta do SDK) busca dados e te dáisLoading/isError/data.useMutation+queryClient.invalidateQueries(...)altera dados e re-busca a lista — o servidor continua sendo a fonte de verdade.
➡️ Próxima página: Formulários — validação com zod e máscaras BR