I have a dialog component that has a student_list props and this dialog is being triggered by a watcher, So basically, in my main component when I increment variable dialog_watcher++, the dialog opens.
The student_list props contains data which looks like this:
student_list = [
{id: 1, name: 'John'},
{id: 2, name: 'Jane'},
{id: 3, name: 'Jake'},
{id: 4, name: 'Finn'},
]
Now in the Dialog component, In my watch:{} hook, I have a variable student_list_data where I assigned the value of the student_list props..
watch: {
let temp = this.student_list;
this.student_list_data = temp;
}
Now, in the Dialog component, I have a button where I splice some object in the student_list_data array.
_selectStudent(item) {
//remove the selected student from the array
const index = this.student_list_data.findIndex(obj => obj.id == item.id);
this.student_list_data.splice(index, 1);
//append the selected student to a new array
this.selected_student.push(item);
}
Now, when I inspect in my vue devtools, I noticed that not only the student_list_data are being spliced, but also the student_list props. I checked everywhere but I dont have any function that splices data to student_list props.
Any idea where I went wrong?
When the non-primitive variables are assigned using "=", the reference will also be copied. This may lead to the mutation for all the variables that a particular object is assigned to.
Try the following.
You can use JSON.stringify to convert it to string and then parse into JSON.parse.
this.student_list_data = JSON.parse(JSON.stringify(this.student_list));
You can use spread operator.
this.student_list_data = [...this.student_list_data]
You are cloning the student_list wrongly. Try with this.
watch: {
this.student_list_data = [... this.student_list];
}
Related
I have a deeply nested JSON object as state initially made with useState hook -
const [template, setTemplate] = useState([
{
statement: 'this is top level statement',
nestingLevel: 0,
children: [
{
nestingLevel: 1,
statement:
'this is a statement with such a template',
children: [
{
statement: 'first chart',
nestingLevel: 2,
},
{ statement: 'second chart',
nestingLevel: 2,
},
],
},
],
},
{
statement:
'this is second statement for section with such a metric {{}}',
nestingLevel: 0,
},
]);
I have an input element with an onChange handler.
As you can see, whenever there is some change in the input text, I update some relevant key-value pair based on path. I do so by using the lodash library's get and set functions.
const handleDataChange = (e, path) => {
console.log('handling the data');
// copy the template
let templateCopy = template;
// create the new object with updated information
const tempObj = _.set(
templateCopy,
`${path}['statement']`,
e.target.value,
);
setTemplate([...tempObj]);
};
The problem is in the handleDataChange function. When I do setTemplate(tempObj) the state doesn't get updated. However when I do setTemplate([...tempObj])(which will essentially yield the same result), this later solution works as expected.
I want to know why that is the case. Is it because lodash gives results always as object and destructuring and repacking it make it array again and hence it works as expected?
The object reference stays the same when you mutate nested property only, and as react does shallow comparison in order to detect changes, it won't react to the change.
You can deepClone the object, then mutate it as you do with set and then update the state.
I have this state in my React component-->
const [employeeState, setEmployee] = useState({
"empName": null,
"Age": null,
"depts": [], //depts is an array of objects
"groups": [] //group is an array of objects
});
I have a separate state for depts and groups like follows -->
const depts = {
"name": "some name",
"code": "s123",
}
const [deptState, setDeptState] = useState([
{ ...depts},
]);
same is for groups too....
Now when I am trying to set the employee state on some btn click like below it's not updating and keeping depts and groups property as it is empty -->
const deptsLst = [...deptState];
const groupsLst = [...groupstate];
console.log(depsLst); // this will print the results as expected
console.log(groupsLst); // this will print the results as expected
setEmployee({
...employeeState,
['depts']: deptsLst ,
['groups']: groupsLst ,
})
console.log(employeeState);// this will show depts and groups as empty only
I am new to this spread variable copying concept. What am I doing wrong here?
You state that "I am new to this spread variable copying concept"...not only are you new to the spread operation but your code demonstrates that you do not have a basic understanding of it I think. You are using spread operators on top of spread operators on top of computed property names, all for no reason.
You are also using React state to manage the employee object and then using React state to manage Array properties on the employee object. If you are already performing state management on the employee object, why maintain state of individual properties?
Ok a couple breakdowns of your code:
const depts = {
"name": "some name",
"code": "s123",
}
const [deptState, setDeptState] = useState([
{ ...depts},
]);
Above you clone an object for no reason.
With React state management you only need to clone objects to trigger a render event. A clone creates a new object with a new reference in memory, will "react" to object reference changes and trigger a state change event. React is not capable of watching every property of a complex object, especially array elements to see if a value has changed.
The below code will suffice, you are initializing state, you do not need to clone:
const [deptState, setDeptState] = useState([{
"name": "some name",
"code": "s123",
}]);
Next this code:
setEmployee({
...employeeState,
['depts']: deptsLst ,
['groups']: groupsLst ,
})
Here you are attempting to use the spread operator to clone the "employeeState" object and update new values for the depts property key and groups property key. You use "Computed Property Names' for property keys for no reason.
This is sufficient:
setEmployee({
...employeeState,
depts: deptsLst ,
groups: groupsLst
})
You also create unnecessary state:
const [deptState, setDeptState] = //..rest of code omitted
Here you do not realize React state changes happen async:
setEmployee({
...employeeState,
['depts']: deptsLst ,
['groups']: groupsLst ,
})
console.log(employeeState);
The above code actually has changed the employeeState, just not be the time of your console.log(employeeState);
Ok, so to start I have created a CodePen for you to understand how state changes.
When you want to change state for this employee object of yours you simply need to change the object property values or add new property values and then clone the employee object to change its reference and than call setState with the clone, consider this object state:
const [employee, setEmployee] = React.useState({
"empName": 'Jane Doe',
"Age": 33,
"depts": [
{
"name": "some name",
"code": "s123",
}
]
});
Here I am adding a department to the employee, note I only clone the employee object to trigger React to detect the overall object reference change, React does not know I changed property depts array but the new state does have my new value I pushed on the department array:
function exampleUpdatingEmployee() {
employee.depts.push({
"name": "some new dept",
"code": "876",
});
setEmployee({ ...employee });
}
You can do:
setEmployee({
...employeeState,
depts: deptsLst,
groups: groupsLst
})
to update your employeeState. You probably won't see the update with the console.log you have right after that since it will execute before the state updates. You can verify that the employeeState updated by using a useEffect function like this:
useEffect(() => {
console.log(employeeState)
}, [employeeState])
The spread operator is the ES6 way of copying arrays without passing a reference to your copy so you can modify one array without those changes showing up in the other array. You can read into that more here: Reference And Copying Objects & Arrays.
if depts and group are arrays of objects, you should use spread operator in this way to copy values:
setEmployee({
...employeeState,
depts: [...deptState],
groups: [...groupstate]
});
so if:
const employeeState = {status: 'hungry'};
const deptState = ['one', 'two'];
const groupstate = ['y', 'k', 'o'];
then the first code, after spread, is like:
setEmployee({
status: 'hungry',
depts: ['one', 'two'],
groups: ['y', 'k', 'o']
});
When reading React Cookbook I've stumbled upon a code snippet, this function gets called when user checks a task as completed in a TODO list:
markAsCompleted = id => {
// Finding the task by id...
const foundTask = this.state.items.find(task => task.id === id);
// Updating the completed status...
foundTask.completed = true;
// Updating the state with the new updated task...
this.setState({
items: [
...this.state.items,
...foundTask
]
});
}
UPD: Somehow I've completely missed the spread operator on foundTask. So what's really happening is the state gets updated with only ...this.state.items (which was mutated), and the ...foundTask part does not go into state, since it's not a valid spread.
At first it looked like it should add a new element to the items array, instead of updating, so I went to the JS console to check:
state = { items: [{id: '0', done: false}, {id: '1', done: false}] }
upd = state.items.find(obj => obj.id === '0') // {id: "0", done: false}
upd.done = true // also updates object inside the array
state = { items: [...state.items, upd] }
/* Indeed, adds a new element:
items: Array(3)
0: {id: "0", done: true}
1: {id: "1", done: false}
2: {id: "0", done: true}
*/
So then I've downloaded the code and ran it locally. And, to my surprise, it worked! State was getting updated without any trouble, no extra elements appeared. I used React DevTools to see the live state while testing.
I searched the web, but couldn't find any examples like in the book, but with a better explanation. Usually all solutions involve using .map() to build a new array, and then replace an existing one (e.g. https://stackoverflow.com/a/44524507/10304479).
The only difference I see between the book code snippet and console test is that React is using .setState(), so maybe this helps somehow. Can anyone help to clarify, why is it working?
Thanks!
Array.find will return the first value matched in the array. Here the array consist of objects and the value returned will be the reference to the object.
const foundTask = this.state.items.find(task => task.id === id);
Here foundTask will have reference to the same object contained in the state.items. So when you modify foundTask you're modifying the same object as in state.items.
For example,
If this.state.items is [{ id: 1 }] and if you do
const foundTask = this.state.items.find(obj => obj.id === 1);
foundTask.id = 2;
console.log(this.state.items); // [{ id:2 }]
In the code,
this.setState({
items: [
...this.state.items,
...foundTask
]
});
This will update the state with updated completed value for the task.
...foundTask will give you an error in the console since foundTask will be an object and you're spreading it in an array.
Here not having ...foundTask will produce same result. Perhaps not with an error.
So I have the following object structure:
const SamplePalette = {
id: 1,
name: "Sample Palette",
description: "this is a short description",
swatches: [
{
val: "#FF6245",
tints: ["#FFE0DB", "#FFA797"],
shades: ["#751408", "#C33F27"]
},
{
val: "#FFFDA4",
tints: ["#FFFFE1"],
shades: ["#CCCB83"]
},
{
val: "#BFE8A3",
tints: ["#E7FFD7"],
shades: ["#95B77E"]
}
]
}
Let's imagine that this object is managed by the state of my app like this:
this.state = {
currentPalette: SamplePalette,
}
My question is how would I go about updating the val property of a given swatch object in the swatches array? Or more generally - how do I only update pieces of this object?
I tried using the update helper as well as to figure out how Object.assign() works, however I've been unsuccessful and frankly can't really grasp the syntax by just looking at examples.
Also, since I'm going to be modifying this object quite a lot, should I look into maybe using Redux?
[EDIT]
I tried #maxim.sh suggestion but with no success:
this.setState(
{ currentPalette: {...this.state.currentPalette,
swatches[0].val: newValue}
})
Consider you have new new_swatches
I think the clearer way is to get array, update it and put back as:
let new_swatches = this.state.currentPalette.swatches;
new_swatches[0].val = newValue;
this.setState(
{ currentPalette:
{ ...this.state.currentPalette, swatches: new_swatches }
});
Also you have : Immutability Helpers or https://github.com/kolodny/immutability-helper
Available Commands
{$push: array} push() all the items in array on the target.
{$unshift: array} unshift() all the items in array on the target.
{$splice: array of arrays} for each item in arrays call splice() on the target with the parameters provided by the item.
{$set: any} replace the target entirely.
{$merge: object} merge the keys of object with the target.
{$apply: function} passes in the current value to the function and updates it with the new returned value.
So this is a very simple question. Let's say we have an array of several objects. Then, with the usage of find function we select one object from array. If I change properties of that object, the changes are reflected in the array, as expected.
However, if I define a new variable with a different object and set it to the selected item from the array, the changes are not visible. E.g.:
let
arr = [
{
id: 1,
name: 'James',
prop: {
my: 'prop'
}
},
{
id:2,
name:'Delaney',
prop:
{
my:
'prop'
}
}
],
item = arr.find(x => x.id > 1),
newItem =
{
id: 3,
name:'test',
prop: {
my: 'test'
}
};
item = newItem;
console.log(newItem);
console.log(item);
console.log(arr);
Here I'd expect that since both item and newItem have the SAME value, the changes will be seen in arr as well, but it is not the case. If I change a single property on item, then the changes are visible. What gives?
A working example: http://jsbin.com/roxecofesa/1/edit?html,js,console
Imagine a variable item as a signpost. It points to where in memory is the object returned by the function arr.find(). Next you create another signpost newItem that points towards your newly created object.
When you write item = newItem you are saying: "Hey roadsign item, can you point towards the object that is currently pointed by newItem?"