Episode ini membahas state management di SvelteKit: writable, readable, dan derived stores, custom stores untuk shared state, sinkronisasi state antara server dan client, serta integrasi dengan TanStack Query untuk server state. Kalian akan memilih strategi state yang tepat untuk setiap kebutuhan.

Seiring aplikasi tumbuh, state tidak lagi hanya milik satu komponen. Keranjang belanja yang dilihat header, navbar, dan halaman keranjang adalah contoh state yang harus dibagikan. Episode 7 membahas store — mekanisme resmi Svelte untuk shared state.
Selain store, kita juga perlu memikirkan jenis state yang berbeda. State lokal komponen ditangani runes, state bersama client memakai store, dan server state yang datang dari API punya siklus hidupnya sendiri — sering kali lebih baik ditangani pustaka seperti TanStack Query.
Di akhir episode ini kalian akan tahu kapan memakai runes, kapan memakai store, dan kapan menyerahkan state ke pustaka query. Pilihan yang tepat menghindarkan aplikasi dari bug sinkronisasi yang sulit dilacak.
Writable store adalah store paling dasar: nilainya bisa dibaca, diubah, dan dihitung ulang. Buat dengan fungsi writable dari modul svelte/store.
import { writable, derived, get } from "svelte/store";
export const hitung = writable(0);
export const duaKali = derived(hitung, ($hitung) => $hitung * 2);
hitung.set(5);
hitung.update((n) => n + 1);
console.log(get(hitung));set mengganti nilai, update mengubah nilai berdasarkan nilai sebelumnya, dan get membaca nilai satu kali untuk konteks non-komponen. Semua operasi ini diikuti pemberitahuan otomatis ke seluruh subscriber.
Derived store menghitung nilai baru dari satu atau beberapa store lain. Mirip $derived di komponen, tapi bersifat global dan bisa digunakan di mana saja, termasuk di dalam modul.
import { derived } from "svelte/store";
export const total = derived([keranjang, ongkir], ([items, biaya]) => {
const subtotal = items.reduce((jumlah, item) => jumlah + item.harga, 0);
return subtotal + biaya;
});Derived store hanya menghitung ulang saat dependensinya berubah. Untuk store yang nilainya tidak pernah berubah — misalnya daftar fitur yang diaktifkan — gunakan readable store. Readable cocok untuk nilai yang dibaca banyak komponen tetapi di-set sekali, seperti data konfigurasi yang diisi dari luar.
Custom store menggabungkan subscribe dengan method khusus. Ini adalah pola terbaik untuk state domain seperti keranjang atau daftar notifikasi.
import { writable } from "svelte/store";
function buatKeranjang() {
const { subscribe, update, set } = writable([]);
return {
subscribe,
tambah: (produk) => update((items) => [...items, produk]),
hapus: (id) => update((items) => items.filter((p) => p.id !== id)),
kosongkan: () => set([])
};
}
export const keranjang = buatKeranjang();Method tambah dan hapus menyembunyikan detail update dari pemakai. Komponen cukup memanggil keranjang.tambah(produk) tanpa tahu bagaimana array diubah.
Di dalam komponen, tambahkan prefix $ pada nama store untuk auto-subscription:
<script>
import { hitung } from "$lib/stores/hitung";
import { keranjang } from "$lib/stores/keranjang";
</script>
<p>Hitungan: {$hitung}</p>
<p>Jumlah item: {keranjang.length}</p>
<button onclick={() => hitung.update((n) => n + 1)}>Naik</button>
<button onclick={() => keranjang.tambah({ id: 1, harga: 5000 })}>Tambah</button>Sintaks $store otomatis subscribe saat komponen dibuat dan unsubscribe saat dihancurkan. Ini menghindari kebocoran memori yang umum terjadi jika kalian menulis subscribe manual.
Store adalah singleton di dalam bundle JavaScript. Saat SSR, satu store yang sama digunakan untuk semua request — ini berbahaya karena state dari request satu bisa bocor ke request lain. Jangan pernah mengisi store global dengan data yang spesifik per pengguna di dalam load function.
Alur yang aman adalah: load function mengembalikan data sebagai prop, komponen root menginisialisasi store dari data tersebut, dan setiap request mendapat snapshot sendiri. Untuk state per-request yang tidak perlu diserahkan ke seluruh halaman, gunakan setContext di layout root — context tidak bocor antar request.
<script>
import { page } from "$app/stores";
import { keranjang } from "$lib/stores/keranjang";
let { data } = $props();
keranjang.set(data.keranjangAwal);
</script>
<p>Selamat datang, {data.user.nama}</p>Server state seperti user session dan data keranjang sebaiknya selalu berasal dari server dan ditampilkan sebagai data, bukan diunduh ulang oleh store di client.
Store paling cocok untuk client state: tema, filter, form draft. Untuk server state — data yang dimiliki backend — TanStack Query (versi Svelte) menyediakan caching, retry, dan invalidation yang tidak perlu kalian bangun sendiri.
<script>
import { createQuery } from "@tanstack/svelte-query";
const artikel = createQuery({
queryKey: ["artikel"],
queryFn: () => fetch("/api/artikel").then((r) => r.json())
});
</script>
{#if artikel.isPending}
<p>Memuat...</p>
{:else if artikel.isError}
<p>Gagal memuat: {artikel.error.message}</p>
{:else}
<ul>
{#each artikel.data as item}
<li>{item.judul}</li>
{/each}
</ul>
{/if}createQuery menangani status pending, error, dan success sekaligus. Data di-cache berdasarkan queryKey dan bisa di-invalidate setelah mutasi, sehingga UI selalu sinkron dengan server.
Pola yang matang membagi state secara jelas: load functions untuk data awal halaman, TanStack Query untuk data yang berubah dan di-fetch berulang, store untuk UI state bersama, dan runes untuk state lokal. Tanpa pembagian ini, aplikasi berisiko punya sumber kebenaran ganda yang saling bertabrakan.
Inti yang harus dibawa pulang:
writable, readable, dan derived adalah tiga tipe store dasar di modul svelte/store.$ di komponen melakukan auto-subscription dan cleanup otomatis.Di episode 8 selanjutnya kita membahas configuration & runtime config: svelte.config.js dan runtime configuration, environment variables publik dan privat, adapters dan deployment targets, serta feature flags untuk build environment yang berbeda.