Having fun with svelte stores using store proxies
Nashe Omirro
Posted on November 17, 2023
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:
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:
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:
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!
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!
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:
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!
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~
Posted on November 17, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 26, 2024
November 26, 2024