How to persist svelte store

Solution 1:

You can manually create a subscription to your store and persist the changes to localStorage and also use the potential value in localStorage as default value.

Example

<script>
  import { writable } from "svelte/store";
  const store = writable(localStorage.getItem("store") || "");

  store.subscribe(val => localStorage.setItem("store", val));
</script>

<input bind:value={$store} />

Solution 2:

From https://github.com/higsch/higsch.me/blob/master/content/post/2019-06-21-svelte-local-storage.md by Matthias Stahl:

Say we have a store variable called count.

// store.js
import { writable } from 'svelte/store';

export const count = writable(0);

// App.svelte
import { count } from 'store.js';

In order to make the store persistent, just include the function useLocalStorage to the store object.

// store.js
import { writable } from 'svelte/store';

const createWritableStore = (key, startValue) => {
  const { subscribe, set } = writable(startValue);
  
  return {
    subscribe,
    set,
    useLocalStorage: () => {
      const json = localStorage.getItem(key);
      if (json) {
        set(JSON.parse(json));
      }
      
      subscribe(current => {
        localStorage.setItem(key, JSON.stringify(current));
      });
    }
  };
}

export const count = createWritableStore('count', 0);

// App.svelte
import { count } from 'store.js';

count.useLocalStorage();

Then, in your App.svelte just invoke the useLocalStorage function to enable the persistent state.

This worked perfectly for me in Routify. For Sapper, JHeth suggests "just place count.useLocalStorage() in onMount or if (process.browser) in the component consuming the store. "

Solution 3:

For Svelte Kit I had issues with SSR. This was my solution based on the Svelte Kit FAQ, the answer by Matyanson and the answer by Adnan Y.

As a bonus this solution also updates the writable if the localStorage changes (e.g. in a different tab). So this solution works across tabs. See the Window: storage event

Put this into a typescript file e.g. $lib/store.ts:

import { browser } from '$app/env';
import type { Writable } from 'svelte/store';
import { writable, get } from 'svelte/store'

const storage = <T>(key: string, initValue: T): Writable<T> => {
    const store = writable(initValue);
    if (!browser) return store;

    const storedValueStr = localStorage.getItem(key);
    if (storedValueStr != null) store.set(JSON.parse(storedValueStr));

    store.subscribe((val) => {
        if ([null, undefined].includes(val)) {
            localStorage.removeItem(key)
        } else {
            localStorage.setItem(key, JSON.stringify(val))
        }
    })

    window.addEventListener('storage', () => {
        const storedValueStr = localStorage.getItem(key);
        if (storedValueStr == null) return;

        const localValue: T = JSON.parse(storedValueStr)
        if (localValue !== get(store)) store.set(localValue);
    });

    return store;
}

export default storage

This can be used like this:

import storage from '$lib/store'

interface Auth {
    jwt: string
}

export const auth = storage<Auth>("auth", { jwt: "" })

Solution 4:

In case someone needs to get this working with JavaScript objects:

export const stored_object = writable(
    localStorage.stored_object? JSON.parse(localStorage.stored_object) : {});
stored_object.subscribe(val => localStorage.setItem("stored_object",JSON.stringify(val)));

The benefit is that you can access the writable object with the $ shorthand, e.g.

<input type="text" bind:value={$stored_object.name}>
<input type="text" bind:value={$stored_object.price}>

Solution 5:

You may want to also check this one out https://github.com/andsala/svelte-persistent-store

Also, if you use sapper and don't want something to run on the server, you can use the onMount hook

onMount(() => {
  console.log('I only run in the browser');
});