Episode ini menyatukan seluruh library: dashboard dengan TanStack Table dan Charts, koordinasi query state, table state, dan router state, complex UI flows dengan nested routing, serta data-driven UI dan composition.

Empat belas episode membangun kemampuan per library. Sekarang saatnya menyatukan semuanya: Query menyediakan data, Table menyajikannya, Charts memvisualisasikannya, dan Router mengatur navigasi. Episode 15 membangun pola aplikasi penuh yang menjadi cetak biru proyek kalian.
Episode 15 membahas dashboard dengan Table dan Charts, koordinasi query state, table state, dan router state, complex UI flows dengan nested routing, serta data-driven UI dan composition.
Di sinilah filosofi TanStack membayar lunas: karena tiap library headless dan independen, mengombinasikannya adalah soal menghubungkan state, bukan melawan API.
Dashboard membaca satu kumpulan data, lalu menampilkannya sebagai tabel dan chart. Sumber datanya satu query; keduanya tinggal mengkonsumsi cache yang sama:
import { Chart } from "@tanstack/react-charts"
import { useQuery } from "@tanstack/react-query"
function Dashboard() {
const { data } = useQuery({
queryKey: ["penjualan"],
queryFn: ambilPenjualan,
})
const chartData = [
{
id: "penjualan",
label: "Penjualan per Bulan",
data: data.points,
},
]
return (
<div>
<Chart options={{
data: chartData,
primaryAxis: { getValue: (p) => p.bulan },
secondaryAxes: [{ getValue: (p) => p.total }],
}} />
<TabelPenjualan rows={data.rows} />
</div>
)
}useQuery mengambil penjualan sekali. Data yang sama diubah bentuk untuk Chart dan diteruskan ke tabel. Karena cache sama, memodifikasi data di tabel langsung tercermin di chart — tanpa sinkronisasi manual.
Filter, halaman, dan pencarian tabel paling baik di-simpan di URL. Router menyediakan search params, dan query memakai search params sebagai bagian dari queryKey:
const filterRoute = createRoute({
getParentRoute: () => rootRoute,
path: "laporan",
validateSearch: (search) => ({
halaman: search.halaman ?? 1,
cari: search.cari ?? "",
}),
loader: async ({ context, search }) => {
return context.queryClient.ensureQueryData({
queryKey: ["laporan", search],
queryFn: () => ambilLaporan(search),
})
},
component: LaporanComponent,
})validateSearch mendefinisikan bentuk search params dengan default. Loader memakai search sebagai bagian dari queryKey, sehingga setiap kombinasi filter dan halaman punya cache sendiri. Tabel kemudian membaca search ini untuk mengisi filter dan pagination — URL menjadi single source of truth.
Nested routing memecah flow yang rumit menjadi route kecil yang masing-masing punya loader dan error handling sendiri:
const checkoutRoute = createRoute({
getParentRoute: () => rootRoute,
path: "checkout",
component: LayoutCheckout,
})
const alamatRoute = createRoute({
getParentRoute: () => checkoutRoute,
path: "alamat",
component: FormAlamat,
})
const pembayaranRoute = createRoute({
getParentRoute: () => checkoutRoute,
path: "pembayaran",
loader: ({ context }) =>
context.queryClient.ensureQueryData({ queryKey: ["metode"], queryFn: ambilMetodePembayaran }),
component: FormPembayaran,
})alamatRoute dan pembayaranRoute berinduk pada checkoutRoute. Layout checkout berisi stepper dan <Outlet />, sementara tiap langkah memuat datanya sendiri. Navigasi antar langkah mengubah URL dan memicu loader per langkah — alur yang panjang jadi terukur.
Pola terakhir: biarkan data menentukan struktur UI. Table membaca definisi kolom sebagai data, chart membaca series sebagai data, router membaca route tree sebagai data. Semuanya composition:
function HalamanTabel({ definisiKolom, rows }) {
const table = useReactTable({
data: rows,
columns: definisiKolom,
getCoreRowModel: getCoreRowModel(),
})
return <RenderTable table={table} />
}definisiKolom diteruskan sebagai prop dari data — bukan hard-coded di dalam komponen. Dengan cara ini, halaman yang berbeda bisa memakai komponen tabel yang sama dengan kolom berbeda. Ini esensi data-driven UI: logika dan tampilan dipisahkan, data menjadi penggerak keduanya.
Info
Saat memegang search params di URL, jangan lupa batasi nilainya di validateSearch. Ini mencegah nilai tak terduga masuk ke queryKey dan merusak cache.
Episode 15 menutup pola aplikasi penuh: dashboard yang menyatukan Query, Table, dan Charts, state terkoordinasi lewat search params di URL, flow kompleks dengan nested routing, dan komposisi data-driven yang membuat UI fleksibel.
Inti yang harus dibawa pulang:
Di episode 16 selanjutnya kita akan membahas testing dan quality assurance — unit testing hooks Query, testing navigasi Router, testing render tabel dan list virtual, serta integration testing dengan mock server. Aplikasi kalian akan mulai diuji dengan benar!