Anil Sivadas
Anil Sivadas

Reputation: 1698

How to persist svelte store

Is there any direct option to persist svelte store data so that even when the page is refreshed, data will be available.

I am not using local storage since I want the values to be reactive.

Upvotes: 66

Views: 31926

Answers (13)

fbjorn
fbjorn

Reputation: 890

Found a library called svelte-persisted-store that implements this functionality. Worked for me.

Install:

npm install svelte-persisted-store

Example from their README:

// in store.ts or similar
import { persisted } from 'svelte-persisted-store'

// First param `preferences` is the local storage key.
// Second param is the initial value.
export const preferences = persisted('preferences', {
  theme: 'dark',
  pane: '50%',
  ...
})


// in views

import { get } from 'svelte/store'
import { preferences } from './stores'

preferences.subscribe(...) // subscribe to changes
preferences.update(...) // update value
preferences.set(...) // set value
get(preferences) // read value
$preferences // read value with automatic subscription

Upvotes: 5

Saif-Alislam Dekna
Saif-Alislam Dekna

Reputation: 107

You may do it like this:

import { writable } from 'svelte/store';
import { browser } from '$app/environment';

// check if the item exists in local storage, if so, return the item, otherwise, return null. (This is to avoid errors on initial reads of the store)
// browser && makes sure the command only works in the client side (browser).
const get_local_storage =
    browser && localStorage.getItem('presisted_local_store')
        ? browser && localStorage.getItem('presisted_local_store')
        : null;
// create a writable store
export const presisted_local_store = writable(JSON.parse(get_local_storage));
// create a subscribe method for the store to write back to the local storage (again, on the browser)
presisted_local_store.subscribe((value) => {
    browser && localStorage.setItem('presisted_local_store', JSON.stringify(value));

Upvotes: 1

Vytenis
Vytenis

Reputation: 51

In SvelteKit the official method is called Snapshot:

https://kit.svelte.dev/docs/snapshots

Upvotes: 0

mavdotjs
mavdotjs

Reputation: 75

copied this code from one of my projects $lib/savable.ts

import type { Writable, StartStopNotifier, Unsubscriber } from 'svelte/types/runtime/store';
import { writable } from 'svelte/store';

const attach = (writable: Writable<unknown>, key='store'): void =>{
    const json = localStorage.getItem(key);
    if (json) {
       writable.set(JSON.parse(json));
    }

    writable.subscribe(current => {
        localStorage.setItem(key, JSON.stringify(current));
    });
}
interface Savable<T> extends Writable<T> {
    mount(localstore: Storage): void
    dismount(localstore: Storage): JSON
    unsub: Unsubscriber
}
function savable<T>(key: string, value?: T, start?: StartStopNotifier<T>): Savable<T>{
    const base = writable(value, start)
    return {
        ...base,
        mount(localstore) {
            if(this.mounted) throw new Error("Already mounted");
            this.mounted = true;

            const json = localstore.getItem(key);
            if (json) {
                base.set(JSON.parse(json));
            }

            this.unsub = base.subscribe(current => {
                localStorage.setItem(key, JSON.stringify(current));
            });
            console.log(this)
        },
        dismount(localstore) {
            if(!this.mounted) throw new Error("Not mounted");
            const json = JSON.parse(localstore.getItem(key))
            this.unsub()
            localstore.removeItem(key)
            return json
        },
        unsub() {
           throw new Error('Cannot unsubscribe when not subscribed')
        }
    }
}
export {
    attach,
    savable,
};
export type {
    Savable
}
export default savable

here is an example of a savable being used in index.svelte

<!—- Typescript is not required  —->
<script lang=ts>
    import savable from `$lib/savable`;
    const value = savable(‘input_value’);
    import { onMount } from ‘svelte’;
    onMount(()=>{
        value.mount()
    })
</script>

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

Upvotes: 0

tarasinf
tarasinf

Reputation: 886

Works for me with svelte version 3.44.1.

src/store.js file:

import { writable } from "svelte/store";
import { browser } from "$app/env"

export const fontSize = writable(browser && localStorage.getItem("fontSize") || "15");
fontSize.subscribe((value) => {
    if (browser) return localStorage.setItem("fontSize", value)
});

Upvotes: 0

Spenhouet
Spenhouet

Reputation: 7239

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: "" })

Upvotes: 18

Matyanson
Matyanson

Reputation: 321

This function synchronises svelte store with localStorage. If there is no value stored it takes the initValue parameter instead.

I also added Typescript.

import { writable, Writable } from 'svelte/store';

const wStorage = <T>(key: string, initValue: T): Writable<T> => {
    const storedValueStr = localStorage.getItem(key);
    const storedValue: T = JSON.parse(storedValueStr);

    const store = writable(storedValueStr != null ? storedValue : initValue);
    store.subscribe((val) => {
        localStorage.setItem(key, JSON.stringify(val));
    })
    return store;
}

export default wStorage;

You can then use the function elsewhere like you are used to with writable:

const count = wStorage<number>('count', 0);

Edit: If you are using SSR in your app and don't want to use onMount or check if (process.browser) for every writable method. Here is a modified version:

const wStorage = <T>(key: string, initValue: T): Writable<T> => {
    const store = writable(initValue);
    if (typeof Storage === 'undefined') return store;

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

    store.subscribe((val) => {
        localStorage.setItem(key, JSON.stringify(val));
    })
    return store;
}

Upvotes: 2

europrimus
europrimus

Reputation: 55

With svelte 3.38 and svelte-kit (Sapper's succesor) , I use:

<script>
  import { onMount } from 'svelte';
  import { writable } from "svelte/store";

  let value;

  onMount(() => {
    value = writable(localStorage.getItem("storedValue") || "defaut value");
    value.subscribe(val => localStorage.setItem("storedValue", val));
  })
</script>

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

localStorage isn't available out of onMount()

Upvotes: 2

Adnan Y
Adnan Y

Reputation: 3250

TLDR: Here is a function that takes care of not only setting and getting, but also deletion.

function persistent(name) {
    const value = writable(localStorage.getItem(name));
    value.subscribe(val => [null, undefined].includes(val) ? localStorage.removeItem(name) : localStorage.setItem(name, val));
    return value;
}


export const my_token = persistent('token');

Reasoning: Contrary to intuition, localStorage.setItem('someval', null) would not set return null for the next localStorage.getItem('someval') but "null" which is likely not what one would want. Thus, this also checks for undefined and null and deletes the item accordingly.

Upvotes: 4

Leon
Leon

Reputation: 346

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}>

Upvotes: 4

mic
mic

Reputation: 1266

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. "

Upvotes: 15

Dmitry Boychev
Dmitry Boychev

Reputation: 161

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');
});

Upvotes: 2

Tholle
Tholle

Reputation: 112917

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} />

Upvotes: 88

Related Questions