Problem with Child value from Vue component changing Parent value - javascript

I have an Array that I use to populate a Dialog Vue Component. I have:
basicInfo: [{ firstName: "John" , lastName: "Doe" }],
<basicInfoForm v-model="showBasicInfoForm" :basicInfo="newbasicInfo[0]"></basicInfoForm>
on the Parent I have
{{basicInfo[0].firstName + ' ' + basicInfo[0].lastName}}
I have a button that will call a Form Component and open a Dialog popup that allows editing. My problem is that all changes to the Dialog are also immediately shown on the Parent. I would like to have on the Child Dialog a cancel button so no changes will be made. I cloned the array before I pass it:
this.newbasicInfo = this.basicInfo.slice();
and on the Child Dialog
<v-text-field
v-model="basicInfo.firstName"
label="First Name"
class="input-name styled-input"
></v-text-field>
props: {
value: Boolean,
basicInfo: Array
},
My problem is that I can see each key stroke as changes are being made so there is no way to go back to the original if a cancel is selected. I'm new to Vue and Components so I could have something totally screwed up. Why are changes being make to both the basicInfo array and the newbasicInfo array at the same time.

What is happening here is that you are copying the array by reference. So when you modify the index of one array, actually both are modified because they share the same reference.
What you need to do is to copy the array by values.
this can be easily done this way : this.newbasicInfo = JSON.parse(JSON.stringify(this.basicInfo));
You can check this question on SO for more context : How do you clone an Array of Objects in Javascript?

Your clone method does not work with an array of objects:
const basicInfo = [{ firstName: "John" , lastName: "Doe" }]
const newbasicInfo = basicInfo.slice()
This method creates a "shallow copy" - that means it works with numbers, strings, etc. but does not really create a clone of objects. The cloned objects this way keep their reference, so they will actually be the "same" objects as in the source array.
const basicInfo = [{ firstName: "John" , lastName: "Doe" }]
const newbasicInfo = JSON.parse(JSON.stringify(basicInfo))
This method creates a "deep copy" - everything it holds will be cloned, no matter how "deep" they are nested.
If you have an array of simple values, the shallow copy is more efficient, but if you have objects in your array you need deep copy.
So, it's not a Vue problem, but a more general JavaScript question.
Here's a little snippet, to illustrate the difference:
const basicInfo1 = [{
firstName: "John",
lastName: "Doe"
}]
const newbasicInfo1 = basicInfo1.slice()
newbasicInfo1[0].firstName = "Johnnyboy"
console.log('basicInfo1: ', basicInfo1)
console.log('newbasicInfo1: ', newbasicInfo1)
const basicInfo2 = [{
firstName: "John",
lastName: "Doe"
}]
const newbasicInfo2 = JSON.parse(JSON.stringify(basicInfo2))
newbasicInfo2[0].firstName = "Johnnyboy"
console.log('basicInfo2: ', basicInfo2)
console.log('newbasicInfo2: ', newbasicInfo2)

Related

Updating an Array that's present inside an Object

I'm trying to update an array (Array name is "Variables" please refer the attached screenshot) which presents inside an Object, so I want to update that array if there is word called "placeholder" in alertMessage(it's a different property presents in the same Object)
I'd appreciate any help on how to update this array in question, I tried using pop method but it didn't go as planned and I've attached screenshots of the Objects for reference
You can retrieve the string placeholder like this data['alertMessage']['en_US']['all'] and then use a conditional statement to make changes to the array inside the data object.
let data = {
alertOne: '',
alertTwo: '',
alertMessage: {
en_US: {all: 'placeholder'}
},
variables: [
{id: 0, uuid: '123'},
{id: 1, uuid: '223'},
{id: 2, uuid: '323'}
]
}
let all = data['alertMessage']['en_US']['all']
// if condition is met add a new object to the array
if(all === 'placeholder'){
data.variables = [...data.variables, {id: 3, uuid: '423'}]
}
console.log(data)

Edit readonly javascript objects

I have a read-only object that is returned by GraphQL (vue-apollo) query, the result which is read-only looks something like this:
result: {
id: 'yh383hjjf',
regulations: [{ title: 'Test', approved: false}]
})
I want to bind this to a form and be able to edit/update the values in the regulations array and save it back to the database.
at the moment when I try to edit I get the error below:
Uncaught TypeError: "title" is read-only
I tried cloning the result returned by the database using object.assign
//target template
const regulatoryApprovals = {
id: null,
regulations: [{ title: null, approved: null}]
})
regulatoryApprovals = Object.assign(regulatoryApprovals, result, {
regulations: Object.assign(regulatoryApprovals.regulations, result.regulations)
})
but this didn't work.
Does anyone know how I can properly clone the result?
regulatoryApprovals= Object.assign(regulatoryApprovals, ... indicates the problem because regulatoryApprovals is modified with Object.assign, so it would need no assignment.
Read-only regulatoryApprovals object needs to be cloned. regulations is an array and won't be merged correctly with Object.assign, unless it's known that array elements need to be replaced. It should be:
regulatoryApprovals = {
...regulatoryApprovals,
...result,
regulations: [...regulatoryApprovals.regulations, result.regulations]
}
Where { ...regulatoryApprovals, ... } is a shortcut for Object.assign({}, regulatoryApprovals, ...).

Javascript. adding items to an array updates all items

This question is somewhat related to this issue I had earlier today:
Adding items to an array in javascript
It works to add items to my array now, but it seems that when I update the array all items will be the same even though the object passed into the method is different everytime
My method looks like this:
addShoe(shoe) {
console.log("Adding new shoe to collection: ");
console.log(shoe);
this.setState(
{
shoes: [...this.state.shoes, shoe]
},
function() {
console.log("Shoe collection:");
console.log(this.state.shoes);
}
);
}
So after one run, this is what the console in Chrome looks like. Which seems to be right:
When I try to add one more to the collection, this is what happens:
Now my collection contains two items which is correct, but it seems like all items in the collection has the same data?
What am I doing wrong here?
EDIT
In another React component I have the following state:
this.state = {
shoe: {
selectedBrand: "",
selectedEU: "",
selectedUS: "",
selectedUK: "",
selectedFraction: ""
}
};
Once a field is updated with a new value, the following method will be triggered:
updateSelectedValues(property, event) {
const shoe = this.state.shoe;
shoe[property] = event.value;
this.setState({ shoe: shoe });
}
When a button in this modal window is closed, the this.state.shoe will be pass as a param to method in the "parent" component.

mapping through an array

I have an array called enquiries
Im trying to map through it like this:
enquiries.map(enquiry => enquiry.firstName)
I get the following error:
TypeError: enquiries.map is not a function
Here is output for console.log(enquiries)
{enquiries: Array(2)}
enquiries:Array(2)
0:{_id: "5a28364d5a36f4556af34741",
firstName: "jonny",
surname: "rotten",
telephoneNumber: 859937,
gender: ""male"", …}
1:{id: "5a283e4c5a36f4556af34742",
firstName: "bob",
surname: "hoskins",
telephoneNumber: 939483948,
gender: "male", …}
The first {enquiries: Array(2)} implies that the logged element is an object that contains a key named enquiries.
So if your logged variable is also enquiries then you need
let firstNames = enquiries.enquiries.map(enquiry=>enquiry.firstName);
Also to return the firstName from your arrow function, you need to either use {} and return inside it, or skip the {} altogether for an implicit return.
Well, you have two reasons for that.
First, your "enquiries" variable is an object (but as it seems it's iteratable, that's good!).
As an object, it doesn't have the map function in its prototype.
Second, your arrow function uses curly brackets - which indicates that code should be written in.
To use the shortcut for a single value you could either rid of the curly brackets or use regular ones:
enquiries.map((enquiry) => (enquiry.firstName))
or even:
enquiries.map(enquiry => enquiry.firstName)
So for conclusion:
Array.from(enquiries).map(enquiry => enquiry.firstName)

Knockout JS not setting all members observable

What I am trying to do is to get data from the server and then putting it all in an observable and then make all the properties observable. The issue I am facing is that it does not make all my properties observable and I need them all to be observable as sometimes depending on the data it makes some properties observable and sometimes it doesn't.
var viewModel = this;
viewModel.Model = ko.observable();
viewModel.SetModel = function (data) {
viewModel.Model(ko.mapping.fromJS(data));
}
The data that I am receiving from the server is like this for example: normaldata,items(this is an array with unknown number of elements).
so if i try to access data like viewModel.Model().Items[0]().Layer() i sometimes have Layer as a function and sometimes it is a normal element with observable elements.I want all my objects inside Items to have Layer as a function.
Server data example:
Name: "test"
Items: [Layer[ID: 132]]
In this example Name,Items and ID are observable but Layer is not.
Fiddle example:
jsfiddle.net/98dv11yz/3
So the problem is that sometimes the layer is null resulting in ko making the property observable but sometimes that property has id and ko makes only the child elements observable. The problem is that i have if's in the code and i want it to be a function so i can always reffer to it as layer() because now it is sometimes layer or layer()
An explenation for what's happening:
When the ko.mapping plugin encounters an object in your input, it will make the object's properties observable, not the property itself.
For example:
var myVM = ko.mapping.fromJS({
name: "Foo",
myObject: {
bar: "Baz"
}
});
Will boil down to:
var myVM = {
name: ko.observable("Foo"),
myObject: {
bar: ko.observable("Baz")
}
}
and not to:
var myVM = {
name: ko.observable("Foo"),
myObject: ko.observable({
bar: ko.observable("Baz")
})
}
The issue with your data structure is that myObject will sometimes be null, and sometimes be an object. The first will be treated just as the name property in this example, the latter will be treated as the myObject prop.
My suggestion:
Firstly: I'd suggest to only use the ko.mapping.fromJS method if you have a well documented and uniform data structure, and not on large data sets that have many levels and complexity. Sometimes, it's easier to create slim viewmodels that have their own mapping logic in their constructor.
If you do not wish to alter your data structure and want to keep using ko.mapping, this part will have to be changed client-side:
Items: [
{ layer: {id: "0.2"} },
{ layer: null}
]
You'll have to decide what you want to achieve. Should the viewmodel strip out the item with a null layer? Or do you want to render it and be able to update it? Here's an example of how to "correct" your data before creating a view model:
var serverData = {
Name: "Example Name",
Id: "0",
Items: [
{layer: {id: "0.2"} },
{layer: null}
]
};
var correctedData = (function() {
var copy = JSON.parse(JSON.stringify(serverData));
// If you want to be able to render the null item:
copy.Items = copy.Items.map(function(item) {
return item.layer ? item : { layer: { id: "unknown" } };
});
// If you don't want it in there:
copy.Items = copy.Items.filter(function(item) {
return item.layer;
});
return copy;
}());
Whether this solution is acceptable kind of relies on how much more complicated your real-life use will be. If there's more complexity and interactivity to the data, I'd suggest mapping the items to their own viewmodels that deal with missing properties and what not...

Categories