'Vue.js' Errors in communicating between parent and children - javascript

i have application that calls several requests and displays that data. Everything is working, but I getting some errors that I can't figure out where is the problem..
So I have two components:
--App :Parent
---Events :Children
In App.vue calling children component:
<Events :events="gameInfo" :results="results" #startNewGame="startNewGame" />
Giving as a props "gameInfo", "results" and listening for "startNewGame" event.
When application loads first time in App.vue i'm calling function:
// Get Next Game
getNextGame() {
this.gameInfo = [];
RouletteApi.getNextGame().then(response => {
this.gameInfo.push({ response });
});
}
That children component receives and displays data.
In children component:
<script>
export default {
name: "Events",
props: ["events", "results"],
data() {
return {
GameStartTime: null,
GameId: null,
GameUUID: null
};
},
watch: {
events: function(newVal, oldVal) {
this.GameStartTime = newVal[0]["response"].fakeStartDelta--;
this.GameId = newVal[0]["response"].id;
this.GameUUID = newVal[0]["response"].uuid;
}
},
created() {
setInterval(() => {
if (this.GameStartTime > 0) {
this.GameStartTime = this.events[0]["response"].fakeStartDelta--;
} else {
this.$emit("startNewGame", this.GameUUID); -- call to parent function
}
}, 1000);
}
};
</script>
I watching, getting the data and setting timer, to execute "startNewGame" function from parent, that will make another api call and give children new data.
After timer expires I'm calling "startNewGame" function from parent:
startNewGame(uuid) {
this.startSpinning();
RouletteApi.startNewGame(uuid).then(response => {
if (response.result == null) {
setTimeout(function() {
startNewGame(uuid);
}, 1000);
} else {
this.results.push({ response });
this.gameInfo = []; -- resetting that previous dat
this.getNextGame(); -- call to first function in example
}
});
That checks if response is null then setting timeout and calling that function until response will be not null. If response came not null than I pushing to children result, resetting that gameInfo array and calling again getNextGame() function that will call request and set new value for timer in children component.
RouletteApi.js:
import axios from 'axios'
export default {
getLayout() {
return axios.get('/configuration')
.then(response => {
return response.data
})
},
getStats() {
return axios.get('/stats?limit=200')
.then(response => {
return response.data
})
},
getNextGame() {
return axios.get('/nextGame')
.then(response => {
return response.data
})
},
startNewGame(uuid) {
return axios.get('/game/' + uuid)
.then(response => {
return response.data
})
}
}
Errors:
Error in callback for watcher "events": "TypeError: Cannot read property 'response' of undefined"
TypeError: Cannot read property 'response' of undefined
at VueComponent.events (Events.vue?5cf3:30)
Uncaught ReferenceError: startNewGame is not defined
First two errors i'm getting from children component in "watch" part.
Last one when calling function in setInterval in parent component.

It looks like the watcher is running before the api call finished. Console log the new value to see what your get. Try to check if the newVal is not null or an empty array and then set the values.

Related

I failed to call component's method inside of vue-froala-wysiwyg event

in laravel 8/2.6/bootstrap 4.6/Axios app using vue-froala-wysiwyg I need to get content of entered text and
call component's method with entered text.
I define event as I read here https://github.com/froala/vue-froala-wysiwyg :
import VueFroala from 'vue-froala-wysiwyg' // https://froala.com/wysiwyg-editor/docs
Vue.use(VueFroala) // https://github.com/froala/vue-froala-wysiwyg
Vue.config.productionTip = false
// let self = this
export default {
name: "RoomChat",
components: {},
data() {
return {
currentRoom: null,
...
froalaEditorConfig: {
events: {
initialized: function () {
console.log('froalaEditorConfig initialized')
},
'contentChanged': function () { // https://froala.com/wysiwyg-editor/docs/events/
console.log('froalaEditorConfig contentChanged this::')
// this - is reference to froala component
console.log(this);
console.log('this.el::')
// I tryied to get ref to root component in several ways
console.log(this.el) // undefined
console.log('this.el.$parent::')
console.log(this.el.$parent) // undefined
console.log('this.$parent::')
console.log(this.$parent) // undefined
console.log('froalaEditorConfig contentChanged this.html.get()::')
console.log(this.html.get());
parent.actionUser(this.html.get()) // I got parent.actionUser is not a function error
},
},
},
...
methods: {
actionUser(enteredText) {
...
Which way is valid?
ADDED :
Searching for decision I found
https://medium.com/dataseries/vue-js-components-parent-child-and-root-f1fcbe422feb
article with this.$root described but making inside of my event :
console.log('this.$root::')
console.log(this.$root)
Undefined is outputted.
What I see inside of event outputting this : https://prnt.sc/1r6pp4y and https://prnt.sc/1r6ptoh
Thanks!
I found decision with sending vueComponent instance as parameter :
data:(vm) => ({
...
froalaEditorConfig: {
events: {
initialized: function () {
},
contentChanged () {
vm.actionUser(this.html.get()) // That works ok
},
},
....
},
}), // data(vm) => ({

Reach content of variable in another method

I'm trying to reach the content of matchData from a Vue method. I'm able to console.log(this.matchData), but not able to get its content.
When I console.log(this.matchData[0].matchScores[0]) under method readMatchPlayerScoreIds() I get:
vue.runtime.esm.js?2b0e:619 [Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'matchScores' of undefined"
export default {
data() {
return {
matchData: [],
};
},
methods: {
readMatches() {
db.collection("matches")
.get()
.then((queryMatchSnapshot) => {
queryMatchSnapshot.forEach((doc) => {
this.matchData = [];
this.matchData.push({
awayscore: doc.data().awayscore,
homeScore: doc.data().homeScore,
matchScores: doc.data().matchscores,
})
});
console.log(this.matchData[0].matchScores[0])
});
},
readMatchPlayerScoreIds() {
console.log(this.matchData[0].matchScores[0])
}
},
mounted() {
this.readMatches();
this.readMatchPlayerScoreIds();
},
};
Since you are fetching data from the db asynchronously, the data will be empty until the db call was completed. You should read the data after the Promise has been resolved. (reformulating my comment as answer).
One way to do it, could be to return the Promise from readMatches:
export default {
data() {
return {
matchData: [],
};
},
methods: {
readMatches() {
return db.collection("matches")
.get()
.then((queryMatchSnapshot) => {
queryMatchSnapshot.forEach((doc) => {
// this.matchData = []; // <= why would you reset it in each loop?
this.matchData.push({
awayscore: doc.data().awayscore,
homeScore: doc.data().homeScore,
matchScores: doc.data().matchscores,
})
});
console.log(this.matchData[0].matchScores[0])
});
},
readMatchPlayerScoreIds() {
console.log(this.matchData[0].matchScores[0])
}
},
mounted() {
this.readMatches()
.then(() => this.readMatchPlayerScoreIds());
},
};
But it depends on what you want do to in readMatchPlayerScoreIds method body.
Also, be aware not to reset matchData in the forEach loop.

Global loaded data in VueJs is occasionally null

I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.

VueJS how to use _.debounce on data changes

I'm building a little vue.js-application where I do some post requests. I use the watch-method to whach for api changes which then updates the component if the post request is successfull. Since the watcher constantly checks the API I want to add the ._debounce method but for some reason it doesn't work.
here is the code:
<script>
import _ from 'lodash'
export default {
data () {
return {
cds: [],
cdCount: ''
}
},
watch: {
cds() {
this.fetchAll()
}
},
methods: {
fetchAll: _.debounce(() => {
this.$http.get('/api/cds')
.then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
created() {
this.fetchAll();
}
}
</script>
this gives me the error: Cannot read property 'get' of undefined
Can someone maybe tell me what I'm doing wrong?
EDIT
I removed the watch-method and tried to add
updated(): {
this.fetchAll()
}
with the result that the request runs in a loop :-/ When I remove the updated-lifecycle, the component does (of course) not react to api/array changes... I'm pretty clueless
Mind the this: () => { in methods make the this reference window and not the Vue instance.
Declare using a regular function:
methods: {
fetchAll: _.debounce(function () {
this.$http.get('/api/cds/add').then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
Other problems
You have a cyclic dependency.
The fetchAll method is mutating the cds property (line this.cds = response.body) and the cds() watch is calling this.fetchAll(). As you can see, this leads to an infinite loop.
Solution: Stop the cycle by removing the fetchAll call from the watcher:
watch: {
cds() {
// this.fetchAll() // remove this
}
},

How to fire an event on Vue switch change

I have a Vue component that has a vue-switch element. When the component is loaded, the switch has to be set to ON or OFF depending on the data. This is currently happening within the 'mounted()' method. Then, when the switch is toggled, it needs to make an API call that will tell the database the new state. This is currently happening in the 'watch' method.
The problem is that because I am 'watching' the switch, the API call runs when the data gets set on mount. So if it's set to ON and you navigate to the component, the mounted() method sets the switch to ON but it ALSO calls the toggle API method which turns it off. Therefore the view says it's on but the data says it's off.
I have tried to change the API event so that it happens on a click method, but this doesn't work as it doesn't recognize a click and the function never runs.
How do I make it so that the API call is only made when the switch is clicked?
HTML
<switcher size="lg" color="green" open-name="ON" close-name="OFF" v-model="toggle"></switcher>
VUE
data: function() {
return {
toggle: false,
noAvailalableMonitoring: false
}
},
computed: {
report() { return this.$store.getters.currentReport },
isBeingMonitored() { return this.$store.getters.isBeingMonitored },
availableMonitoring() { return this.$store.getters.checkAvailableMonitoring }
},
mounted() {
this.toggle = this.isBeingMonitored;
},
watch: {
toggle: function() {
if(this.availableMonitoring) {
let dto = {
reportToken: this.report.reportToken,
version: this.report.version
}
this.$store.dispatch('TOGGLE_MONITORING', dto).then(response => {
}, error => {
console.log("Failed.")
})
} else {
this.toggle = false;
this.noAvailalableMonitoring = true;
}
}
}
I would recommend using a 2-way computed property for your model (Vue 2).
Attempted to update code here, but obvs not tested without your Vuex setup.
For reference, please see Two-Way Computed Property
data: function(){
return {
noAvailableMonitoring: false
}
},
computed: {
report() { return this.$store.getters.currentReport },
isBeingMonitored() { return this.$store.getters.isBeingMonitored },
availableMonitoring() { return this.$store.getters.checkAvailableMonitoring },
toggle: {
get() {
return this.$store.getters.getToggle;
},
set() {
if(this.availableMonitoring) {
let dto = {
reportToken: this.report.reportToken,
version: this.report.version
}
this.$store.dispatch('TOGGLE_MONITORING', dto).then(response => {
}, error => {
console.log("Failed.")
});
} else {
this.$store.commit('setToggle', false);
this.noAvailableMonitoring = true;
}
}
}
}
Instead of having a watch, create a new computed named clickToggle. Its get function returns toggle, its set function does what you're doing in your watch (as well as, ultimately, setting toggle). Your mounted can adjust toggle with impunity. Only changes to clickToggle will do the other stuff.

Categories