Having fun with svelte stores using store proxies
Tags: svelte, javascript, webdev
Published: 2023-11-17 18:29:53

So imagine this one big global store with lots of nested properties, how would you write a way to derive a piece of state from that store? One answer would be to just react to that store inside svelte itself, and maybe a utility function could handle the deriving logic:

```ts
import { store } from './store.js';
import { deriveFoo } from './store.utils.js';

$: foo = deriveFoo($store);
```

another more refined answer is to use a derived store to then export it inside of svelte:

```tsx
import { derivedFoo } from './store.js';

<p>{$derivedFoo}</p>
```

In most cases, those two are good enough and is the standard way to get value out, but there is a more radical approach to derivation, and it has big upside to it as well.

## abusing the store contract

Before getting into the third approach, we'll just understand what the store contract is in svelte. In a nutshell, it is the automatic process of svelte subscribing and setting a store inside components:

```tsx
import { store } from './store.js';

function updateStore() {
  // assignments becomes a call to the store's set property
  $store.foo = $store.foo + 1;
}

// svelte spots this and auto-subscribes for you
<p>{$store.foo}</p>
```

What makes them truly awesome though is that they don't care what kind of object it's actually accessing, so long as it has a `subscribe` property and optional `set` property and both conform to the store contract (`subscribe` gets passed a callback and `set` gets passed a value).

Now knowing this, we are able to create our own derived store without the `derived` function from svelte!

```tsx
const store = writable({ foo: 0 });

const derivedFoo = {
  subscribe: (cb) => store.subscribe((state) => cb(state.foo),
};
```
In the above example, we are basically hijacking the subscription process and letting us extract `foo` and pass it to the given callback. This means that users of the store are actually subscribed to the big gigantic store but only get a small part of it.

## derived and writable?

_Okay, it's nice to understand what's actually happening but why do this when you can just use `derived`?_

The answer to that is the fun part, unlike `derived`, we can supply a `set` property! Making the store writable!

```tsx
const store = writable({ foo: 0 });

const foo = {
  subscribe: (cb) => store.subscribe((state) => cb(state.foo),
  set: (value) => store.set((state) => { 
    ...state,
    foo: value
  }
};
```
And to use it:
```tsx
import { foo } from './store.js';

function updateFoo() {
  // we can correctly set foo!
  $foo = $foo + 1
}

// we can use it just like derived
<p>{$foo}</p>
```
And that's not all, another feature that uses the store contract when dealing with stores is the `bind:` directive, which means we could do this!

```tsx
import { foo } from './store.js';

<input bind:value={$foo} />; 
```
Binding to a derived state! Pretty cool, yeah?

## Conclusion

You don't have to stop there, this is just a simple example, the truth is that as long as you follow the store contract, anything goes and you can get crazier with it.

but I guess stores will probably kick the bucket soon because of the new runes coming to town, understanding this though will still help you try to do the same with runes! Or maybe not... apart from the "you can do this?!" factor, realistically there isn't really a common use case this solves that the other, more simpler and readable approaches don't solve. It is still kinda neat~