Is there any way to 'watch' for localstorage in Vuejs? - javascript

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?

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

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.

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.

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
adds a mixin to make initialState available in all Vue instances, and
watches for changes and stores them.
Disclaimer: I'm the author of vue-persistent-state.

you can do it in two ways,
by using vue-ls and then adding the listener on storage keys, with
Vue.ls.on('token', callback)
or
this.$ls.on('token', callback)
by using storage event listener of DOM:
document.addEventListener('storage', storageListenerMethod);

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

Related

vue.js: When implementing the "Simple State Management from Scratch" approach, how to pass on/access the store?

This may be a really dumb question, but after reading the state management documentation of vue.js, i'd like to play around with the store pattern.
I noticed that the store.state is shared among the two apps in the example. But how would i now call the setMessageAction method of the store from within a component? Shouldn't the store be somehow injected into/registered with the vue instance in order to be accessible via this from within a component or something like that?
Yes, you are correct.
You should declare your store in your component declaration as described here
document.js
var store = {
debug: true,
state: {
message: 'Hello!'
},
setMessageAction (newValue) {
if (this.debug) console.log('setMessageAction triggered with', newValue)
this.state.message = newValue
},
clearMessageAction () {
if (this.debug) console.log('clearMessageAction triggered')
this.state.message = ''
}
}
var vmA = new Vue({
data: {
privateState: {},
<!-- HERE YOU ARE PASSING THE STATE -->
sharedState: store.state
}
})

How to reload localStorage VueJS after using this.$router.go(-1);

This is my Login.vue:
mounted() {
if (localStorage.login) this.$router.go(-1);
},
methods: {
axios.post(ApiUrl + "/login") {
...
}
then(response => {
...
localStorage.login = true;
this.$router.go(0); /* Reload local storage */
})
}
App.vue:
mounted() {
axios
.get("/user")
.then(response => {
localStorage.user_id = response.data.user.id;
localStorage.package_id = response.data.user.package_id;
})
},
Project.vue:
mounted() {
this.user_id = localStorage.user_id
this.package_id = localStorage.package_id
}
With that above code, I cannot get localStorage.user_id and localStorage.package_id as I expected. But if I change like the follow, it worked.
mounted() {
const self = this
setTimeout(function () {
self.user_id = localStorage.user_id
self.package_id = localStorage.package_id
self.getProject();
},1000)
}
But I think setTimeout not good in that case. Is there any way to refactor this code?
Thank you!
Try this: in your root component (it's usually const app = new Vue({ ... })) write the following:
import {localStorage} from 'localStorage'; // import your module if necessary
// this is relative to the way you manage your dependencies.
const app = new Vue({
//...
data: function() {
return {
localStorage: localStorage;
}
}
})
Now whenever you want to use localStorage, access it from root component like this:
this.$root.localStorage
Hope this solves your problem.
Don't know your project structure ,but I guess it's probably an async issue. You got the user information async, so when Project.vue mounted, the request is not complete yet. As a result, the localstorage is empty at the monent.
There are two solutions for this:
Make sure Project.vue is not rendered before userinfo is complete. For example, things like <project v-if="userinfo.user_id" /> should works.
Use some data binding libary like vuex to bind userinfo to Project.vue instead of assign it in lifecycle like mounted or created.
Hope it helps.

Create Global Function For Checking If LocalStorage is not empty - Vue.JS

I'm just wondering how to create a global function wherein it checks whether the localStorage is not empty specifically if there's a token inside
What I've tried is creating a global variable in my main.js
Vue.$checkIfTokenIsNotEmpty = !!localStorage.getItem('token');
// this returns true or false
In my component,
<template>
Is token Empty? {{ isTokenIsEmpty }} // I'm able to get true or false here
</template>
<script>
export default {
data(){
return {
isTokenIsEmpty: this.$checkIfTokenIsNotEmpty
}
}
}
</script>
This works properly when I reload the page. The problem is, it's not reactive or real time. If I clear the localStorage, the value of my this.$checkIfTokenIsNotEmpty doesn't change. It only changes when I reload the page which is bad in my spa vue project.
You can acces token like here: https://jsfiddle.net/djsj8dku/1/
data: function() {
return {
world: 'world',
get token() {
return localStorage.getItem('token') || 0;
},
set token(value) {
localStorage.setItem('token', value);
}
};
}
Or you can use one of this packages: vue-reactive-storage, vue-local-storage
You cannot detect when localStorage is wiped out manually but you can watch when localStorage is updated. So watcher is what you need. Link
Regarding global function you can set a method & variable inside root component.
new Vue({
el:"#app",
data:{
isTokenIsEmpty:null
},
methods: {
checkIfTokenIsNotEmpty() {
this.isTokenIsEmpty= !!localStorage.getItem('token');
}
}
})
Inside component,
mounted(){
this.$root.checkIfTokenIsNotEmpty() //can be added anywhere when needed to check localStorage's Availablity
}
Html
<template> Is token Empty? {{ $root.isTokenIsEmpty }} // I'm able to get true or false here </template>

Vue.js access component method or parent method?

I'm new to Vue and managed to make my first app with some glitches but I'm really enjoying it so far. I used a video tutorial which jump started with vue-cli project creation which as turns out is a litte different due to webpack.
I've created the project, the project does mostly what it should right now I'm trying to do some refactoring which includes DRYing out the code.
On each page I would like to access a variable stored in the cookie file I've done the saving and reading on the HomeComponent in the script section which works as promised.
<script>
import MenuComponent from '#/components/MenuComponent.vue'
import Typewriter from '#/components/vue-type-writer.vue'
export default {
name: 'HomeComponent',
components: {
MenuComponent,
Typewriter
},
prop:{
isPlaying: Boolean,
username: String,
currentSound: Object
},
data() {
return{
currentSound: null,
isPlaying: false,
username: ''
}
},
methods:{
clickButton() {
this.msg= 'test 2'
},
toggleSound(){
var a = this.currentSound;
if (a.paused) {
a.play();
this.isPlaying = true;
} else {
a.pause();
this.isPlaying = false;
}
},
getCookieInfo(){
var value = "; " + document.cookie;
var parts = value.split("; weegreename=");
if (parts.length == 2)
this.username = parts.pop().split(";").shift();
else this.username = '';
},
seveFormValues (submitEvent) {
this.username = submitEvent.target.elements.name.value;
this.$refs.audio1.pause();
this.$refs.audio2.play();
var expires = "";
var days = 31;
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days*24*60*60*1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = "weegreename=" + (this.username || "") + expires + "; path=/";
}
},
mounted(){
this.isPlaying = true;
this.getCookieInfo();
if (this.username) this.currentSound = this.$refs.audio2;
else this.currentSound = this.$refs.audio1;
this.currentSound.play();
}
}
</script>
Now on every sub page I would like to access the getCookieInfo() method to check id the username is set.
I've tried to add this in the main App.vue script section, in the main.js
new Vue({
router,
render: h => h(App),
methods: {
//here the getCookieInfo code from above
}
}).$mount('#app')
created a new component whit the methods and then tried to access them in the main app via componentname.method as below.
import CookieComponent from '#/components/CookieComponent.vue'
export default {
// prop:{
// isToggled: Boolean
// },
components: {
MenuComponent,
CookieComponent
},
data() {
return{
isToggled: false
}
},
methods:{
clickToggle() {
this.isToggled = !this.isToggled;
},
},
mounted(){
CookieComponent.getCookieInfo();
}
}
I don't know right now the best approach and I will learn more in the future but this project is time sensitive - I decided to learn vue by making a simple site for my client :)
If you need it on every page it can be put into your App.vue. From there you have three options:
Pass the data as a prop to child components.
Create an event bus and emit the data to whichever component needs it.
Use Vuex to store the data and access it from your components.
If you really want to keep your cookie data inside the component you need to emit it up your component chain.
https://v2.vuejs.org/v2/guide/components.html#Emitting-a-Value-With-an-Event
Depending on how deep your chain goes and how many sibling components you have this can get really messy and in those cases Vuex or an event bus might be a better idea.
Do not try to do things like:
CookieComponent.getCookieInfo();
Please review the documentation to see good example on how to do component communication.
For that kind of stuff, the best practice is to use a state. It will save data of your application and will allow you to access them accross all components/pages.
You can see a simple state management in the Vue doc, or directly use VueX, the official state management library for Vue.
To sum up how it works (with VueX):
You create a cookieStore:
// Where data will be saved
const state = { cookie: {} }
// Getters allow you to access data
const getters = { cookie: state => state.cookie }
// Mutations allow you to modify the state
const mutations = {
// Set cookie data
saveCookie (state, cookieData) {
state.cookie = cookieData
}
}
In your HomeComponent, you will get the cookie info, and save it in
the store: this.$store.commit('saveCookie', cookieData)
In all other components, instead of getting the cookie info from the cookie, you can access the saved data from the store and do what you want with it: this.$store.getters.cookie

How to get the latest data from parent to child components after page refresh

I am working on a project and using Vue.js for the frontend. I have following code in the main.js file.
new Vue({ // eslint-disable-line no-new
//el: '#app',
router,
data () {
return {
friends: []
}
},
methods: {
getFriends: function () {
return this.friends;
}
},
created: function () {
this.$http.get('/user/' + this.getUserIDCookie('userID') +
'/friends').then(function (response) {
this.friends = response.data;
});
},
components: {
'nav-bar': require('./components/Navigation.vue')
},
template: `
<div id="app">
<nav-bar></nav-bar>
<router-view class="router-view"></router-view>
</div>`
}).$mount('#app');
In one of the pages(for ex. when the page is redirected to localhost/#/user/1/details, I am retrieving the friends' list from main.js like below:
<script type="text/babel">
export default {
name: 'profile',
data: function () {
return {
user: {},
friends: []
}
},
methods: {
// Some methods
},
created: function () {
this.friends = this.$root.getFriends();
}
}
</script>
The problem arises when I refresh the current page. After page refresh, this.friends is null/undefined because this.$root.getFriends() is returning null/undefined. I can move it to user component, but I want to keep it in main.js so that GET call is used once and data will be available to the whole application.
Any input regarding how to solve this issue would be great. I am using Vue 2.0.1
Really, what you want to do, is pass the data the component needs as props.
The dirt simple easiest way to do it is this.
<router-view class="router-view" :friends="friends"></router-view>
And in your profile component,
export default {
props:["friends"],
name: 'profile',
data: function () {
return {
user: {},
friends: []
}
},
methods: {
// Some methods
}
}
If you want to get more sophisticated, the later versions of VueRouter allow you to pass properties to routes in several ways.
Finally, there's always Vuex or some other state management tool if your application gets complex enough.
The problem is that when you refresh the page, the whole app reloads, which includes the get, which is asynchronous. The router figures out that it needs to render details, so that component loads, and calls getFriends, but the asynchronous get hasn't finished.
You could work around this by saving and pulling the Promise from the get, but Bert's answer is correct: the Vue Way is to send data as props, not to have children pull it from parents.

Categories