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)
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))
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))
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:
<script>
import { content } from "./store"
</script>
<p>{$content}</p>
<input bind:value={$content} />
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 Dev.to (danawoodman), Twitter (@danawoodman) and/or Github (danawoodman)!
Photo by Joshua Aragon on Unsplash
Top comments (9)
localStorage is not defined. Error
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 thisthis code is running in my code , thank you ,appreciated
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.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 registeringnode-localstorage
before importing the lib that depends onlocalStorage
.The
register
endpoint innode-localstorage
does not overwritelocalStorage
if it already exists, so you still get the native browser implementation.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)
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;
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?developer.mozilla.org/en-US/docs/W...
Great solution thank you!