Trouble when editing Vue Data - javascript

If I have data like so in Vue JS 2:
data : {
newBus: {
name: '',
hours: {
sunday: '',
}
}
}
And I set it here:
<input v-model="newBus.hours.sunday" type="text" placeholder="Sunday">
Adding a business this way works, the issue comes when I try to update them. The user clicks on the item from a list and then the saved data fills in the form like so:
<div v-for="bus in businesses" v-on:click="editBus(bus)" class="list-group-item bus-list-item">{{bus.name}}</div>
Method:
editBus: function (bus) {
/*Set post values to form*/
this.newBus = bus
},
But I get the error:
vue.js:1453 TypeError: Cannot read property 'sunday' of undefined
This happens immediately when the item to be updated is clicked. Any ideas?
EDIT: It appears to be related to the hours property not being avaliable on bus. If I console.log(bus) it doesn't show. But I need to add this data to this business. Is there a way to have it ignore data it doesn't yet have? What I don't understand is that if it was not nested...aka sunday: '' instead of hours: { sunday: ''} it works fine.
EDIT: Here is the CodePen recreation.

The problem here isn't really a Vue problem it's a Javascript problem. You cannot access a property of undefined.
Using the example data structure from your pen.
newComment: {
name: '',
comment: '',
votes: 0,
time: timeStamp(),
subcomment: {
name: ''
}
}
In this case, you've set everything up with a defined value, so everything is going to be resolved. The problem comes when you are clicking your edit button, the object received looks like this:
{
name: "some name",
comment: "some comment",
time: "4/5/2017",
votes: 145,
.key: "-Kh0PVK9_2p1oYmzWEjJ"
}
Note here that there is no subcomment property. That means that the result of this code
newComment.subcomment
is undefined. But in your template, you go on to reference
newComment.subcomment.name
So you are essentially trying to get the name property of undefined. There is no name property of undefined. That is why you get your error.
One way you can protect yourself from this error is to check to make sure the subcomment property exists before rendering the element using v-if (forgive me if this is the wrong pug syntax-I'm not that familiar with it)
input#name(type="text")(placeholder="Name")(v-model="newComment.subcomment.name")(v-if="newComment.subcomment")
That will prevent the error.
Alternatively in your editComment method, you could check to see if subcomment exists, and if it doesn't, add it.
if (!comment.subcomment)
comment.subcomment = {name: ''}
this.newComment = comment
Finally, you ask, why does it work if it's not nested data. The key difference is this: if newComment exists, say like this
newComment: {}
and you try to get newComment.name then the returned value of name is undefined. If you have a template that has something like v-model="newComment.name" nothing is going to crash, it's just going to set the value of the input element to undefined, and when you change it, newComment.name will get the updated value.
In other words, when you try to reference newComment.subcomment.name you are trying to reference the name property of something that doesn't exist whereas when you try to reference newComment.name, the object exists, and it doesn't have the name property.

Related

Cant access object properties retrieved via API with error message TypeError: Cannot read property '' of undefined - Nextjs/JavaScript

I have a problem that appears to be unique to NextJs.
I am hitting an API through a useHook and retrieving the results. What I get back is an array of objects. Everything works fine and I can console.log the entire result (Array of objects).
const mymoviegenreobjects = useFetchNavBarCatagories();
[{
"id": "1",
"name": "Adventure",
},
{
"id": 2,
"name": "Horror"
},
{
"id": 3,
"name": "Action"
}]
Then I filter the array of objects so that I can find which object has a name key that is = to 'Adventure'. That also works fine and I am able to extract the object which has the name key = to "Adventure" and now what I get back is an array with one object inside (the correct one).
const avengers = mymoviegenreobjects.filter(
(character) => character.name === "Adventure"
);
[{
"id": "1",
"name": "Adventure",
}]
Then to access the correct element of the array I use the [0] method which allows me to console.log just the first object. However when I want to console.log the first object.id which I do like thi
console.log("The ID for Avengers is", avengers[0].id)
I get this error.
Server Error
TypeError: Cannot read property 'id' of undefined
I am not sure why this is happening as this is the correct way to access the element in an array of objects, then a particular element within the selected object.
I have read some very vague explanation [link below] but I was unable to make out why this was actually happening and how to solve it. Furthermore, I've also read elsewhere that process.window is a deprecated function and should not be used.
So my question is how would I access the ID property of my object so that I would be able to console.log it and/or store it in a different variable.
Any help would be great. Thanks.
Next.js TypeError: Cannot read property '' of undefined when trying to console log api results
I have added a code sandbox to help you understand my error and what I am trying to do. Please disregard all my commented out stuff as I am just playing around and trying to do different stuff. Link is below.
https://codesandbox.io/s/modest-neumann-2x6iff
What I am trying to do is access the individual properties of the last console.log (This is avangers two)
The code for the log can be found in the usefetchMovieGenreResults hook.
I also tried to implement painting the object on the screen on the MovieGenreResults component just to see what happens but it does not work (as expected). I used the hook above to feed it. Ideally I would use the . notion to access the specific element I want to paint however it gives me an error.
can you make a codesandbox? It seems you're using next.js, calling the api on the server but logging the result in the jsx, you need to receive props to the component to achieve that next.js server side data fetching

Can I use a dynamic value to access a specific array in Vue.js?

I've been trying to figure out how to do this, but can't seem to get it to work. I have created a function that is being called when I click a component using v-on:click. I am also trying to pass in a value that I can then use to access a particular array that is coming in the form of data from a backend.
Here is the function in the component:
<v-card
#click="getContent('topSellers')"
>
I am then passing the 'topSellers' value as an "id" into the function that is being used to get access the exact array that I am looking for:
getContent(id) {
this.accordion = this.data.id;
console.log("data", id);
}
First of all, this.data.topSellers works. And I am seeing that the id value is correct in the console. Obviously, this is not working because id is currently a string, but I don't know how to fix that issue. Any help would be appreciated!
You need this.data[id] instead of this.data.id.
See property accessors on MDN for reference.
data[id] will access the property of data with the name of the value of id, in your case topSellers. So data.topSellers is equivalent to data["topSellers"]
[] allows you to access objects with variables.
It is recommended to handle exceptions because the variable received is not safe.
getContent(id) {
const accordion = this.data[id] || null;
if(accordion){
console.log("data", accordion);
this.accordion = accordion;
}
}

what does the template in vue.js bind to?

I recently experienced a bug in my vue.js application. I was able to fix it, and it taught me something about how vue.js works. I want to know if I'm corret.
First of all, the bug:
props: {
property: {},
...
}
data(): {
return {
propertyData: {
field1: null,
field2: '',
field3: [{email: null, notifying: false}]
},
...
};
}
created() {
this.propertyData = JSON.parse(JSON.stringify(this.property));
}
I was trying to make a copy of the property prop and assigned that to propertyData as a means of being able to mutate propertyData without mutating the property prop directly. But this was causing issues in the template. It almost seemed like there was a disconnect between the propertyData object and whatever the template was binding to.
For example, I would add this to the template:
{{propertyData.field3}}
...and it would print to the screen:
[{email: null, notifying: false}]
...which is expected at first. But I have an email text field that binds to the email field in each object of field3:
<v-layout v-for="(item, index) in propertyData.field3" :key="index">
<v-flex>
<v-text-field
label="Email"
v-model="item.email"
placeholder="username#example.com"
:rules="[v => !!v || 'Email is required',
v => /.+#.+/.test(v) || 'Email is invalid' ]"
maxlength="255"
required>
</v-text-field>
</v-flex>
</v-layout>
When I type something into the email field, the propertyData array doesn't update on the screen... that is, until the email field is validated (for example, when I finally add # and the first character of the domain).
I would put console logs in the code and it would print out the correct output for propertyData. That is, it would print out exactly what I currently had in the email field even though, on the screen, it didn't.
Then I tried this instead:
created() {
Object.assign(this.propertyData, {...this.property});
}
This fixed the problem. Now the propertyData.field3 array prints the actual current data entered into the email field and updates immediately.
So the lesson I learned from this (and this is what I'd like someone to confirm) is that the object the template binds to is not the data object (not directly at least). Once the component is created (or maybe mounted), any references to propertyData in the template, or to any of its fields, refers to the initial object defined in data() (with null for field1, '' for field2, etc.). But if you assign a completely different object to propertyData after that (which is what this.propertyData = JSON.parse(JSON.stringify(this.property)) would do), the template doesn't change the object it binds to. It's still bound to the initial object (with null for field1, '' for field2, etc.) and it requires a rendering update (which validation would do) to update the object the template binds to to the object propertyData refers to. My fix worked because Object.assign(...) doesn't change the object propertyData refers to, it just changes (or adds to) the fields.
^ Is this correct?

How to use reactive forms inside ng-template

I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)

Ember Data - Get parent value from child

I have a widget model which has a shallow parent-child relationship. A given widget may be a "root" widget and not have any parent, or it may be a child widget which has a parent.
The ember data model looks like this:
export default DS.Model.extend({
name: DS.attr('string'),
parentWidget: DS.belongsTo('widget', { async: true, inverse: null }),
isSubWidget: DS.attr('boolean')
})
I'm trying to add a "displayName" property that will show the name for root widgets, or "parent name - child name" for child widgets
displayName: Ember.computed('name', 'parentWidget.name', 'isSubLob', function() {
if this.get('isSubWidget') {
return "#{this.get('parentWidget.name')} - #{#get('name')}"
}
else {
return "#{this.get('name')}"
}
})
This is not working, however. The child lob's displayName always comes as
undefined - WidgetName
The json is being returned like so:
{
"widgets": [
{
"id": 2,
"name": "Widget Name",
"is_sub_widget": true,
"parent_widget_id": 1
},
...
}
For the record, all the records are being returne by the json at the same time.
I feel like Ember should be asyncronously resolving the parent widget and the string should be updated as well, however it doesn't seem to be working. Any idea what I'm doing wrong here?
I would say you have two issues:
You're not declaring an inverse to your parentWidget relationship, which means that Ember Data is guessing the inverse (and likely guessing wrong). You should change that declaration to look like this, just to be sure:
parentWidget: DS.belongsTo('widget', { async: true, inverse: null }),
I doubt that will fix your issue, but it's good practice.
You're not waiting for your promise to resolve before trying to use the name. You've specified the parentWidget relationship as being asynchronous, which means that #get('parentWidget') will not return a model. It's going to return a promise that will eventually resolve to your model. Normally this would be fine as the computed property would just recompute when the promise resolves, except that you're not watching the proper key.
/* PS: Assuming that your comma was misplaced on this line */
displayName: Ember.computed('name', 'parentWidget', function() {
^^^^^^^^^^^^
As seen, you're only watching the parentWidget property. So if the name property on the parentWidget every updates, you won't be notified. Change that line to this and you should be good to go:
displayName: Ember.computed('name', 'parentWidget.name', function() {
Just keep in mind that the first few times through, parentWidget.name will still be undefined. It won't be the value you want until the promise resolves, which means the computed property could run several times before it does resolve.

Categories

Resources