TanStack em 2026: O Ecossistema Unificado Que Está Dominando o React
Olá HaWkers, se você trabalha com React em 2026, provavelmente já ouviu falar do TanStack. O que começou como React Query se transformou em um ecossistema completo que está mudando a forma como construímos aplicações.
Vamos explorar cada parte desse ecossistema e como usar na prática.
O Ecossistema TanStack
Visão Geral em 2026
// O que o TanStack oferece hoje
const tanstackEcosystem = {
query: {
purpose: 'Server state management',
status: 'Maduro e estável',
adoption: 'Padrão de mercado'
},
router: {
purpose: 'Type-safe routing',
status: 'Alternativa séria ao React Router',
feature: 'File-based + type safety'
},
table: {
purpose: 'Tabelas e grids complexos',
status: 'Líder na categoria',
feature: 'Headless, extensível'
},
form: {
purpose: 'Gerenciamento de formulários',
status: 'Crescendo rapidamente',
feature: 'Type-safe, performático'
},
store: {
purpose: 'Client state management',
status: 'Alternativa leve ao Redux/Zustand',
feature: 'Signals-based'
},
db: {
purpose: 'Local-first database',
status: 'Novo em 2025-2026',
feature: 'Sync, offline, real-time'
},
start: {
purpose: 'Full-stack framework',
status: 'Beta avançado',
feature: 'SSR, file routing, API routes'
},
ai: {
purpose: 'AI/LLM integrations',
status: 'Experimental',
feature: 'Streaming, hooks para AI'
}
};
TanStack Query
O Coração do Ecossistema
// TanStack Query - Server State Management
import {
useQuery,
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
// Setup
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutos
gcTime: 1000 * 60 * 30, // 30 minutos (antes era cacheTime)
retry: 3,
refetchOnWindowFocus: true,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<MyApp />
</QueryClientProvider>
);
}
// Uso básico
interface User {
id: number;
name: string;
email: string;
}
function useUser(userId: number) {
return useQuery({
queryKey: ['user', userId],
queryFn: async (): Promise<User> => {
const res = await fetch(`/api/users/${userId}`);
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
},
});
}
function UserProfile({ userId }: { userId: number }) {
const { data: user, isLoading, error } = useUser(userId);
if (isLoading) return <Skeleton />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}Mutations e Optimistic Updates
// Mutations com optimistic updates
interface UpdateUserData {
name?: string;
email?: string;
}
function useUpdateUser(userId: number) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: UpdateUserData) => {
const res = await fetch(`/api/users/${userId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Failed to update');
return res.json();
},
// Optimistic update
onMutate: async (newData) => {
// Cancela queries em andamento
await queryClient.cancelQueries({ queryKey: ['user', userId] });
// Snapshot do valor anterior
const previousUser = queryClient.getQueryData<User>(['user', userId]);
// Otimisticamente atualiza
queryClient.setQueryData<User>(['user', userId], (old) => ({
...old!,
...newData,
}));
// Retorna contexto para rollback
return { previousUser };
},
// Se erro, rollback
onError: (err, newData, context) => {
if (context?.previousUser) {
queryClient.setQueryData(['user', userId], context.previousUser);
}
},
// Sempre refetch após mutation
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['user', userId] });
},
});
}
// Uso
function EditUserForm({ userId }: { userId: number }) {
const { data: user } = useUser(userId);
const updateUser = useUpdateUser(userId);
const [name, setName] = useState(user?.name ?? '');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
updateUser.mutate({ name });
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<button disabled={updateUser.isPending}>
{updateUser.isPending ? 'Salvando...' : 'Salvar'}
</button>
</form>
);
}
TanStack Router
Type-Safe Routing
// TanStack Router - Routing com type safety total
import {
createRootRoute,
createRoute,
createRouter,
RouterProvider,
Link,
Outlet,
} from '@tanstack/react-router';
// Define a root route
const rootRoute = createRootRoute({
component: () => (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/users">Users</Link>
<Link to="/users/$userId" params={{ userId: '1' }}>
User 1
</Link>
</nav>
<Outlet />
</div>
),
});
// Home route
const homeRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Home</h1>,
});
// Users list route
const usersRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users',
component: UsersPage,
// Loader para prefetch de dados
loader: async () => {
const users = await fetchUsers();
return { users };
},
});
// User detail route com parâmetro tipado
const userRoute = createRoute({
getParentRoute: () => usersRoute,
path: '$userId',
component: UserDetail,
// Validação de parâmetros
parseParams: (params) => ({
userId: parseInt(params.userId),
}),
stringifyParams: (params) => ({
userId: String(params.userId),
}),
// Loader com acesso aos params tipados
loader: async ({ params }) => {
const user = await fetchUser(params.userId);
return { user };
},
});
// Route tree
const routeTree = rootRoute.addChildren([
homeRoute,
usersRoute.addChildren([userRoute]),
]);
// Router instance
const router = createRouter({ routeTree });
// Type registration para type safety global
declare module '@tanstack/react-router' {
interface Register {
router: typeof router;
}
}
// App
function App() {
return <RouterProvider router={router} />;
}
// Componentes usando dados do loader
function UsersPage() {
const { users } = usersRoute.useLoaderData();
return (
<ul>
{users.map((user) => (
<li key={user.id}>
<Link to="/users/$userId" params={{ userId: user.id }}>
{user.name}
</Link>
</li>
))}
</ul>
);
}
function UserDetail() {
const { user } = userRoute.useLoaderData();
const { userId } = userRoute.useParams();
return (
<div>
<h2>{user.name}</h2>
<p>ID: {userId}</p>
</div>
);
}
TanStack Form
Formulários Type-Safe
// TanStack Form - Gerenciamento de formulários
import { useForm } from '@tanstack/react-form';
import { z } from 'zod';
// Schema de validação
const userSchema = z.object({
name: z.string().min(2, 'Nome deve ter pelo menos 2 caracteres'),
email: z.string().email('Email inválido'),
age: z.number().min(18, 'Deve ser maior de 18'),
role: z.enum(['admin', 'user', 'guest']),
});
type UserFormData = z.infer<typeof userSchema>;
function UserForm() {
const form = useForm<UserFormData>({
defaultValues: {
name: '',
email: '',
age: 18,
role: 'user',
},
onSubmit: async ({ value }) => {
// value é totalmente tipado
await saveUser(value);
},
validators: {
onChange: userSchema,
},
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<form.Field
name="name"
children={(field) => (
<div>
<label>Nome</label>
<input
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
{field.state.meta.errors.length > 0 && (
<span className="error">
{field.state.meta.errors.join(', ')}
</span>
)}
</div>
)}
/>
<form.Field
name="email"
children={(field) => (
<div>
<label>Email</label>
<input
type="email"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
/>
{field.state.meta.errors.length > 0 && (
<span className="error">
{field.state.meta.errors.join(', ')}
</span>
)}
</div>
)}
/>
<form.Field
name="age"
children={(field) => (
<div>
<label>Idade</label>
<input
type="number"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(parseInt(e.target.value))}
/>
</div>
)}
/>
<form.Field
name="role"
children={(field) => (
<div>
<label>Role</label>
<select
value={field.state.value}
onChange={(e) =>
field.handleChange(e.target.value as UserFormData['role'])
}
>
<option value="user">User</option>
<option value="admin">Admin</option>
<option value="guest">Guest</option>
</select>
</div>
)}
/>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit || isSubmitting}>
{isSubmitting ? 'Salvando...' : 'Salvar'}
</button>
)}
/>
</form>
);
}
TanStack Table
Tabelas Complexas
// TanStack Table - Tabelas headless
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
getFilteredRowModel,
getPaginationRowModel,
flexRender,
createColumnHelper,
SortingState,
} from '@tanstack/react-table';
interface User {
id: number;
name: string;
email: string;
role: string;
createdAt: Date;
}
const columnHelper = createColumnHelper<User>();
const columns = [
columnHelper.accessor('id', {
header: 'ID',
cell: (info) => info.getValue(),
}),
columnHelper.accessor('name', {
header: 'Nome',
cell: (info) => info.getValue(),
}),
columnHelper.accessor('email', {
header: 'Email',
cell: (info) => (
<a href={`mailto:${info.getValue()}`}>{info.getValue()}</a>
),
}),
columnHelper.accessor('role', {
header: 'Role',
cell: (info) => <Badge>{info.getValue()}</Badge>,
}),
columnHelper.accessor('createdAt', {
header: 'Criado em',
cell: (info) => info.getValue().toLocaleDateString(),
sortingFn: 'datetime',
}),
columnHelper.display({
id: 'actions',
header: 'Ações',
cell: (info) => (
<div>
<button onClick={() => editUser(info.row.original)}>Edit</button>
<button onClick={() => deleteUser(info.row.original.id)}>
Delete
</button>
</div>
),
}),
];
function UsersTable({ data }: { data: User[] }) {
const [sorting, setSorting] = useState<SortingState>([]);
const [globalFilter, setGlobalFilter] = useState('');
const table = useReactTable({
data,
columns,
state: {
sorting,
globalFilter,
},
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<div>
{/* Filtro global */}
<input
value={globalFilter}
onChange={(e) => setGlobalFilter(e.target.value)}
placeholder="Buscar..."
/>
{/* Tabela */}
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th
key={header.id}
onClick={header.column.getToggleSortingHandler()}
style={{ cursor: 'pointer' }}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
{header.column.getIsSorted() === 'asc' && ' 🔼'}
{header.column.getIsSorted() === 'desc' && ' 🔽'}
</th>
))}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</table>
{/* Paginação */}
<div>
<button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Anterior
</button>
<span>
Página {table.getState().pagination.pageIndex + 1} de{' '}
{table.getPageCount()}
</span>
<button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Próxima
</button>
</div>
</div>
);
}
TanStack Store
State Management Leve
// TanStack Store - Client state com signals
import { Store, useStore } from '@tanstack/store';
// Criar store
interface AppState {
user: {
id: number;
name: string;
} | null;
theme: 'light' | 'dark';
notifications: string[];
}
const store = new Store<AppState>({
user: null,
theme: 'light',
notifications: [],
});
// Actions como funções
function login(user: AppState['user']) {
store.setState((state) => ({
...state,
user,
}));
}
function logout() {
store.setState((state) => ({
...state,
user: null,
}));
}
function toggleTheme() {
store.setState((state) => ({
...state,
theme: state.theme === 'light' ? 'dark' : 'light',
}));
}
function addNotification(message: string) {
store.setState((state) => ({
...state,
notifications: [...state.notifications, message],
}));
}
// Uso em componentes
function UserMenu() {
// Seleciona apenas o que precisa
const user = useStore(store, (state) => state.user);
if (!user) {
return <button onClick={() => login({ id: 1, name: 'John' })}>Login</button>;
}
return (
<div>
<span>Olá, {user.name}</span>
<button onClick={logout}>Logout</button>
</div>
);
}
function ThemeToggle() {
const theme = useStore(store, (state) => state.theme);
return (
<button onClick={toggleTheme}>
Tema: {theme === 'light' ? '☀️' : '🌙'}
</button>
);
}
function NotificationBell() {
const count = useStore(store, (state) => state.notifications.length);
return (
<button>
🔔 {count > 0 && <span className="badge">{count}</span>}
</button>
);
}
Integrando o Ecossistema
Exemplo Completo
// App completa usando múltiplos TanStack
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { RouterProvider, createRouter } from '@tanstack/react-router';
import { Store } from '@tanstack/store';
// Setup global
const queryClient = new QueryClient();
const appStore = new Store({ theme: 'light' as const });
// Router com integração Query
const rootRoute = createRootRoute({
component: () => {
const theme = useStore(appStore, (s) => s.theme);
return (
<div className={theme}>
<Header />
<Outlet />
</div>
);
},
});
const usersRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/users',
// Prefetch com Query
loader: async ({ context }) => {
await context.queryClient.ensureQueryData({
queryKey: ['users'],
queryFn: fetchUsers,
});
},
component: () => {
const { data: users } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
return <UsersTable data={users ?? []} />;
},
});
// Router com context
const router = createRouter({
routeTree: rootRoute.addChildren([usersRoute]),
context: {
queryClient,
},
});
// App
function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
</QueryClientProvider>
);
}Conclusão
O TanStack em 2026 não é mais "só o React Query" - é um ecossistema completo e coeso para construir aplicações React modernas. A vantagem principal é a consistência: APIs similares, type safety em tudo, e integração perfeita entre as partes.
Quando usar cada parte:
- Query: Sempre que tiver dados do servidor
- Router: Se quer type safety no routing (e quer sair do React Router)
- Table: Tabelas complexas com sort/filter/pagination
- Form: Formulários médios a complexos
- Store: Estado global simples (alternativa ao Zustand)
O ecossistema continua crescendo, e TanStack Start promete ser uma alternativa interessante ao Next.js para quem quer máximo controle com DX excelente.
Para entender mais sobre o estado atual do React, confira: React Compiler em 2026.

