Learn TanStack - Core Concepts & Main Architecture
Episode 2 of 24

Learn TanStack - Core Concepts & Main Architecture

This episode dissects the architecture behind TanStack: the query client and cache in Query, the column system and row model in Table, state management and loaders in Router, and how the virtualizer measures the viewport in Virtual.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Episode 1 explained what TanStack solves. Now we go deeper: how these libraries work behind the scenes. Understanding the architecture means you're not just memorizing APIs, but you know the reasoning behind every behavior — for example, why queries are cached, why columns are defined as data, and why the router separates state from components.

Episode 2 dissects four core architectures: the query client and cache in TanStack Query, the column system and row model in TanStack Table, state management and loaders in TanStack Router, and how the virtualizer measures the viewport in TanStack Virtual.

These concepts are the foundation. In episode 3 you'll touch real code, and every architectural detail learned here will be used all the way to episode 23.

TanStack Query Architecture Behind the Scenes

QueryClient, QueryCache, and QueryObserver

The core of TanStack Query is the QueryClient, which holds a QueryCache. Every useQuery call creates a Query in the cache, and the subscribing components are called QueryObservers. When several components use the same queryKey, they share a single Query — that's the basis of deduplication.

JSQueryClient dengan default global
import { QueryClient } from "@tanstack/react-query"
 
const queryClient = new QueryClient({
  defaultOptions: {
    queries: { staleTime: 30_000, retry: 1 },
  },
})

new QueryClient({ defaultOptions }) sets the default behavior for all queries. staleTime: 30_000 makes data considered stale after 30 seconds, and retry: 1 limits retries on error. Both are covered in detail in episode 4.

Query Workflow

When a component mounts, the observer checks the cache: if valid data exists, it's used directly without fetching. If there's none, or it's stale, the query is refetched in the background. When done, the result is written to the cache and all subscribed observers are notified. This pattern explains why navigating between pages feels instant — the data is already warm in the cache.

Provider Context

QueryClientProvider delivers the queryClient to the entire component tree through React context. Without this provider, useQuery doesn't know which cache to use and throws an error immediately.

TanStack Table Architecture

Table Core and Column Definitions

TanStack Table splits things in two: the core table that handles state and logic, and rendering that you fully control. Columns are defined as data through columnHelper, so columns can be dynamic, derived, or programmatically generated.

JSDefinisi kolom sebagai data
import { createColumnHelper } from "@tanstack/react-table"
 
const columnHelper = createColumnHelper()
 
const columns = [
  columnHelper.accessor("nama", { header: "Nama" }),
  columnHelper.accessor("tahun", { header: "Tahun" }),
]

columnHelper.accessor("nama", { header: "Nama" }) maps a column to the nama property of the data, with a header label. This columns array is what gets passed to useReactTable in episode 6.

Row Model

The row model is the engine that turns raw data into render-ready rows. getCoreRowModel is the base model; in episodes 6 and 9 you'll add sorting, filtering, grouping, and pagination models. Each model wraps the previous one in a pipeline.

TanStack Router Architecture

Route Tree and Router State

TanStack Router organizes the entire application as a route tree. Each route is an object with path, loader, and component. The router holds centralized state — including the current location, search params, and loader data — and notifies components when it changes.

JSMembangun route tree
import { createRootRoute, createRoute, createRouter } from "@tanstack/react-router"
 
const rootRoute = createRootRoute()
const routeTree = rootRoute.addChildren([])
const router = createRouter({ routeTree })

createRouter({ routeTree }) produces the router that gets passed to RouterProvider. The entire route tree is type-safe: wrong URLs are rejected by the compiler.

Loader and Navigation Lifecycle

When navigation happens, the router calls the loader of the destination route, waits for its data or shows a pending state, then renders the component. Components read the loader result through useLoaderData. This lifecycle lets the router know exactly when to show loading, success, or error.

TanStack Virtual Architecture

Virtualizer and Viewport Measurement

TanStack Virtual uses a Virtualizer to determine which items are visible within the viewport. The Virtualizer reads the scroll position through getScrollElement, calculates the range of visible items, and reports the total height of the scroll area.

JSVirtualizer untuk list raksasa
import { useVirtualizer } from "@tanstack/react-virtual"
 
const virtualizer = useVirtualizer({
  count: 10_000,
  getScrollElement: () => parentRef.current,
  estimateSize: () => 40,
})

count: 10_000 declares the number of items, and estimateSize: () => 40 is the initial height estimate for each item. Only the visible items are rendered — the rest are replaced by empty blocks of the same size.

Item Rendering

Each virtual item is given a start and size. The component positions items absolutely using a translateY transform of the start value. As a result, scrolling stays smooth even with datasets of millions of rows — full details are in episode 11.

Conclusion

Episode 2 dissected the architecture of the four core libraries: query client and cache in Query, core table and row model in Table, route tree and loader lifecycle in Router, and virtualizer and viewport in Virtual. All these architectures share one pattern: logic is separated from rendering.

Key takeaways:

  • QueryClient holds the QueryCache; observers subscribe to the same query.
  • TanStack Table separates core logic from the rendering you control.
  • Router builds a route tree and holds centralized state.
  • Loaders run on navigation and their results are read through useLoaderData.
  • The Virtualizer only renders items inside the viewport.
  • Separating logic from rendering is the thread running through all of TanStack.

In the next episode, episode 3, we'll start a project with TanStack — scaffolding a Vite React-TypeScript project, installing the five @tanstack/* libraries, the folder structure, and TypeScript and ESLint configuration. Get your terminal ready, because starting from episode 3 we write real code!