Welcome back to Part 2 of our blog series on our Svelte journey, where we look into all of Svelte’s peculiarities and features as our team discovered them after first six months of using Svelte.
The previous time, we discussed how Svelte manages state within the component hierarchy. We’ll investigate Svelte’s handling of state outside of the component hierarchy this time. Developers can accomplish this using Svelte in one of two ways: by using context API or storage.
The two alternatives differ primarily in that stores are available throughout the entire application, including all modules and/or components, but the context API is only available to the individual components. Stores are also reactive, whereas context is not.
Store in Svelte is simply an object that has a _subscribe_ method that allows interested parties to be notified whenever the store value changes. Svelte has 3 different store types:
– writable
– readable
– derived
Writable stores allow us to read and write data, whereas readable and derived allow us to only read the stored data. For example we would create a store like by calling the appropriate function from the Svelte “store” module:
// ./store.js
import { writable } from 'svelte/store';
export const count = writable(0);
To subscribe to a store we can simply call the subscribe method which exposes the value stored to be referenced:
<script> import { count } from './store.js'; let countValue; count.subscribe((value) => { countValue = value; }); </script> <h1>The count is {countValue}</h1>
Unfortunately, this example won’t work without potentially dangerous side effects. If we were to run this example in our app, we would induce a risk of memory leaks since stores need to be unsubscribed from when the component is destroyed. All we would need to do is declare an unsubscribe function that is returned by the subscribe function.
<script>
import { count } from './store.js';
let countValue;
const unsubscribe = count.subscribe((value) => {
countValue = value;
});
</script>
<h1>The count is {countValue}</h1>
Once we have the unsubscribe function we can use it inside of the onDestroy lifecycle hook which is just a function that runs when our component is destroyed.
<script>
import { count } from './store.js';
let countValue;
const unsubscribe = count.subscribe((value) => {
countValue = value;
});
onDestroy(() => {
unsubscribe();
});
</script>
<h1>The count is {countValue}</h1>
The unfortunate thing is that our component is going to get a bit messy with multiple stores. The Svelte team recognized the potential for components ridden with boilerplate and developed a feature that automatically subscribes and unsubscribes to stores by referencing them with a $ reserved character. Because of that, we are prevented from declaring variable names starting with “$”. The previous example using auto-subscriptions would look like this:
<script>
import { count } from './stores.js';
</script>
<h1>The count is {$count}</h1>
Also, auto-subscription only works with store variables that are declared (or imported) at the top-level scope of a component. Since we created a writable store, we can also _set_ or _update_ the value of the store, which will reactively update and trigger a re-render of view elements that depend on it.
But, not all stores should allow their values to be changed by each entity that has the reference to it. That’s why we have readable stores. For example, we can create a readable store that represents the current time:
Also, auto-subscription only works with store variables that are declared (or imported) at the top-level scope of a component. Since we created a writable store, we can also _set_ or _update_ the value of the store, which will reactively update and trigger a re-render of view elements that depend on it.
But, not all stores should allow their values to be changed by each entity that has the reference to it. That’s why we have readable stores. For example, we can create a readable store that represents the current time:
import { readable } from 'svelte/store';
export const time = readable(new Date(), function start(set) {
const interval = setInterval(() => {
set(new Date());
}, 1000);
return function stop() {
clearInterval(interval);
};
});
The first argument that our store requires is a default value and a start function that runs when the store gets the first subscriber and returns a stop function that runs when the last subscriber unsubscribes.
Also we have derived stores who base their values on one or more other stores. Building on our previous example, we can create a store that derives the time the page has been open:
export const elapsed = derived(time, ($time) => Math.round(($time - start) / 1000));
As we mentioned store is an object that implements the subscribe method that Svelte internals have the reference to. Only way to achieve that is without hacking Svelte internals would be to use mentioned functions that create stores, but on their own they lack the ability to provide additional functionality and logic to which they are coupled. That’s why we have the ability to make custom stores. Building upon the first example we could bind to the custom store the functionality that would build upon the _set_ and _update_ methods that the store provides and not expose but rather the more specific methods that alter the store value in the way we intend.
function createCount() {
const { subscribe, set, update } = writable(0);
return {
subscribe,
increment: () => update((n) => n + 1),
decrement: () => update((n) => n - 1),
reset: () => set(0)
};
}
What we get in the end is the object that implements the subscribe method and 3 domain-specific methods tightly coupled to our store, which should be the preferred way to expose the store values and their coupled functionality.
That being said, Svelte stores in our experience, were an amazing tool to work with, allowing us to write concise and expressive code that exists to help us achieve desired functionality and not to weigh on the project with boilerplate that takes up space and means nothing. To complete the story of Svelte’s state management capabilities, we still need to cover the context API, so hang tight until the next one.
Josip Ante Cindrić

Software Engineer who joined our team because it seemed like a great opportunity to develop great things while growing as a person in a healthy environment. He loves dogs and board games.