Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for The Unwritten Svelte Stores Guide
Jonathan Gamble
Jonathan Gamble

Posted on • Edited on

     

The Unwritten Svelte Stores Guide

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 javascriptSet() object.

Basics

A svelte store looks like this:

store.ts

import{writable}from'svelte/store';...exportconstmy_store=writable<string>('default value');
Enter fullscreen modeExit fullscreen mode

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

importmy_storefrom'./store.ts';...my_store.set('new value');
Enter fullscreen modeExit fullscreen mode

or get the state easily:

component2.svelte

import{get}from'svelte/store';importmy_storefrom'./store.ts';...constvalue=get(my_store);
Enter fullscreen modeExit fullscreen mode

Theget 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

importmy_storefrom'./store.ts';...constunsubscribe=my_store.subscribe((value:string)=>{console.log('The current value is:',value);// do something});...onDestroy(unsubscribe);
Enter fullscreen modeExit fullscreen mode

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.

importmy_storefrom'./store.ts';...// set latest value$my_store='new value';...// always get latest valueconstnew_value=$my_store;...// always update DOM with latest value<h1>{$my_store}</h1>
Enter fullscreen modeExit fullscreen mode

The beauty of using the$ syntax is that you don't have to handle the subscription withonDestroy, this is automatically done for you.

Update

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

You could do this:

importmy_storefrom'./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));
Enter fullscreen modeExit fullscreen mode

Or you could just use theupdate method:

importmy_storefrom'./store.ts';...my_store.update((value:string)=>'new value'+value);
Enter fullscreen modeExit fullscreen mode

The key with theupdate method is to return the new value. When you store an actual object in your store, theupdate 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.

const{subscribe,set,update}=writable<string>('default value');...// Subscribesubscribe((value:string)=>console.log(value));...// Setset('new value');...// Updateupdate((value:string)=>'new value'+value);
Enter fullscreen modeExit fullscreen mode

Start and Stop Notifications

Svelte Stores also have a second argument. This argument is a function that inputs theset method, and returns anunsubscribe method.

import{typeSubscriber,writable}from"svelte/store";...exportconsttimer=writable<string>(null,(set:Subscriber<string>)=>{constseconds=setInterval(()=>set(newDate().getSeconds().toString()),1000);return()=>clearInterval(seconds);});
Enter fullscreen modeExit fullscreen mode

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 theset andupdate 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 rxjscombineLatest. 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.

import{derived,readable,writable,typeSubscriber,typeWritable}from"svelte/store";...exportconsttimer=writable<string>(null,(set:Subscriber<string>)=>{constseconds=setInterval(()=>set(newDate().getSeconds().toString()),1000);return()=>clearInterval(seconds);});exportconsttimer2=writable<string>(null,(set:Subscriber<string>)=>{constseconds=setInterval(()=>set(newDate().getMinutes().toString()),1000);return()=>clearInterval(seconds);});
Enter fullscreen modeExit fullscreen mode

Let's say we have these two random timers. What if we want to concatenate or add them somehow?

derived<[stores...],type>([stores...],([$stores...])=>{// do somethingreturnnewvalue...});
Enter fullscreen modeExit fullscreen mode

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:

exportconstd=derived<[Writable<string>,Writable<string>],string>([timer,timer2],([$timer,$timer2]:[$timer:string,$timer2:string])=>{return$timer+$timer2;});
Enter fullscreen modeExit fullscreen mode

If thetypescript confuses you here, just imagine this in vanilla js:

exportconstd=derived([timer,timer2],([$timer,$timer2])=>$timer+$timer2);
Enter fullscreen modeExit fullscreen mode

Or if you just want to change the value from one store, you could do:

exportconstd=derived(timer,$timer=>$timer+newDate().getMinutes().toString());
Enter fullscreen modeExit fullscreen mode

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 theonAuthStateChanged andonIdTokenChanged observables in Supabase and Firebase.

import{readable,typeSubscriber}from"svelte/store";...exportconstuser=readable<any>(null,(set:Subscriber<any>)=>{set(supabase.auth.user());constunsubscribe=supabase.auth.onAuthStateChange((_,session)=>session?set(session.user):set(null));returnunsubscribe.data.unsubscribe;});
Enter fullscreen modeExit fullscreen mode

or a Firestore subscription:

exportconstgetTodos=(uid:string)=>writable<Todo[]>(null,(set:Subscriber<Todo[]>)=>onSnapshot<Todo[]>(query<Todo[]>(collection(db,'todos')asCollectionReference<Todo[]>,where('uid','==',uid),orderBy('created')),(q)=>{consttodos=[];q.forEach((doc)=>todos.push({...doc.data(),id:doc.id}));set(todos);}));
Enter fullscreen modeExit fullscreen mode

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 anunsubscribe method. Supabase, for some odd reason, has its unsubscribe method embedded, so we have to return that directly.

Here is a Firebase Auth example:

exportconstuser=readable<UserRec>(null,(set:Subscriber<UserRec>)=>onIdTokenChanged(auth,(u:User)=>set(u)));
Enter fullscreen modeExit fullscreen mode

which is much simpler...

Function

A writable is really just an object with theset,update, andsubscribe 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.

exportconstsomething=(value:string)={const{set,update,subscribe}=writable<string|null>(value);return{set,update,subscribesetJoker:()=>set('joker')}};
Enter fullscreen modeExit fullscreen mode

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.

importsomethingfrom'./stores.ts';...constnewStore=something('buddy');newStore.setJoker();
Enter fullscreen modeExit fullscreen mode

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.

<Dialogbind:open={$resourceStore.opened}>...</Dialog>
Enter fullscreen modeExit fullscreen mode

resourceStore.ts

interfacerStore{type:'add'|'edit'|'delete'|null,resource?:Resource|null,opened?:boolean};const_resourceStore=writable<rStore>({type:null,resource:null,opened:false});exportconstresourceStore={subscribe:_resourceStore.subscribe,set:_resourceStore.set,update:_resourceStore.update,reset:()=>_resourceStore.update((self:rStore)=>{self.type=null;self.opened=false;self.resource=null;returnself;}),add:()=>_resourceStore.update((self:rStore)=>{self.type='add';self.opened=true;returnself;}),edit:(resource:Resource)=>_resourceStore.update((self:rStore)=>{self.type='edit';self.resource=resource;self.opened=true;returnself;}),delete:(resource:Resource)=>_resourceStore.update((self:rStore)=>{self.type='delete';self.resource=resource;self.opened=true;returnself;})};
Enter fullscreen modeExit fullscreen mode

Here a resource can be anything. Something like this can be called with:

constr=newResource(...);resourceStore.edit(r);
Enter fullscreen modeExit fullscreen mode

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

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;returnself;}),add:()=>update((self:rStore)=>{self.type='add';self.opened=true;returnself;}),edit:(resource:Resource)=>update((self:rStore)=>{self.type='edit';self.resource=resource;self.opened=true;returnself;}),delete:(resource:Resource)=>update((self:rStore)=>{self.type='delete';self.resource=resource;self.opened=true;returnself;})}};exportconstresourceStore=_resourceStore();
Enter fullscreen modeExit fullscreen mode

You could also do it in one line like so:

exportconstresourceStore=(()=>{...})();
Enter fullscreen modeExit fullscreen mode

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

Checkoutcode.build for more tips!

Top comments(9)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
unlocomqx profile image
Mohamed Jbeli
  • Joined

For manual sub/unsub, you can useonMount like so

onMount(my_store.subscribe((value:string)=>{console.log('The current value is:',value);// do something}))
Enter fullscreen modeExit fullscreen mode

This will pass the unsub fn to onMount, that fn will be executed on component destroy.

CollapseExpand
 
drthey profile image
Aloysius X. They
  • Joined
• Edited on• Edited

Think of derived stores like rxjscombineLatest

So, to understand derived stores (at least from this article) you either have to already know or be familiar with rxjs or go learn it.

Great! 🙄

CollapseExpand
 
jdgamble555 profile image
Jonathan Gamble
My main job is not in IT, but I do have a CS degree and have been programming for over 20 years (only born in 1984). I hate dealing with Servers, and want Graph Databases to be the new norm.
  • Location
    Louisiana
  • Education
    LSU, Oregon State, Middlebury
  • Joined

Not to worry, Svelte 5 will be out in the next few months, which uses signals, is much more intuitive, and will make this obsolete eventually.

CollapseExpand
 
drthey profile image
Aloysius X. They
  • Joined

excellent! 🚀

CollapseExpand
 
atuttle profile image
Adam Tuttle
  • Location
    Suburbs of Philadelphia, PA, USA
  • Work
    CTO at AlumnIQ
  • Joined

Thanks for this! Thewritable<type> syntax seems to be missing from the Svelte docs (or I suck at finding it). Been wondering about this for about a week now!

CollapseExpand
 
morzaram0 profile image
Chris King
  • Joined

This objects section is an example I've been searching for! Thank you!

CollapseExpand
 
myleftshoe profile image
myleftshoe
  • Joined

Nice one, as you promised - simple, succinct and entertaining. thanks!

CollapseExpand
 
juniordevforlife profile image
Jason F
JavaScript enthusiast , Xfce enjoyer, I use Fedora btw.
  • Location
    Austin, TX
  • Education
    BS CIS
  • Work
    UI Developer
  • Joined

Thanks for this informative introduction to Svelte stores. As an Angular developer I really enjoy seeing the reactivity baked in to the store.

CollapseExpand
 
trujared profile image
Jared Truscott
  • Joined

This is exactly how Svelte Stores should be taught. Thanks for this.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

My main job is not in IT, but I do have a CS degree and have been programming for over 20 years (only born in 1984). I hate dealing with Servers, and want Graph Databases to be the new norm.
  • Location
    Louisiana
  • Education
    LSU, Oregon State, Middlebury
  • Joined

More fromJonathan Gamble

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp