Custom Stores — Svelte

Custom store membungkus logic bisnis di dalam store yang expose API tertentu. Membuat Custom Store import { writable } from "svelte/store" function…

Custom store adalah store yang kamu bungkus sendiri: sebuah function membuat writable di dalamnya, lalu cuma membuka subscribe dan method bernama yang kamu izinkan, misalnya tambah, hapus, dan kosongkan. Bayangin mesin kasir Warung Bu Sri. Pelayan cuma bisa menekan tombol "tambah item", "hapus item", dan "batal semua". Laci uangnya terkunci, jadi gak ada yang bisa mengisi angka sembarangan. Writable biasa itu laci yang terbuka; custom store itu mesin kasir dengan tombolnya.

🧪 Tebakan cepat: keranjang adalah writable([]) biasa, dan Header menampilkan Keranjang: {$keranjang.length} menu. Sebuah komponen lain menjalankan keranjang.set("rusak"). Header jadi menampilkan apa? Jawabannya ada di bagian "Masalah writable biasa".

Masalah writable biasa

Di pelajaran Stores, keranjang adalah writable yang dibuka penuh: siapa pun bisa memanggil set dan update dengan nilai apa pun. Itu menimbulkan dua masalah:

createKeranjang(): bungkus logikanya

// keranjang.js
import { writable } from "svelte/store"

function createKeranjang() {
  const { subscribe, set, update } = writable([])

  return {
    subscribe,
    tambah: (nama) =>
      update((isi) => {
        const ada = isi.find((m) => m.nama === nama)
        if (ada) return isi.map((m) => (m === ada ? { ...m, porsi: m.porsi + 1 } : m))
        return [...isi, { nama, porsi: 1 }]
      }),
    hapus: (nama) => update((isi) => isi.filter((m) => m.nama !== nama)),
    kosongkan: () => set([]),
  }
}

export const keranjang = createKeranjang()

Kontrak store: cukup subscribe

Kenapa $keranjang masih jalan, padahal objeknya bukan writable? Karena bagi Svelte, store adalah objek apa pun yang punya method subscribe: method yang memanggil callback dengan nilainya sekarang dan tiap berubah, lalu mengembalikan function untuk berhenti. Itu disebut kontrak store. Objek buatan sendiri juga sah:

<script>
  const jamBuka = {
    subscribe(fn) {
      fn("07.00")
      return () => {}
    },
  }
</script>

<p>Buka jam {$jamBuka}</p>

Hasilnya Buka jam 07.00. Sebaliknya, kalau createKeranjang lupa mengembalikan subscribe, {$keranjang} gagal dengan store.subscribe is not a function (di mode dev: 'keranjang' is not a store with a 'subscribe' method).

Memakai di komponen

<!-- DaftarMenu.svelte -->
<script>
  import { keranjang } from "./keranjang.js"
  const menu = ["Nasi Goreng", "Mie Ayam", "Es Teh"]
</script>

{#each menu as nama}
  <button on:click={() => keranjang.tambah(nama)}>Tambah {nama}</button>
{/each}
<!-- Header.svelte -->
<script>
  import { keranjang } from "./keranjang.js"
</script>

<p>Keranjang: {$keranjang.length} menu</p>
{#each $keranjang as m}
  <p>{m.nama} x{m.porsi} <button on:click={() => keranjang.hapus(m.nama)}>Hapus</button></p>
{/each}
<button on:click={keranjang.kosongkan}>Kosongkan</button>
Kejadian Isi keranjang Header
Tambah Nasi Goreng Nasi Goreng x1 Keranjang: 1 menu
Tambah Nasi Goreng lagi Nasi Goreng x2 Keranjang: 1 menu
Tambah Es Teh Nasi Goreng x2, Es Teh x1 Keranjang: 2 menu
keranjang.set("rusak") (gak berubah) TypeError: keranjang.set is not a function

Komponen cuma tahu nama method-nya. Kalau aturan tambah diubah, misalnya maksimal 5 porsi, cukup ubah keranjang.js.

Latihan: createCounter

Latihannya memakai pola yang sama untuk penghitung:

<script>
  import { writable } from "svelte/store"

  function createCounter() {
    const { subscribe, set, update } = writable(0)
    return {
      subscribe,
      increment: () => update((n) => n + 1),
      decrement: () => update((n) => n - 1),
      reset: () => set(0),
    }
  }

  const counter = createCounter()
</script>

<p>Count: {$counter}</p>
<button on:click={counter.increment}>+</button>
<button on:click={counter.decrement}>-</button>
<button on:click={counter.reset}>Reset</button>

Awalnya Count: 0. Klik + jadi Count: 1, klik Reset kembali Count: 0, dan dari awal, klik - jadi Count: -1. on:click={counter.increment} menyerahkan method-nya, tanpa tanda kurung, persis seperti handler di pelajaran Event Handling.

Svelte 5

Custom store tetap jalan di Svelte 5. Svelte 5 juga punya cara lain untuk membungkus state dan logikanya: class dengan field $state di file .svelte.js, misalnya class Keranjang { isi = $state([]); tambah(nama) { ... } }, lalu export const keranjang = new Keranjang(). Komponen membacanya sebagai keranjang.isi, tanpa $.

⚠️ Jebakan yang sering ketemu

TL;DR: Custom store adalah function seperti createKeranjang() yang membuat writable di dalamnya, lalu cuma mengembalikan subscribe plus method bernama (tambah, hapus, kosongkan). Logikanya tinggal di satu tempat (enkapsulasi), dan gak ada yang bisa memanggil set dari luar: keranjang.set("rusak") menghasilkan TypeError, sedangkan writable biasa menerimanya dan Header jadi Keranjang: 5 menu. Objek apa pun yang punya subscribe adalah store (kontrak store), jadi {$keranjang} tetap jalan; tanpa subscribe, error. writable memberi tiga method: subscribe, set, dan update. Latihannya: createCounter() dengan increment, decrement, dan reset, <p>Count: {$counter}</p>, dan tombol +, -, Reset yang menghasilkan Count: 1, Count: -1, dan Count: 0.

Yang akan kamu pelajari