Check out a free preview of the full Svelte Fundamentals course

The "Stores" Lesson is part of the full, Svelte Fundamentals course featured in this preview video. Here's what you'd learn in this lesson:

Rich demonstrates handling state that does not belong to a specific component using stores. Writeable stores, auto subscriptions, readable stores, derived stores, custom stores, and binding to stores are covered in this segment.


Transcript from the "Stores" Lesson

>> Okay, this is the final section of part one. And it's about stores. This is about state that does not belong to a specific component. Because very often you will have application state that is not part of the component hierarchy. It's something that can be accessed by multiple components or by a regular JavaScript module.

In Svelte, we have a state management primitive built in is called a store. And a store is really just an object with a subscribe method. When you call subscribe on a store, you will pass in a callback that gets executed every time the value of the store changes.

So in app.svelte count is a store, and we're calling count.subscribe, and we're assigning to this value count_value. So, let's look at the implementation of count. We're importing something called writable from svelte/store, and we're exporting an instance of a writable store with an initial value of 0. Okay, and we can reference this store value in multiple components.

Let's look at the incrementor.svelte. We can update the value of the count using the count's update method, count.update. It receives the initial value. And returns the next value. So now if we press the plus button, we're incrementing the value of count and inside app.svelte, because we're assigning the current value of the store to count_value.

We can see that inside our h1 element. We can do the opposite and decremented.svelte. So again count.update, get the old value and then return the value -1 can increment and we can decrement. Then finally in our resetter.svelte, we can implement this reset function. This time, we don't need to call update because we don't care about the previous value.

We'll just call count.set(0) like so. Press the button, the count resets. Now that works, but there is a subtle bug in this app that's quite similar to the bug we had with onMount, where things weren't getting cleaned up when the component was destroyed. So if this component were to get instantiated multiple times, we would be creating multiple subscriptions to this store.

And we would never be getting rid of them. So, over time, that could result in a memory leak. And one way that we can undo that memory leak is by getting a reference to the unsubscribe function, which is returned from count.subscribe. And we, could call it an onDestroy lifecycle hook, which we didn't talk about because generally I don't recommend that you use it That would look like this.

And so now when the app.svelte is removed from the DOM, we'll clean up that subscription. But this is a lot to write. The onDestroy, the let count_value, the const unsubscribe, the subscribe, the callback, onDestroy, all of that. You can imagine that if you had multiple stores that you're referencing inside the component.

This is a lot to deal with. But because svelte hates boilerplate, we have a shorthand way of doing all this. I'm just gonna go ahead and get rid of all of that. Get rid of the onDestroy, get rid of the let count_value, get rid of this count.subscribe call, and get rid of the call to onDestroy there.

And we replace the count_value variable with $count. And that is equivalent that is set at the subscription and it is added the unsubscribe logic automatically for us and it works just the same way as before. Right, because the dollar prefix tells svelte that we are dealing with the store value.

It is reserved, you cannot create your own variables inside a svelte component that has that dollar prefix. So in that example count was a writable store but sometimes it doesn't make sense for a store to be writable you shouldn't be able to set the value of the store if you have a reference to it and it's read only.

So things like the mouse position or the user's geolocation. There's no reason that you should be able to set those values from outside. And so we have a thing called a readable store as well. In our stores JS module, we're importing the readable function from svelte/stores. And we are creating a readable store, which has an initial value of null.

Can be null or undefined or whatever. And the second function to a readable store is a start function that gets called when the user subscribes to this store. So when the first person subscribes to this store, this callback will be invoked. When the last person stops subscribing to the store,

this stop function will get called as well. So this is a convenient place to do setup and tear down logic. So the current time, let's begin by passing in the current date. You can see that already the store value in app.spelt is reflected in the current date.

Down here it's formatting the current time using the Intl.DateTimeFormat function that's built into the platform. And inside here we want to add an interval that's gonna update this once a second. So you can create an interval with const interval = setInterval. That's gonna run once a second, and it's gonna set the value to the current date.

And then inside our stop function. We'll just clear the interval. So now we have this time value that we can use anywhere in our application, and it will always be perfectly consistent. We don't need to have this setup and teardown logic inside each individual component. It's all encapsulated within the store itself.

Sometimes you might have stores whose value is derived from other stores. So in this case, perhaps we wanna have a store that tells us how long the current page has been open. And we can derive that value from the time store, go back into stores.js and underneath the time declaration We're gonna create an elapsed store.

It's been scaffolded out already, we just need to add the logic. An elapsed store takes one or more stores as its input value, and then it has a callback which is passed those values whenever they change and can return a new value. Math.round (time- start)/1000. So now time and elapsed are perfectly in sync, you'll notice that I'm using the dollar prefix here.

Even though I'm in a JavaScript module, there's no syntactical requirement to do that. It's just something that helps keep it consistent with your components. And so it's a convention that I like to use. So here we're deriving a value from a single store you can derive a store from multiple other stores and you can also do things asynchronously if you need to.

If you want to do that sort of thing, then consult the API reference and all will be revealed.
>> If we go back to the readable store as one. Can you help clarify the purpose in the stores.js file of the stop function and what.
>> Yeah.
>> I believe that is doing in this?

>> So if I were to call time.subscribe, and then do something with the current time like log it. Then we need to get this value somehow, but by default this function isn't gonna run, this function is only gonna run once the ones we call .subscribe. Because, a lot of the time like this could be wasted effort.

You might have a whole bunch of stores that aren't actually being used at the moment. And so rather than wasting the user's battery on, like, getting the geolocation information or whatever. We wait until that information is actually requested. So this function doesn't get called immediately. It only gets called when we subscribe.

If we were to get the unsubscribe handle and then later on, call that. Well at that point we need to get rid off that interval, otherwise it's just going to keep ticking forever. So this function will get called when the first subscriber starts subscribing, and if subsequent things subscribe to the same store then that function doesn't get re-invoked.

But as soon as everything that is currently subscribing to the store stops subscribing, that's when we call the stop function. So start gets run when the first subscriber arrives, stop gets run when the last subscriber leaves. Does that make sense? You can imagine that we're not using this and we don't have this, this function will not run.

And we can verify that by adding an alert, right? We're importing it. We're declaring it. But this function is not running. But if I then uncomment that, now the function is running. So we have a subscriber, we have one subscriber. If I then do this, we have two subscribers.

And it recalled the alert because it's refreshed the entire module. But if you have multiple subscribers normally, then you wouldn't see two alerts, you'd only see one. So right now, it's tracking two subscribers. And then if we unsubscribe from that and we get rid of this component, then it's gonna just basically clear the interval, right?

Because no one cares about the time anymore once everything is unsubscribed. Yeah, sorry, that was not the clearest explanation, but all you need to know is that you need to add your setup code and your teardown code and the svelte will manage the rest.
>> In clearInterval(interval) isn't interval a closure?

>> So you're returning the stop function from the start function. So yes, stop is a closure. It closes over the interval that you're declaring in the start function. So you're not passing two functions to readable. It's not like we've got one of these and then one of these, you're returning the stop from start.

So anything that you declare inside start, you can reference in stop. All right, let's talk about custom stores. As I said earlier, a store is just an object that has a subscribe method. There's nothing that special about it. So as long as an object correctly implements that method, it's a store.

And beyond that, you can do whatever you want. So we can use stores for domain specific logic. So in the case of our count store, maybe you want a lot of different things to have a reference to that store. But you don't want everyone to be able to set it to arbitrary values.

You don't wanna be able to set the value to a string or something. So you can restrict what people can do with that function, with that store by not exposing the set, and update functions, but instead exposing your own functions. So, inside stores.js, we have some scaffolding where we're creating a custom store, we have the subscribe function which is returned from writable.

And we're just returning that from create count so that it can be used as a regular store so that you can use it inside your markup like this. The count is 0. We're not doing anything differently here. We're just referencing that the same way that we would reference any other store.

But instead of returning the set and update methods, we're returning some methods of our own creation, increment, decrement, and reset. At the moment, they're not wired up, so they're not doing anything. Let's fix that. Now increment is gonna update n to n + 1. Decon is going to update n to n- 1.

And reset is going to set the value to 0. And now inside app.svelte we can just pass those methods to our event handler, And interact with the store that way. Finally, just like we can bind the value of things like inputs and text areas to local state, we can bind to stores.

As long as the store is writable, it's created with the writable helper from Svelte store, then we can bind to it. So, take the text binding here right now, nothing's happening. But if we change that to a binding then the value of name is now bound to the content in the input.

If we also add an event handler, we can assign to the value of name in the same way that we'd assign to any other value inside this Svelte component. There we go. Right you can use name.set, or you can use dollar name + =. It's really just a stylistic preference.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now