The Unwritten Svelte Stores Guide
Tags: svelte, sveltekit, webdev, javascript
Published: 2022-02-20 16:11:41

Svelte stores are not that difficult to understand. However, when you're first learning and you google "svelte stores," all you see is a whole bunch of counter examples. 

I believe they are misunderstood, easier than you think, and need to be explained better.

At heart, a svelte store is a way to store data outside of components. The store object returns subscribe, set, and update methods. Because of the subscribe method, the store acts as an observable to update your data in real time. Under the hood, the data is being stored in a javascript `Set()` object.

### Basics

A svelte store looks like this:

**store.ts**
```typescript
import { writable } from 'svelte/store';
...
export const my_store = writable<string>('default value');
```

If you store this in an outside `.js` or `.ts` file, you can import it anywhere to share your state.

#### Set / Get

You can set the state easily:

**component.svelte**
```typescript
import my_store from './store.ts';
...
my_store.set('new value');
```

or get the state easily:

**component2.svelte**
```typescript
import { get } from 'svelte/store';
import my_store from './store.ts';
...
const value = get(my_store);
```
The `get` method will get the current value at that moment in time. If you change the value later, it will not be updated in the place in your code.

#### Subscribe

So you can subscribe to always get the latest value:

**component3.svelte**
```typescript
import my_store from './store.ts';
...
const unsubscribe = my_store.subscribe((value: string) => {
  console.log('The current value is: ', value);
  // do something
});
...
onDestroy(unsubscribe);
```
Notice just like any observable you have to destroy the instance of your subscription when the component is done rendering for good memory management.

#### Auto Subscriptions

You can also use a reactive statement to subscribe to a store.

```typescript
import my_store from './store.ts';
...
// set latest value
$my_store = 'new value';
...
// always get latest value
const new_value = $my_store;
...
// always update DOM with latest value
<h1>{$my_store}</h1>
```
The beauty of using the `$` syntax is that you don't have to handle the subscription with `onDestroy`, this is automatically done for you.

#### Update

Sometimes you want to change the value based on the current value.

You could do this:

```typescript
import my_store from './store.ts';
import { get } from 'svelte/store';
...
my_store.subscribe((value: string) => {
  my_store.set('new value' + value);
  // do something
});
...
// or this
...
my_store.set('new value' + get(my_store));
```
Or you could just use the **update** method:

```typescript
import my_store from './store.ts';
...
my_store.update((value: string) => 'new value' + value);
```
The key with the **update** method is to return the new value. When you store an actual object in your store, the **update** method is key to easily changing your object.

#### Deconstruction

You can deconstruct the 3 methods of a store to get exact control of your store.

```typescript
const { subscribe, set, update } = writable<string>('default value');
...
// Subscribe
subscribe((value: string) => console.log(value));
...
// Set
set('new value');
...
// Update
update((value: string) => 'new value' + value);
```
#### Start and Stop Notifications

Svelte Stores also have a second argument. This argument is a function that inputs the `set` method, and returns an `unsubscribe` method. 

```typescript
import { type Subscriber, writable } from "svelte/store";
...
export const timer = writable<string>(
    null, (set: Subscriber<string>) => {
    const seconds = setInterval(
        () => set(
            new Date().getSeconds().toString()
        ), 1000);
    return () => clearInterval(seconds);
});
```
I tried to make this easy to read (dev.to prints their code large). All this is is a function that gets repeated. When the component gets destroyed, the returned function is called to destroy the repetition in memory. That's it! It does not have to be overly complicated. As you can see, the second argument is perfect for observables.

### Readable

The last example should really have been a readable. A readable is just a writable store, without returning the `set` and `update` methods. All it has is subscribe. Hence, you set the initial value, or your set the value internally with the start and stop notification function.

### Derived Stores

Think of derived stores like rxjs `combineLatest`. It is a way to take two or more different store values, and combine them to create a new store. You also could just change only one store into a new value based on that store.

```typescript
import {
  derived,
  readable,
  writable,
  type Subscriber,
  type Writable
} from "svelte/store";
...
export const timer = writable<string>(
    null, (set: Subscriber<string>) => {
        const seconds = setInterval(
            () => set(
                new Date().getSeconds().toString()
            ), 1000);
        return () => clearInterval(seconds);
    });

export const timer2 = writable<string>(
    null, (set: Subscriber<string>) => {
        const seconds = setInterval(
            () => set(
                new Date().getMinutes().toString()
            ), 1000);
        return () => clearInterval(seconds);
    });
```
Let's say we have these two random timers. What if we want to concatenate or add them somehow?

```typescript
derived<[stores...], type>(
  [stores...],
  ([$stores...]) => {
  // do something
  return new value...
});
```
This seems hard to read, but it basically says:
- first argument is the original store, or an array of stores
- second argument is the new function with the auto subscription, or an array of auto subscriptions from the stores.
- the return value is whatever type you want for the new value

So, to put our times together to some odd value, we could do:

```typescript
export const d = derived<
  [Writable<string>, Writable<string>],
  string
>(
  [timer, timer2],
  ([$timer, $timer2]: [$timer: string, $timer2: string]) => {
    return $timer + $timer2;
});
```
If the **typescript confuses you here**, just imagine this in vanilla js:

```typescript
export const d = derived(
  [timer, timer2],
  ([$timer, $timer2]) => $timer + $timer2
);
```
Or if you just want to change the value from one store, you could do:

```typescript
export const d = derived(
  timer,
  $timer => $timer + new Date().getMinutes().toString()
);
```
So derived stores have a very specific use case, and are not easy to read even in vanilla js.

## Cookbook

### Observables

Instead of importing wanka, rxjs, zen-observables, etc, you can just convert you subscription object into a store.

A perfect example of this is the `onAuthStateChanged` and `onIdTokenChanged` observables in Supabase and Firebase.

```typescript
import { readable, type Subscriber } from "svelte/store";
...
export const user = readable<any>(null, (set: Subscriber<any>) => {
    set(supabase.auth.user());
    const unsubscribe = supabase.auth.onAuthStateChange(
        (_, session) => session ? set(session.user) : set(null));
    return unsubscribe.data.unsubscribe;
});
```
or a Firestore subscription:
```typescript
export const getTodos = (uid: string) => writable<Todo[]>(
    null,
    (set: Subscriber<Todo[]>) =>
        onSnapshot<Todo[]>(
            query<Todo[]>(
                collection(db, 'todos')
                  as CollectionReference<Todo[]>,
                where('uid', '==', uid),
                orderBy('created')
            ), (q) => {
                const todos = [];
                q.forEach(
                  (doc) =>
                    todos.push({ ...doc.data(), id: doc.id })
                );
                set(todos);
            })
);
```
Again, it is hard to make this readable on dev.to, but you can see you just return the observable here, which will already have an `unsubscribe` method. Supabase, for some odd reason, has its unsubscribe method embedded, so we have to return that directly.

Here is a Firebase Auth example:

```typescript
export const user = readable<UserRec>(
    null,
    (set: Subscriber<UserRec>) =>
        onIdTokenChanged(auth, (u: User) => set(u))
);
```
which is much simpler...

### Function
A writable is really just an object with the `set`, `update`, and `subscribe` methods. However, you will see a lot of examples returning a function with these methods because it is easier to embed the writable object.

The problem with these examples, is a writable is technically NOT a function, but an object.

```typescript
export const something = (value: string) = {
  const { set, update, subscribe } = writable<string | null>(value);
  return {
    set,
    update,
    subscribe
    setJoker: () => set('joker')
  }
};
```
So, this has all the functionality of a store, but with easy access to create new functionality. In this case, we can call a function to do anything we want. Normally, we set or update a value.

```typescript
import something from './stores.ts';
...
const newStore = something('buddy');
newStore.setJoker();
```

### Objects

When we want to store several values in a store, or an object itself, we can use an object as the input. 

Also, sometimes we need to bind a value to store. We can't do this with a function.

```html
<Dialog	bind:open={$resourceStore.opened}>
...
</Dialog>
```
**resourceStore.ts**
```typescript
interface rStore {
    type: 'add' | 'edit' | 'delete' | null,
    resource?: Resource | null,
    opened?: boolean
};

const _resourceStore = writable<rStore>({
    type: null,
    resource: null,
    opened: false
});

export const resourceStore = {

    subscribe: _resourceStore.subscribe,
    set: _resourceStore.set,
    update: _resourceStore.update,
    reset: () =>
        _resourceStore.update((self: rStore) => {
            self.type = null;
            self.opened = false;
            self.resource = null;
            return self;
        }),
    add: () =>
        _resourceStore.update((self: rStore) => {
            self.type = 'add';
            self.opened = true;
            return self;
        }),
    edit: (resource: Resource) =>
        _resourceStore.update((self: rStore) => {
            self.type = 'edit';
            self.resource = resource;
            self.opened = true;
            return self;
        }),
    delete: (resource: Resource) =>
        _resourceStore.update((self: rStore) => {
            self.type = 'delete';
            self.resource = resource;
            self.opened = true;
            return self;
        })
};
```
Here a resource can be anything. Something like this can be called with:
```typescript
const r = new Resource(...);
resourceStore.edit(r);
```

**Update: 5/4/23** - This should be refactored to prevent global declarations like so:

```ts
const _resourceStore = () => {

    const { set, update, subscribe } = writable<rStore>({
        type: null,
        resource: null,
        opened: false
    });

    return {
        subscribe,
        set: set,
        update: update,
        reset: () => update((self: rStore) => {
            self.type = null;
            self.opened = false;
            self.resource = null;
            return self;
        }),
        add: () => update((self: rStore) => {
            self.type = 'add';
            self.opened = true;
            return self;
        }),
        edit: (resource: Resource) => update((self: rStore) => {
            self.type = 'edit';
            self.resource = resource;
            self.opened = true;
            return self;
        }),
        delete: (resource: Resource) => update((self: rStore) => {
            self.type = 'delete';
            self.resource = resource;
            self.opened = true;
            return self;
        })
    }
};

export const resourceStore = _resourceStore();
```
You could also do it in one line like so:

```ts
export const resourceStore = (() => {

...

})();
```

So as you can see from beginning to end, a simple concept can be made overly complicated. All you're doing is storing a value outside your component. You can update it, set it, get it, subscribe to it, or create your own methods.

Either way, I find Svelte Stores easier to learn than React Hooks, but not as easy as Angular Services when it comes to objects.

I hope this helps someone,

J

Checkout [code.build](https://code.build) for more tips!