Vue JS 2 data manipulation - javascript

I have an instance of mounted in Vue, where I'm trying to set a date picker having the starting date depends on a data fed by an ajax response. Let's say this data property is named start_date. I run my ajax request via the created instance in Vue.
It's a little weird when I tried to console log vm.myObject, it shows the correct value start_date property coming from the ajax response. However, whenever I access the specific property via vm.myObject.start_date it will show you the default one I've created for data binding. My code structure below:
<script>
export default {
mounted() {
const vm = this;
console.log(vm.myObject); // this will show the data from ajax response
console.log(vm.myObject.start_date); //this will show the default value I set which si the 2017-10-25
},
created() {
const self = this;
$.ajax({
url: ApiRoutes.paths.GetDealData,
data: { id: 1 },
success: function(res) {
self.myObject.start_date = res.start_date;
}
});
},
data() {
return {
myObject: { start_date: "2017-10-25" }
};
}
};
</script>
I'm very new to Vue JS, so I'm currently having a hard time handling the data in the component via ajax request. I've already tried all the instances included beforeCreate, beforeMount but it didn't fix my issue still. How can I understand this kind of behavior?

Your code can't really work the way you described in your answer.
you are doing asynchronous operation (ajax call) and try to print the values right after synchronous operation? nope.
If you want to console.log(response) , you can do it in your callback function.
If you want to print the value on the page, but show nothing until the value is fetched (asynchronous operation), you can define on your data an attribute that signal if the fectching process is finished or not. and toggle it inside your callback.
I have edited the code to show how to declare the date-picker (have to be declared from the template side.
You have to pass the start_date as a props (I assume the prop name for the date-picker is start-date). when the ajax request is finished, the reactivity of vue will take care of re-rendering of the date-picker
<template>
<div>
<datePicker :start-date="myObject.start_date" />
</div>
</template>
<script>
export default {
created() {
$.ajax({
url: ApiRoutes.paths.GetDealData,
data: { id: 1 },
success: function(res) {
self.myObject.start_date = res.start_date;
self.isFetchedFinished = true
}
});
},
data() {
return {
startDate: ''
};
}
};
</script>

The reason is console.log() working before AJAX response is returned, so console.log(vm.myObject.start_date) prints unchanging string with initial value. However, console.log(vm.myObject) prints your object, which then changes, and you can see actual property value in browser console.
Example:
var obj = { "name": "oldName" };
console.log(obj);
obj.name = "newName";
If you want a "frozen" version of your object, you can create a copy for logging:
console.log(Object.assign({}, vm.myObject));
Also, instead self.myObject.start_date = res.start_date; you probably want to use this.$set(this.myObject, 'start_date', res.start_date); for change tracking.

Related

Why is the reactive Value sometimes not updating in template? (Vue)

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?

Reference to javascript object apparently returning different values in different places with no modifications in between

I'm using a variable twice within a function but it returns different values even though I'm making no modifications to it.
This is happening within a form component developed with Vue.js (v2) which dispatches a Vuex action. I think this has nothing to do with Vue/Vuex per se, but it's important to understand part of the code.
Here is the relevant piece of code from my component
import { mapActions } from 'vuex'
export default {
data() {
return {
product: {
code: '',
description: '',
type: '',
productImage: [],
productDocs: {},
}
}
},
methods: {
...mapActions(['event']),
save() {
console.log("this.product:", this.product)
const valid = this.$refs.form.validate() // this validates the form
console.log("this.product:", this.product)
if (valid) {
try {
this.event({
action: 'product/addProduct',
data: this.product
})
}
finally {
this.close()
}
}
},
// other stuff
and a small piece of code for the vuex action "event"
event: async ({ dispatch }, event) => {
const time = new Date()
const evid = `${Date.now()}|${Math.floor(Math.random()*1000)}`
console.log(`Preparing to dispatch... Action: ${event.action} | data: ${JSON.stringify(event.data)} | Event ID: ${evid}`)
// enriching event
event.evid = evid;
event.timestamp = time;
event.synced = 0
// Push user event to buffer
try {
await buffer.events.add(event)
} catch (e) {
console.log(`Error writing event into buffer. Action ${event.action} | evid: ${evid} `)
}
// dispatch action
try {
await dispatch(event.action, event)
}
catch (err) {
console.log(`Error dispatching action: ${event.action} | data: ${event.data}\n${err.stack || err}`)
window.alert('Could not save. Try again. \n' + err + `\n Action: ${event.action} | data: ${event.data}`)
}
},
The problem is with this.product. I've placed the several console.log to check out the actual values because it wasn't working as expected. The logs from the save() functions return undefined, but within the event function (a vuex action) the values are as expected, as shown in the console logs:
When I log this.product in the save() function. Both logs are the same.
When I log the event in the vuex action, it shows that event.data is actually the product.
I must be doing something terribly wrong here, but I'm totally blind to it. Any help is appreciated.
#Sumurai8: thanks for editing the question and for the hint.
Part of this may be because of that tiny i next to the opened product.
If you hover over it, it says that "the object has been evaluated just
now", which means it evaluates what is in the object when you open the
object, which is way after executing the action. [...] Whatever is
changing the product may very well happen after the event somewhere.
It actually helped me find the solution.
Basically within the this.close function called in the finally statement of the save() function, I was resetting the form and thus this.product, which was used solely to hold the form data. So at evaluation time, the object had undefined properties, while the event function managed to output to the console before the reset. However at the end the store would not get updated as expected (that's how I noticed the issue), because the event function and the action called within it are asynchronous and so the value got reset before the actual mutation of the vuex store.
Logging JSON.stringify(this.product) outputted the right value even within the save() method. I used that to create a more robust copy of the data and passed that to the event function as follows:
this.event({
action: 'product/addProduct',
data: JSON.parse(JSON.stringify(this.product))
})
Now everything works like a charme.

VueJS, Vuex, Getter is showing as an empty array, but console.log shows it's an object with all the values

This is the method I'm using, pretty simple.
DailyCountTest: function (){
this.$store.dispatch("DailyCountAction")
let NewPatientTest = this.$store.getters.NewPatientCountGET
console.log(NewPatientTest)
}
The getter gets that data from a simple action that calls a django backend API.
I'm attempting to do some charting with the data so I need to assign them to variables. The only problem is I can't access the variables.
This is what the console looks like
And this is what it looks like expanded.
You can see the contents, but I also see empty brackets. Would anyone know how I could access those values? I've tried a bunch of map.(Object) examples and couldn't get any success with them.
Would anyone have any recommendation on how I can manipulate this array to get the contents?
Thanks!
Here is the Vuex path for the API data
Action:
DailyCountAction ({ commit }) {
axios({
method: "get",
url: "http://127.0.0.1:8000/MonthlyCountByDay/",
auth: {
username: "test",
password: "test"
}
}).then(response => {
commit('DailyCountMutation', response.data)
})
},
Mutation:
DailyCountMutation(state, DailyCount) {
const NewPatientMap = new Map(Object.entries(DailyCount));
NewPatientMap.forEach((value, key) => {
var NewPatientCycle = value['Current_Cycle_Date']
state.DailyCount.push(NewPatientCycle)
});
}
Getter:
NewPatientCountGET : state => {
return state.DailyCount
}
State:
DailyCount: []
This particular description of your problem caught my eye:
The getter gets that data from a simple action that calls a django backend API
That, to me, implies an asynchronous action and you might be getting a race condition. Would you be able to post a sample of your getter function to confirm my suspicion?
If that getter does indeed rely on an action to populate its contents, perhaps something to the effect of the following might do?
DailyCountTest: async () => {
await this.$store.dispatch('DailyCountAction')
await this.$store.dispatch('ActionThatPopulatesNewPatientCount')
let NewPatientTest = this.$store.getters.NewPatientCountGET
// ... do whatever with resulting array
}
You can also try with a computer property. You can import mapGetters
import { mapGetters } from 'vuex'
and later in computed properties:
computed: {
...mapGetters(['NewPatientCountGET'])
}
then you can use your NewPatientCountGET and it will update whenever the value changes in the store. (for example when the api returns a new value)
Hope that makes sense

Parent's data change does not update child component in vuejs

I have the following:
Vue.component('times-updated', {
template: '<span>Times Updated: {{ timesUpdated }}</span>',
data: function() {
return {
timesUpdated: this.$parent.myData.timesUpdated
}
}
});
var vm = new Vue({
el: '#test',
data: function() {
return {
myData: {}
}
}
})
setInterval(function(){
$.ajax({
url: `${window.location.href}/json`, // This just returns an array : array.timesUpdated: 2 etc
}).done(function (data) {
vm.myData = data; // changes this data
});
}, 1000)
and am using the following html:
<div class="test">
<times-updated></times-updated>
</div>
I poll a REST API that returns an array which includes a timesUpdated property:
{
timesUpdated: 5
}
My intention is that every second I use jQuery's $.ajax method to call the API, update the myData data object on vm, which would then update the times-updated component.
The code works on initial page load, the times-updated component can retrieve the value on its parent's myData property, but whilst I have confirms that vm.myData does reflect the new value from the API, the component doesn't update its display to show the new count.
What am i doing wrong?
The data function is only called once during the life cycle of the component; when it is initially created. So essentially your component is just displaying the value as it existed when the component was created.
Additionally, it's generally bad practice to reach out of a component to get a data value. Vue is props down, events up. You should convert your component to use a property.
Vue.component('times-updated', {
props:["times"],
template: '<span>Times Updated: {{ times }}</span>',
})
The fact that you are using a function to define the Vue in this particular case doesn't really matter, it's just not a typical practice. Components require a function because they need an isolated scope.
Here is an example.
That callback is required only in components
// vue instance
new Vue({
data: {
status: true
}
};
// vue components (callback)
Vue.component('custom-component', {
data: function() {
return {
status: false
}
}
});

Object's data missing when passing to Meteor.call

In my React/Meteor application, I am trying to pass an object with data from the state to a method on the server, for insertion into the database. However, there seems to be an issue passing the object from the React component to the Meteor method - one of the child objects ends up in the Meteor method, but all of its children are gone. I do nothing to the object except use check() to ensure it is an Object:
'Appointments.saveData'(dataObj) {
check(dataObj, Object);
console.log(dataObj);
// ....
}
Here's what happens on the front-end:
Meteor.call('Appointments.saveData', {
vitalsData: this.state.vitalsData || {},
subjectiveData: this.state.subjectiveData || '',
physicalExamData: this.state.physicalExamData || {},
rosData: this.state.rosData || {},
impressionData: this.state.impressionData || [],
extraNotes: this.state.extraNotes || ''
}, (err, res) => {
if (res && !err) {
this.refs.toasts.success(
'Data for this encounter has been saved.',
'Records saved!'
);
} else {
this.refs.toasts.error(
'An unknown error has occurred. Reload the page and try again.',
'Error!'
);
}
});
I combine all of my state variables into an object using {}, which in turn becomes dataObj in the method. However, dataObj.impressionData exists, and is an array containing objects, however, data is missing from any of the objects in the array.
For example, dataObj.impressionData[0].diagnosis should be an object, in fact, it is supposed to be an exact copy of an object already pulled from the database. However, if I console.log it, the object is empty.
I have verified that the data exists as it should at each step before passing to the Meteor method. I console.log the object immediately before calling Meteor.call and immediately after calling check in my method. I cannot for the life of me understand why data is missing.
What am I forgetting?
EDIT: I've changed my code so that the data is now added to the state directly from a ref. Now the server method does properly receive the object. However, in the following code:
if (dataObj.impressionData && dataObj.impressionData.length > 0) {
dataObj.impressionData.forEach(obj => {
console.log(obj); // obj.diagnosis exists and is as expected
const x = ICD10Codes.findOne({ _id: obj.diagnosis._id });
console.log(x); // this also works as it should
impressionFields.push({ patientId: appt.patient._id, diagnosis: x, note: obj.note, x });
});
}
Setting diagnosis to x, which I KNOW is a valid copy of the object straight from the database yields the same results:
meteor:PRIMARY> db.EncounterData.findOne()
...
"impression" : {
"patientId" : "47de32b428d8c4aaac284af3",
"appointmentId" : "TwL7DF9FoXPRgmrjR",
"fields" : [
{
"patientId" : "47de32b428d8c4aaac284af3",
"diagnosis" : {
}
}
]
},
...
I think I'm going crazy.
So your issue comes down to the fact that this.setState is an asynchronous function, so when you make your Meteor call, this.state hasn't actually be updated yet. As such, you need to wait for the this.setState call to finish. The only way to do this is to use the React lifecycle methods. You can use either componentWillUpdate (called before the next render) or componentDidUpdate (called after the next render).
var MyComponent = React.createClass({
save: function() {
...
case 'impression':
this.setState({ impressionData: data }, this.callServerMetho‌​d);
break;
...
},
// This is one of the React lifecycle methods
componentWillUpdate: function(nextProps, nextState) {
// Put your Meteor call here
// Make sure to use nextState instead of this.state
// This way you know that this.state has finished updating
}
});
I solved the issue myself - it turns out that I was importing my SimpleSchema objects as default but exporting my SimpleSchema objects as named. The SimpleSchema objects were thus invalid.

Categories