54

I'm attempting to watch for localstorage:

Template:

<p>token - {{token}}</p>

Script:

computed: {
  token() {
    return localStorage.getItem('token');
  }
}

But it doesn't change, when token changes. Only after refreshing the page.

Is there a way to solve this without using Vuex or state management?

thanksd
  • 54,176
  • 22
  • 157
  • 150
artem0071
  • 2,369
  • 3
  • 14
  • 25

6 Answers6

43

localStorage is not reactive but I needed to "watch" it because my app uses localstorage and didn't want to re-write everything so here's what I did using CustomEvent.

I would dispatch a CustomEvent whenever you add something to storage

localStorage.setItem('foo-key', 'data to store')

window.dispatchEvent(new CustomEvent('foo-key-localstorage-changed', {
  detail: {
    storage: localStorage.getItem('foo-key')
  }
}));

Then where ever you need to watch it do:

mounted() {
  window.addEventListener('foo-key-localstorage-changed', (event) => {
    this.data = event.detail.storage;
  });
},
data() {
  return {
    data: null,
  }
}
Salam
  • 1,126
  • 14
  • 20
31

Sure thing! The best practice in my opinion is to use the getter / setter syntax to wrap the localstorage in.

Here is a working example:

HTML:

<div id="app">
  {{token}}
  <button @click="token++"> + </button>
</div>

JS:

new Vue({
  el: '#app',
  data: function() {
    return {
      get token() {
        return localStorage.getItem('token') || 0;
      },
      set token(value) {
        localStorage.setItem('token', value);
      }
    };
  }
});

And a JSFiddle.

FitzFish
  • 8,557
  • 2
  • 30
  • 39
  • Good alternate, but in the case compiler keep on hitting again and again one hit to check whether value changes or not. – Pardeep Jain Dec 18 '17 at 12:52
  • Interestingly, eslint gives me the error `vue/no-dupe-keys` on the second entry (getter or setter). I suppressed it with `// eslint-disable-line vue/no-dupe-keys` which seems to work. – MattCochrane May 24 '18 at 09:31
  • 1
    Also, localStorage seems to convert ints to strings so I needed to run `return parseInt(localStorage.getItem('userSessionIndex') || 0)` for it to work. Others may or may not run into this problem. – MattCochrane May 24 '18 at 09:32
  • 2
    @MattClimbs Everyone will run into that because that's how Local Storage work. https://stackoverflow.com/questions/2010892/storing-objects-in-html5-localstorage – user2875289 Jun 10 '18 at 14:52
  • @Cobaltway Do you know, why it does not work very well using the same logic inside computed? If using inside data like you did, everything works as expected. – Tarvo Mäesepp Jun 14 '19 at 11:15
  • 10
    The only reason this would work is because every time it is clicked, the get value is called to retrieve then the set is called to modify. It is not reactive when other sources modify the localstorage. – Kody Jan 06 '21 at 23:05
10

The VueJs site has a page about this. https://v2.vuejs.org/v2/cookbook/client-side-storage.html

They provide an example. Given this html template

<template>
  <div id="app">
    My name is <input v-model="name">
  </div>
<template>

They provide this use of the lifecycle mounted method and a watcher.

const app = new Vue({
  el: '#app',
  data: {
    name: ''
  },
  mounted() {
    if (localStorage.name) {
      this.name = localStorage.name;
    }
  },
  watch: {
    name(newName) {
      localStorage.name = newName;
    }
  }
});

The mounted method assures you the name is set from local storage if it already exists, and the watcher allows your component to react whenever the name in local storage is modified. This works fine for when data in local storage is added or changed, but Vue will not react if someone wipes their local storage manually.

tony19
  • 125,647
  • 18
  • 229
  • 307
Lucas
  • 1,149
  • 1
  • 9
  • 23
7

Update: vue-persistent-state is no longer maintained. Fork or look else where if it doesn't fit your bill as is.

If you want to avoid boilerplate (getter/setter-syntax), use vue-persistent-state to get reactive persistent state.

For example:

import persistentState from 'vue-persistent-state';  

const initialState = {
  token: ''  // will get value from localStorage if found there
};
Vue.use(persistentState, initialState);

new Vue({
  template: '<p>token - {{token}}</p>'
})

Now token is available as data in all components and Vue instances. Any changes to this.token will be stored in localStorage, and you can use this.token as you would in a vanilla Vue app.

The plugin is basically watcher and localStorage.set. You can read the code here. It

  1. adds a mixin to make initialState available in all Vue instances, and
  2. watches for changes and stores them.

Disclaimer: I'm the author of vue-persistent-state.

tony19
  • 125,647
  • 18
  • 229
  • 307
arve0
  • 3,424
  • 26
  • 33
2

you can do it in two ways,

  1. by using vue-ls and then adding the listener on storage keys, with

        Vue.ls.on('token', callback)
    

    or

        this.$ls.on('token', callback)
    
  2. by using storage event listener of DOM:

        document.addEventListener('storage', storageListenerMethod);
    
Yash Ojha
  • 792
  • 9
  • 17
0

LocalStorage or sessionStorage are not reactive. Thus you can't put a watcher on them. A solution would be to store value from a store state if you are using Vuex for example. Ex:

SET_VALUE:(state,payload)=> {
    state.value = payload
    localStorage.setItem('name',state.value)
    or
    sessionStorage.setItem('name',state.value)
  }
Stephan Ngaha
  • 121
  • 1
  • 3