DEV Community

Cover image for πŸš€ Svelte Quick Tip: Connect a store to local storage
Dana Woodman
Dana Woodman

Posted on • Updated on

πŸš€ Svelte Quick Tip: Connect a store to local storage

Local storage, oh my 🀩

Here's a really quick tip for you today; how to use Svelte stores to keep data in-sync with local storage.

This is particularly useful if you're wanting to persist some user values, say UI configuration (e.g. their preferred theme, something that is shown/hidden, etc) and have the settings retained for future sessions.

Doing this with Svelte is pretty trivial, let's check it out πŸ‘‡

Create the store

All we need to do to connect to local storage is create a writable store and then set a default value based on local storage and on any change (via subscribe) we update the local storage entry.

// src/stores/content.js
import { writable } from 'svelte/store'

// Get the value out of storage on load.
const stored = localStorage.content
// or localStorage.getItem('content')

// Set the stored value or a sane default.
export const content = writable(stored || 'Hello, World!')

// Anytime the store changes, update the local storage value.
content.subscribe((value) => localStorage.content = value)
// or localStorage.setItem('content', value)
Enter fullscreen mode Exit fullscreen mode

The key thing to remember here is local storage always stores strings, so if you're storing something else, say a boolean or some JSON, then you will want to convert to/from the data type you want and the local storage string representation.

For example, if you wanted to store a boolean, it would look more like this:

// src/stores/enabled.ts
import { writable } from 'svelte/store'

export const enabled = writable<boolean>(localStorage.enabled === 'true')

enabled.subscribe((value) => localStorage.enabled = String(value))
Enter fullscreen mode Exit fullscreen mode

Notice that we read the value and compare it to the string 'true' versus treating it like a boolean, which won't work. Also note that we need to convert it to a string before saving it to local storage (especially if we're using Typescript).

If you're working with objects or arrays, you can lean towards using JSON.parse instead:

// src/stores/user.ts
import { writable } from 'svelte/store'

interface User {
  email: string
  username: string

export const enabled = writable<User>(JSON.parse(localStorage.getItem('user')))

enabled.subscribe((value) => localStorage.user = JSON.stringify(value))
Enter fullscreen mode Exit fullscreen mode

Not that we will want to use getItem instead of the property accessor because getItem returns null where as the property accessor returns undefined on missing keys and null is valid with JSON.parse whereas undefined causes it to explode violently with Uncaught SyntaxError: Unexpected token u in JSON at position 0.

Use your store

Now you can use the value in your component:

  import { content } from "./store"


<input bind:value={$content} />
Enter fullscreen mode Exit fullscreen mode

Any time you update the value it will be updated in local storage and when you reload it will automatically be set to the value you had set last. Pretty neat!

That's it!

I told you it would be quick 😎

Hopefully this comes in handy for you, cheers! 🍻

EDIT: Thanks to Luke Edwards (@lukeed05) on Twitter for pointing out you can do localStorage['content'] (or localStorage.content) instead of the more verbose localStorage.getItem('content') and localStorage.content = '...' instead of localStorage.setItem('content', '...')

EDIT 2: Shoutout to Jamie Birch (@LinguaBrowse) on Twitter who mentioned it might be safer to stick with getItem and setItem since they're specifically declared int the local storage spec. It seems safe enough to use the property accessors, but if you want to be extra safe, use getItem and setItem.

EDIT 3: SΓΆren (@the_soerenson) on Twitter pointed out you could take this further by adding event listeners so you could detect local storage changes in other browser tabs/windows. Maybe cool if you're trying to sync offline data across browser tabs?

EDIT 4: Thanks to @JuSellier on Twitter who reminded me we can use JSON.parse on primitive values (boolean, number etc), so I've updated the example to use that instead. Thanks JuSellier!

Thanks for reading! Consider giving this post a ❀️, πŸ¦„ or πŸ”– to bookmark it for later. πŸ’•

Have other tips, ideas, feedback or corrections? Let me know in the comments! πŸ™‹β€β™‚οΈ

Don't forget to follow me on (danawoodman), Twitter (@danawoodman) and/or Github (danawoodman)!

Photo by Joshua Aragon on Unsplash

Top comments (9)

delanyobott profile image
delanyo {Yokoyama} agbenyo

localStorage is not defined. Error

lubiah profile image
Lucretius Biah

In sveltekit, you can check if the piece of code is running on the server or browser, you just have to import the browser variable from the env and then use an if statement to run your code. So it should be something like this

import { browser } from "$app/env";

if (browser){
    variable.subscribe((value) => localStorage.user = JSON.stringify(value))
Enter fullscreen mode Exit fullscreen mode
cepiherd profile image

this code is running in my code , thank you ,appreciated

ryanfiller profile image

I'm doing this in Sapper, I haven't made the switch to Sveltekit yet, so take the with a grain of salt.

I was able to get it work by doing a typeof check and skipping the subscription during SSR.

// stores/user.js

import { writable } from 'svelte/store'
export const user = writable()

// check for localStorage, this won't run on SSR
if (typeof localStorage !== 'undefined') {
  user.subscribe((value) => localStorage.user = JSON.stringify(value))
Enter fullscreen mode Exit fullscreen mode
kenkunz profile image
Ken Kunz

I got the same error while migrating an app to SvelteKit. The FAQ states this is due to how Vite processes imports (even with SSR disabled). See "How do I use a client-side only…" in SvelteKit FAQ as well as Issue #754 True SPA mode.

For now, I was able to get this working by disabling SSR on the page that depends on localStorage and importing & globally registering node-localstorage before importing the lib that depends on localStorage.

import 'node-localstorage/register';
import { storable } from 'svelte-storable';
Enter fullscreen mode Exit fullscreen mode

The register endpoint in node-localstorage does not overwrite localStorage if it already exists, so you still get the native browser implementation.

danawoodman profile image
Dana Woodman

Where are you running this? If it is in SvelteKit localStorage won't be defined so you'll have to only use the store in the browser as documented in the Kit docs (see "browser" variable)

cycle4passion profile image
Scott Rhamy • Edited

Consider tearing down the subscription to prevent memory leak.
Since we are in JS land (not svelte), I would expect onDestroy() use is probably inappropriate, so....

const unsubscribe = content.subscribe((value) => localStorage.content = value)
window.onbeforeunload = unsubscribe;

danawoodman profile image
Dana Woodman

Why would you want to unsubscribe from your store in this context?

Also, as far as I'm aware, onbeforeunload event is fired when a window is being destroyed so there would be no need to unsubscribe since the JS runtime is also destroyed for this page, unless I'm not understanding what you're trying to do?

jaskaransarkaria profile image
Jaskaran Sarkaria

Great solution thank you!