A frontend developer told me: 'I was using useEffect + useState for API calls, managing loading states, caching, pagination, and refetching manually. 200 lines of code per API call.' TanStack Query replaced all of that with 5 lines.
What TanStack Offers
TanStack (open source, free):
- TanStack Query — async state management (data fetching)
- TanStack Table — headless, powerful data tables
- TanStack Router — type-safe routing
- TanStack Form — type-safe forms with validation
- TanStack Virtual — virtualize massive lists
- Works with React, Vue, Svelte, Solid, Angular
TanStack Query (Data Fetching)
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Fetch data (with caching, refetching, loading states)
function Posts() {
const { data, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: () => fetch('/api/posts').then(r => r.json()),
staleTime: 5 * 60 * 1000, // Cache for 5 min
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return data.map(post => <PostCard key={post.id} post={post} />);
}
// Mutations (create/update/delete)
function CreatePost() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newPost) => fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(newPost)
}).then(r => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] });
}
});
return (
<button onClick={() => mutation.mutate({ title: 'New Post' })}>
{mutation.isPending ? 'Creating...' : 'Create Post'}
</button>
);
}
TanStack Table (Data Tables)
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, flexRender } from '@tanstack/react-table';
function DataTable({ data }) {
const columns = [
{ accessorKey: 'name', header: 'Name', enableSorting: true },
{ accessorKey: 'email', header: 'Email' },
{ accessorKey: 'role', header: 'Role', filterFn: 'equals' },
{
accessorKey: 'createdAt',
header: 'Joined',
cell: ({ getValue }) => new Date(getValue()).toLocaleDateString()
}
];
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel()
});
return (
<table>
<thead>
{table.getHeaderGroups().map(hg => (
<tr key={hg.id}>
{hg.headers.map(h => (
<th key={h.id} onClick={h.column.getToggleSortingHandler()}>
{flexRender(h.column.columnDef.header, h.getContext())}
{h.column.getIsSorted() === 'asc' ? ' ↑' : h.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>
);
}
TanStack Virtual (Virtualize 1M+ Rows)
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef(null);
const virtualizer = useVirtualizer({
count: items.length, // Can be 1,000,000+
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // Row height
});
return (
<div ref={parentRef} style={{ height: 600, overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: virtualRow.start,
height: virtualRow.size,
width: '100%'
}}
>
{items[virtualRow.index].name}
</div>
))}
</div>
</div>
);
}
Infinite Scroll
import { useInfiniteQuery } from '@tanstack/react-query';
function InfiniteList() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ['items'],
queryFn: ({ pageParam = 1 }) => fetch(`/api/items?page=${pageParam}`).then(r => r.json()),
getNextPageParam: (lastPage) => lastPage.nextPage ?? undefined
});
return (
<div>
{data?.pages.flatMap(page => page.items).map(item => (
<ItemCard key={item.id} item={item} />
))}
{hasNextPage && (
<button onClick={() => fetchNextPage()} disabled={isFetchingNextPage}>
{isFetchingNextPage ? 'Loading...' : 'Load More'}
</button>
)}
</div>
);
}
Need data for your tables? Check out my web scraping actors on Apify — structured data from any website.
Need help building data-heavy apps? Email me at spinov001@gmail.com.
Top comments (0)