In my component, I am doing :
firebase() {
const userId = firebase.auth().currentUser.uid
return {
race: userRef.child(userId).child('races').child(this.raceKey)
}
},
mounted () {
console.log(this.$firebaseRefs.race.name)
}
I can access the race property's values inside my component's template, but I cannot figure out how to access them inside a created hook or a method. The value is always undefined. How can I do this?
The structure for the race is:
race: {
name: "the name",
.....
}
This is because queries to Firebase Real Time Database are asynchronous, and therefore there is no guarantee that you get the result of your query in a lifecycle hook like mounted. In other words, the Firebase binding of your race object does not finish before the instance is mounted.
See the following posts for more detail and possible workaround with readyCallback:
https://github.com/vuejs/vuefire/issues/70 and https://github.com/vuejs/vuefire/issues/69
Solved it using the readyCallBack function. The problem was mounted triggered before the firebase reference had actually loaded. Thank you Renaud Tarnec for the help.
firebase() {
return {
race: {
source: userRef.child(firebase.auth().currentUser.uid).child('races').child("-LMgzo_50TzlfJwCblDS"),
asObject: true,
cancelCallback: function () {},
readyCallback: function() {
console.log("Firebase race was loaded")
this.renderMap()
}
}
}
}
Related
I have a component which checks the endpoint parameter in the URL and then finds the server that contains the said parameter from inside an array with servers. I am trying to execute this code every time the server computed property is evaluated:
this.$store.dispatch('joinServer', this.server)
this.$store.commit('setSelectedServer', this.server)
I do this by adding watchers. Whenever the route URL changes, the computed server property also changes and I watched for those changes and executed the code like this:
watch: {
$route(to, from) {
this.serverEndpoint = this.$route.params.endpoint
},
server(newServer){
this.$store.dispatch('joinServer', newServer)
this.$store.commit('setSelectedServer', newServer)
}
}
The problem is that with my code, whenever I render the component the first time, the code doesn't execute. If I change the URL endpoint, my watchers take effect and the code does get executed.
So what I'm asking is how can I execute the code after the server computed property is evaluated the first time after I visit the component?
My code:
<script>
import ServerRooms from '../components/ServerRooms'
import Chat from '../components/Chat'
import Members from '../components/Members'
export default {
components: {
ServerRooms,
Chat,
Members
},
data(){
return {
serverEndpoint: this.$route.params.endpoint
}
},
computed: {
server(){
return this.$store.state.servers.find((server) => {
return server.endpoint == '/' + this.serverEndpoint
})
}
},
watch: {
$route(to, from) {
this.serverEndpoint = this.$route.params.endpoint
},
server(newServer){
this.$store.dispatch('joinServer', newServer)
this.$store.commit('setSelectedServer', newServer)
}
}
}
</script>
Try using the immediate keyword when defining your watcher. See vue docs on watch
the callback will be called immediately after the start of the
observation
E.g.
watch: {
server: {
handler(newServer){
console.log('newServer = ' + newServer);
this.$store.dispatch('joinServer', newServer)
this.$store.commit('setSelectedServer', newServer)
},
immediate: true
}
}
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
}
})
I have a simple h3 tag containing a title that is bound to a reactive data property.
I am fetching the value from a Firestore database and assign it to the data property. When I don't reload and access the page through client-side navigation, everything works fine.
However once I reload the title value gets updated properly (seen in console logs and vue dev tools) but the h3-tag remains empty.
Here is the code:
<template>
<h3 #click="displayCoursePreview" class="mt-5">{{ titl }}</h3>
</template>
<script>
props: {
student: {
type: Boolean
}
},
watch: {
rehydrated: {
// Always triggers once store data is rehydrated (seems to work without any problems)
immediate: true,
async handler(newVal, oldVal) {
if (newVal) {
await this.getSections();
return this.getTopics();
}
}
}
},
data() {
return {
titl: null
};
},
computed: {
rehydrated() {
return this.$store.state.rehydrated; // Equals true once store is rehydrated from local storage
}
},
methods: {
getSections() {
console.log('running') // Runs every time
let ref = this.$store.state.courses;
var cid = this.student
? ref.currentlyStudying.cid
: ref.currentlyPreviewing.cid;
// Get Course Title
this.$fireStore
.collection("courses")
.doc(cid)
.get()
.then(doc => {
console.log(doc.data().name) // Logs correct title every time
this.titl = doc.data().name;
this.thumbSrc = doc.data().imgsrc;
})
.catch(err => console.log(err));
}
</script>
I can't figure out why it sometimes displays the title and sometimes does not. Is there another way to bind titl to the content of the h3-tag without the {{}} syntax?
Thank you in advance!
EDIT:
I have changed the {{}} syntax to v-text like so:
<h3 #click="displayCoursePreview" class="mt-5" v-text="titl"></h3>
And now it works every time, even after a hard reload. Can anyone explain the difference and why this works?
To answer the original question it looks like you might have a race condition between this component and the store. The watch will only trigger 'getSections' if it sees a change in this.$store.state.rehydrated after it's been mounted, but the store might have completed that before this component got mounted, so then the watch never gets triggered.
Not sure why switching to v-text would have altered this, maybe it allows the component to mount slightly faster so it's getting mounted before the store completes it's rehydration?
I am using the mapGetters helper from VueX but i have some problem only on the first load of the page, it's not reactive...
Let me show you :
my html template triggering the change :
<input type="number" value="this.inputValue" #change="this.$store.dispatch('setInputValue', $event.target.value)">
my store receiving the value
{
state: {
appValues: {
inputValue: null
},
},
getters: {
getInputValue: (state) => {
return state.appValues.inputValue;
},
},
mutations: {
setInputValue(state, value) {
state.appValues.inputValue = value;
},
},
actions: {
setInputValue(context, payload) {
return new Promise((resolve, reject) => {
context.commit('setInputValue', payload);
resolve();
});
},
}
}
and then my component listening the store :
import {mapGetters} from 'vuex';
computed: {
...mapGetters({
inputValue: 'getInputValue',
}),
}
watch: {
inputValue: {
deep: true,
immediate: true,
handler(nVal, oVal) {
console.log("inputValue", nVal, oVal);
}
},
}
So now, when i first load the page I get this console.log "inputValue" null undefined which is totally normal because as I have nothing in my store it gaves me the default value null.
But now it's the weird part. I start changing the input value and I don't have nothing appearing in my console. Nothing is moving...
Then I reload the page and on the load I get this console.log "inputValue" 5 undefined (5 is the value I entered previously) so as you can see, when I was changing the input previously, it was well keeping the value in the store but the computed value was not updating itself...
Ans now, when I change the value of the input I have my console log like this "inputValue" 7 5 so it's working as I would like it to work from the start...
What do I do wrong? Why on the first load the computed value not reactive?
Thanks for your answers...
I think the best way to solve this issue is to store a local variable with a watcher, and then update vuex when the local is changed:
On your component:
<input type="number" v-model="value">
data() {
return {
value: ''
};
},
computed: {
...mapGetters({
inputValue: 'getInputValue'
})
}
watch: {
value(value){
this.$store.dispatch('setInputValue', value);
},
inputValue(value) {
console.log('inputValue', value);
}
},
created() {
// set the initial value to be the same as the one in vuex
this.value = this.inputValue;
}
Please take a look at this sample: https://codesandbox.io/s/vuex-store-ne3ol
Your mistake is, you are using this keyword in template. One shouldn't use this in template code.
<input
type="number"
value="inputValue"
#change="$store.dispatch('setInputValue', $event.target.value)"
>
Bonus tip: It is redundant to use a getter to return the default state
if you can just use mapState to return the state.
There are a few small mistakes in the template. It should be this:
<input type="number" :value="inputValue" #change="$store.dispatch('setInputValue', $event.target.value)">
I've removed the this. in a couple of places and put a : out the front of value. Once I make these changes everything works as expected. The this.$store was causing console errors for me using Vue 2.6.10.
I would add that you're using the change event. This is the standard DOM change event and it won't fire until the field blurs. So if you just start typing you won't see anything happen in the console. You'd be better off using input if you want it to update on every keystroke. Alternatively you could use v-model with a getter and setter (see https://vuex.vuejs.org/guide/forms.html#two-way-computed-property).
My suspicion is that when you were reloading the page that was triggering the change event because it blurred the field.
Ok, so ... I found the problem and it was not relative to my examples, I can't really explain why, but I'll try to explain how :
In my store I have the next method :
mutations: {
deleteAppValues(state) {
state.appValues = null;
}
}
I was using this one on the Logout, or when the user first comes on the pageand was not logged-in... So what was going-on?
The User first load the page, the store is initializing well, and the index inputValue is initialized with null value, so it exists...
... But as the User is not logged, I destroy the store so now the inputValue is not equals to null, it just doesn't exist...
Trying to use mapGetters on something that don't exists, the reactivity won't work, so if I dispatch a change, the store key will be created, but as the mapGetters was initialized with an inexisting key, it doesn't listen the reactivity...
After reloading the page, the key now exists in the store so the getter can be attached to it and so now everything working fine...
This is exactly the explaination of what was going wrong about my code... So to make it works fine, I just changed my destruction mutation to :
mutations: {
deleteAppValues(state) {
state.appValues = {
inputValue: null,
};
}
}
Like this, the inputValue key of the store object will always exists and so the getter won't lose his reactivity...
I tryed to make a simple concise question but that made me forgot the bad part of my code, sorry.
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)
}