Belajar TanStack - Router Advanced & Data Management
Episode 10 of 24

Belajar TanStack - Router Advanced & Data Management

Episode ini mendalami TanStack Router: deferred data loading dengan Await, error boundaries dan notFound component, route-based mutations dengan invalidasi, serta full-stack routing patterns yang memakai loader data secara type-safe.

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

Pendahuluan

Episode 7 memperkenalkan loader dasar. Aplikasi nyata jarang serumit itu: kadang ada bagian halaman yang harus menunggu data lambat, kadang data tidak ditemukan, kadang mutasi terjadi setelah navigasi. Episode 10 menjawab semua skenario ini.

Episode 10 membahas deferred data loading, error boundaries dan notFound, route-based mutations, serta full-stack routing patterns. Setiap teknik membuat router lebih tangguh dalam mengelola data yang berat dan tak terduga.

Pada akhir episode, route kalian akan tahu kapan harus menunggu, kapan harus menampilkan sebagian, kapan menampilkan error, dan kapan menampilkan halaman tidak ditemukan — tanpa mengorbankan type safety.

Deferred Data Loading

Menampilkan Data Cepat Dulu, Lambat Menyusul

Loader bisa mengembalikan promise untuk data yang lambat, dan halaman dirender dulu dengan Await. Bagian cepat tampil segera, bagian lambat muncul belakangan:

JSDeferred data loading dengan Await
import { Await } from "@tanstack/react-router"
 
const detailRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "detail/$id",
  loader: ({ params }) => ({
    pokok: ambilDetail(params.id),
    rekomendasi: ambilRekomendasi(params.id),
  }),
  component: DetailComponent,
})
 
function DetailComponent() {
  const { pokok, rekomendasi } = useLoaderData({ from: detailRoute.id })
  return (
    <DetailPokok data={pokok} />
    <Await promise={rekomendasi}>
      {(data) => <DaftarRekomendasi data={data} />}
    </Await>
  )
}

useLoaderData mengembalikan objek dari loader. rekomendasi adalah promise, dan <Await promise={rekomendasi}> me-render komponen anak setelah promise ter-resolve. Bagian pokok langsung tampil tanpa menunggu rekomendasi — persis pola yang dipakai halaman detail produk di production.

Error Boundaries dan NotFound

Menangani Error per Route

Router menyediakan errorComponent untuk setiap route, sehingga error loader tidak merusak seluruh aplikasi:

JSError dan notFound component
const detailRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "detail/$id",
  loader: async ({ params }) => {
    const data = await ambilDetail(params.id)
    if (!data) throw new NotFoundError()
    return data
  },
  errorComponent: ({ error }) => <p>Terjadi error: {error.message}</p>,
  notFoundComponent: () => <p>Detail tidak ditemukan</p>,
  component: DetailComponent,
})

new NotFoundError() adalah error khusus yang diterjemahkan router menjadi halaman notFoundComponent, sementara error lain jatuh ke errorComponent. Keduanya bisa dipasang di level root sebagai fallback global atau per-route untuk kontrol yang presisi.

Route-based Mutations

Mengubah Data Setelah Navigasi

Router dan Query bekerja sama: mutasi dijalankan di dalam route, lalu router di-invalidate agar loader memuat ulang data. Untuk itu, queryClient disuntikkan lewat router context:

JSMutasi dengan invalidasi router
import { useMutation } from "@tanstack/react-query"
import { useRouter, useRouterContext } from "@tanstack/react-router"
 
function TambahBarang() {
  const router = useRouter()
  const { queryClient } = useRouterContext()
 
  const mutation = useMutation({
    mutationFn: tambahBarang,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ["barang"] })
      router.invalidate()
    },
  })
}

useRouterContext() mengambil queryClient yang disuntikkan saat router dibuat. Setelah mutasi sukses, queryClient.invalidateQueries menyegarkan query terkait dan router.invalidate() menjalankan ulang loader route yang bergantung pada data itu — semuanya dalam satu alur.

Full-stack Routing Patterns

Loader sebagai Satu-satunya Sumber Data

Pola full-stack terbaik: loader adalah satu-satunya tempat data di-fetch, dan komponen hanya membaca useLoaderData. Ini membuat navigasi selalu punya data sebelum render, serta menyeragamkan cara data disiapkan:

JSLoader sebagai sumber kebenaran
const daftarRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "daftar",
  loader: async ({ context }) => {
    const data = await context.queryClient.ensureQueryData({
      queryKey: ["daftar"],
      queryFn: ambilDaftar,
    })
    return data
  },
  component: DaftarComponent,
})

ensureQueryData mengambil data dari cache jika ada, atau mem-fetch jika belum — menghindari request ganda antara loader dan useQuery. Dengan pola ini, komponen selalu menerima data dari loader dan menambahkan useQuery hanya jika membutuhkan interaktivitas seperti refetch.

Tip

ensureQueryData adalah sahabat terbaik full-stack pattern: router menjamin data tersedia, dan query tetap memakai cache yang sama sehingga tidak ada data ganda.

Penutup

Episode 10 menutup data management di router: deferred loading dengan Await untuk data lambat, errorComponent dan notFoundComponent untuk kegagalan, route-based mutations yang meng-invalidate router, serta loader sebagai satu-satunya sumber data dengan ensureQueryData.

Inti yang harus dibawa pulang:

  • Loader bisa mengembalikan promise dan dirender dengan Await.
  • ErrorComponent menangani kegagalan loader per route.
  • NotFoundError diterjemahkan router menjadi halaman tidak ditemukan.
  • queryClient disuntikkan lewat router context.
  • router.invalidate menjalankan ulang loader setelah mutasi.
  • ensureQueryData mencegah request ganda di loader dan useQuery.

Di episode 11 selanjutnya kita akan membahas virtualization dan performance — dasar windowing dan useVirtualizer, virtual scrolling untuk list dan tabel, pengukuran item dinamis dengan measureElement, serta performance tuning untuk data dalam jumlah besar. Inilah kunci membuat aplikasi tetap mulus dengan jutaan baris!

Belajar TanStack - Router Advanced & Data Management | Belajar TanStack