Learn Svelte - Stores & State Management
Episode 6 of 24

Learn Svelte - Stores & State Management

This episode dissects state management in Svelte: writable, readable, and derived stores, custom stores with their own methods, the subscription mechanism, and integrating stores with reactive statements and lifecycle. You will also learn when to use a store versus runes.

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

Introduction

In episode 5 you learned to share data between components through props and events. That is enough for direct parent-child communication. But what if two distant components — say, a navbar and a settings page — must read the same state? Passing props through layer after layer makes a mess of your code. That is where stores step in.

A store is a reactive object that can be subscribed to from anywhere. Svelte provides three basic types: writable for mutable state, readable for read-only values, and derived for values computed from other stores. Combined with the ability to write custom stores, you can build state management patterns that fit your application's needs.

This episode covers all three store types, custom stores, the subscription mechanism, and their integration with reactive statements and lifecycle. By the end, you will know exactly when to use a store and when plain runes $state are enough.

Writable, Readable, and Derived Stores

Writable Store

writable is the most basic store. It holds a value and provides methods to read it reactively:

JSCreating a writable store
import { writable } from "svelte/store"
 
export const count = writable(0)

Inside a component, the $ prefix makes a store readable and updatable directly:

Using a store in a component
<script>
  import { count } from "../lib/stores.js"
 
  function tambah() {
    count.update((n) => n + 1)
  }
</script>
 
<button onclick={tambah}>Klik {$count}</button>

$count is the current store value — Svelte compiles it into an automatic subscription. count.update((n) => n + 1) changes the value with a transformation function. .set(value) overwrites the value entirely.

Readable Store

readable is used for values that cannot be changed from outside — for example a timer, a clock, or a status set once. The second argument is a function that receives set and runs when the first subscriber arrives:

JSReadable store with a timer
import { readable } from "svelte/store"
 
export const jam = readable(new Date(), (set) => {
  const id = setInterval(() => set(new Date()), 1000)
  return () => clearInterval(id)
})

readable(nilaiAwal, (set) => ...) fills in the value through set. The returned function is called when the last subscriber leaves — the place to clean up the interval. This is store integration with lifecycle: subscription and cleanup are handled automatically by Svelte.

Derived Store

derived computes a new value from one or more stores. Every time a source store changes, the derived value updates too:

JSDerived store from two sources
import { writable, derived } from "svelte/store"
 
export const harga = writable(1000)
export const qty = writable(2)
 
export const total = derived([harga, qty], ([h, q]) => h * q)

derived([harga, qty], ([h, q]) => h * q) accepts an array of stores as sources and a function to compute the result. total only updates when harga or qty changes — no wasted computation.

Custom Stores and Subscription

Creating a Custom Store

A custom store is any object that has a subscribe method. The most common pattern: wrap a writable and expose tailored methods. This hides internal details and prevents arbitrary modifications:

JSCustom cart store
import { writable } from "svelte/store"
 
function buatKeranjang() {
  const { subscribe, update } = writable([])
 
  return {
    subscribe,
    tambah(item) {
      update((items) => [...items, item])
    },
    hapus(id) {
      update((items) => items.filter((i) => i.id !== id))
    },
    reset() {
      update(() => [])
    },
  }
}
 
export const keranjang = buatKeranjang()

buatKeranjang() returns an object with subscribe taken from the internal writable, plus the tambah, hapus, and reset methods. Components cannot touch the raw array directly — all changes go through methods. This is an example of the facade pattern that makes state management safe.

Manual Subscription

Outside components — for example in a module or function — you subscribe manually with .subscribe(callback), which returns an unsubscribe function:

JSManual subscription
import { count } from "../lib/stores.js"
 
const unsubscribe = count.subscribe((nilai) => {
  console.log("Nilai baru:", nilai)
})
 
unsubscribe()

count.subscribe((nilai) => ...) runs the callback every time the value changes, and once immediately at subscription time. The returned function is the unsubscribe — call it when you no longer need it, or you will leak memory.

Integration with Reactive Statements and Lifecycle

Stores in Reactive Statements

Stores can be read with the $ prefix inside reactive statements and runes alike:

Stores and derived in a component
<script>
  import { keranjang } from "../lib/keranjang.js"
</script>
 
<p>Total item: {$keranjang.length}</p>

$keranjang.length is read reactively in the markup. When the cart changes, the text updates without writing any subscription code. Combining stores with $derived is also common for computing display values at the component level.

Stores and Lifecycle Hooks

When combining stores with onMount and onDestroy, always make sure manual subscriptions are cleaned up.

The $ prefix in components automatically handles unsubscribe when the component is destroyed. But if you call .subscribe() manually, keep the unsubscribe function and call it in onDestroy — a habit that prevents memory leaks in large applications.

When to Use Stores vs Runes

Runes for Local State

For state used by a single component, $state is the simplest choice. No subscription, no indirection:

Local state with runes
<script>
  let buka = $state(false)
</script>
 
<button onclick={() => (buka = !buka)}>
  {buka ? "Tutup" : "Buka"} panel
</button>

let buka = $state(false) is enough for state that never leaves the component. Adding a store to state like this only adds complexity without benefit.

Stores for Shared State

Use stores when state is read or modified by many components that are not directly connected: user session, shopping cart, theme preferences, notifications. Stores also fit data fetched from a server and used across pages.

The principle is simple: start with $state, move up to a store when state starts being shared between many components. The following episodes use this pattern constantly, from auth to theme settings.

Conclusion

Key takeaways:

  • writable for mutable state, readable for fixed values, derived for derived values.
  • Custom stores wrap a writable and expose safe, descriptive methods.
  • The $ prefix makes stores reactive in components; .subscribe() manually for non-component code.
  • Always call unsubscribe for manual subscriptions to avoid leaks.
  • Store integration with lifecycle is managed automatically via the $ prefix.
  • Start with runes $state for local state; use stores when state is shared across components.

In the next episode 7 we will discuss transitions and animations — the motion directives transition:, animate:, in:, and out:, plus custom animation with tweened and spring. Stores will serve as the source of values to animate. See you in the next episode!

Learn Svelte - Stores & State Management | Learn Svelte