Svelte stores are great for managing state in an application but when combined with modern browser features. they really shine. Let’s take a look at how I implemented localStorage in a recent project with an annotated sample file:
$stores/local.js
import { browser } from '$app/environment';
import { writable } from 'svelte/store';
// it works with readable stores too!
// create an object w/default values
let goals = {
goal1: 2000,
goal2: 50
};
// ensure this only runs in the browser
if (browser) {
// if the object already exists in localStorage, get it
// otherwise, use our default values
goals = JSON.parse(localStorage.getItem('goals')) || goals;
}
// export the store for usage elsewhere
export const goalStore = writable(goals);
if (browser) {
// update localStorage values whenever the store values change
goalStore.subscribe((value) =>
// localStorage only allows strings
// IndexedDB does allow for objects though... 🤔
localStorage.setItem('goals', JSON.stringify(value))
);
}
Throughout the rest of the app, the “goalStore” (and therefore, the localStorage object) can be accessed by importing in components like so:
<script>
import { goalStore } from '$stores/local';
import { browser } from '$app/environment';
</script>
<!-- prevent issues with SSR by only rendering dependent components in browser based environment -->
{#if browser}
<!-- use '$' for reactivity and 'bind:' to keep data upstream in sync -->
<Component bind:count={$goalStore.goal1} />
{/if}
As mentioned by Marc Rodney Tompkins in the comments, it may be necessary to wrap your code in browser checks as well. Svelte makes this easy with conditional {#if}
blocks.
I hope this helps someone! If you’d like to see how I implemented this in its entirety, you can view the source code here.
2 replies on “Svelte stores in localStorage”
This probably should have been obvious, but it took me a while to work out:
Not only do you need to browser-guard the workings of the store, but ALSO every reference in your .svelte files. This line:
will work when the page is first loaded (and for the life of me I don’t know why – it shouldn’t!) but if you refresh the page you will get
“TypeError: Cannot read properties of null (reading ‘goal1’)” on the server/in the dev console.
LocalStorage and SSR do _not_ mix. There may be some more sophisticated way around this, but I just wrapped my whole form in {#if browser} {/if} and it works now.
Great point! I’ll update the post to make mention of that.