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.
Related
I am trying to be able to read a value that is boolean to see if a user did a specific action or not and I am using the ReactJS functional component style. I am trying to read the runValue in my code to see if the run() method changed the value and I want to be able to read this value without recalling the function.
I want to be able to put in my useEffect method this line of code;
Run.RunFunction().run((index) => {
if (index) {
\\\ do stuff here if index is true
} else {
///if index is false
}
}
my code
const Run = {
RunFunction: () => {
let runValue = false;
return {
run() {
runValue = true
},
listener: function(val) {},
checkStatus: function(listen) {
this.listener = listen
}
}
},
}
Run.RunFunction().checkStatus((index) => {
if (index) {
console.log('running')
} else {
console.log('not running')
}
});
I am having trouble having this code to work and I want to be able to see the value of the runValue initially and if it changes.
Thank you
I have this code in my Vue.js component:
mounted() {
if (localStorage.dobDate) {
this.form.dobDate = localStorage.dobDate;
}
if (localStorage.dobMonth) {
this.form.dobMonth = localStorage.dobMonth;
}
if (localStorage.dobYear) {
this.form.dobYear = localStorage.dobYear;
}
},
watch: {
"form.dobDate": {
handler: function(after, before) {
localStorage.dobDate = after;
},
deep: true
},
"form.dobMonth": {
handler: function(after, before) {
localStorage.dobMonth = after;
},
deep: true
},
"form.dobYear": {
handler: function(after, before) {
localStorage.dobYear = after;
},
deep: true
}
Ask you can see it can get very repetitive, if for example I had a large form, and I don't want to do this for every field. Is there a way I can approach this to make it more DRY? Is there a way I can make it more dynamic for any field in a form for example?
In the mounted hook create an array of localStorage fields ["dobDate","dobMonth","dobYear"] and loop through it using forEach method, for each field localStorage[fieldName] check if it's defined using conditional operator, if it's defined assign it to the correspondant field name in the form data property else pass to the next element:
mounted(){
["dobDate","dobMonth","dobYear"].forEach(field=>{
localStorage[field]?this.form[field]=localStorage[field]:{};
})
}
In the watch property watch the form object deeply (watch its nested fields) then loop through its keys by doing the reciprocal operation made in mounted hook :
watch: {
form: {
handler: function(after, before) {
Object.keys(after).forEach(key=>{
localStorage[key]=after[key]
})
},
deep: true
}
}
Here is another approach with multiple (no deep) watchers.
data: {
form: {},
dateFields: ['dobDate', 'dobMonth', 'dobYear']
},
mounted() {
for (const dateField of this.dateFields) {
if (localStorage[dateField])
this.$set(this.form, dateField, localStorage[dateField])
}
},
created() {
for (const dateField of this.dateFields) {
this.$watch('form.' + dateField, function(after, before) {
localStorage[dateField] = after;
});
}
}
I ignore if it's more or less efficient than only one deep watcher. It may depends on the way your data change.
I'm sure you must have reasons for using localStorage for saving form data in localStorage, so with this code, you can pass the whole form object to localStorage and can retrieve that. in this case, any change in form would make this watch run
mounted() {
if (localStorage.form) {
this.form = localStorage.form
}
},
watch: {
"form": {
handler: function(after, before) {
localStorage.form = after;
},
deep: true
}
}
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.
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
}
},
I'm using Meteor with react and FlowRouter to handle subscriptions. I find that when my component renders it will render twice after a few seconds, but only when I have the meteor mixin subscribed to a subscription.
For example:
PeoplePage = React.createClass({
displayName:"People",
mixins: [ReactMeteorData],
getMeteorData() {
const subHandles = [
Meteor.subscribe("allPeople"),
];
const subsReady = _.all(subHandles, function (handle) {
return handle.ready();
});
return {
subsReady: subsReady,
people: People.find({}).fetch(),
};
},
render(){
if(this.data.subsReady == false){
return (<Loading/>);
} else {
console.log(this.data);
........
}
The same information is shown twice. Is this due to fast render that FlowRouter uses, or is it something that I am doing incorrectly?
Hmm, I guess the problem is that you are triggering the subscription every time, when the component re-renders.. I haven't tried it, but you could check if this will solve the problem
getMeteorData() {
const subsReady = _.all(this.subs || [{}], function (handle) {
if (typeof handle.ready == 'function')
return handle.ready();
return false;
});
if (!subsReady) // you can extend it, to provide params to subscriptions
this.subs = [
Meteor.subscribe("allPeople")
];
return {
subsReady: subsReady,
people: People.find({}).fetch(),
}
}
It should not trigger the subs if they are already ready.
Be aware, that mustn't pass an empty array to _.all, because of this:
_.all([], function(a) {return a.b()}) // true
this is why I added an empty object to the array, so this way you can check for the ready member..
I would suggest doing to subscription within the componentWillMount() function. This way, you make sure that you only subscribe once before the initial render().
getMeteorData() {
var ready = _.all(this.subHandles, function (handle) {
return handle.ready();
});
return {
subsReady: ready,
people: People.find({}).fetch()
}
},
componentWillMount() {
this.subHandles = [];
this.subHandles.push(Meteor.subscribe('allPeople');
},
componentWillUnmount() {
this.subHandles.map(function(handle) {
handle.stop();
});
}
If it still renders twice, I would suggest trying to turn of fast render for the route and check if this problem still occurs.