Change a 2d array in React state [duplicate] - javascript

This question already has answers here:
Array.prototype.fill() with object passes reference and not new instance
(7 answers)
Closed 4 years ago.
I have a initial state like this:
constructor(props) {
super(props);
this.state = this.getInitialState();
this.setSqValue = this.setSqValue.bind(this);
}
getInitialState() {
return {
allSq: new Array(3).fill(new Array(3).fill(null))
};
}
Then in a method like setSqValue (called by clicking a button) when I assign a new value to a cell, strangely all other cells with any position in first dimensional and same position in second dimensional will take that value!
The second problem is that the state is changed while I didn't call any setState!
setSqValue(i, j) {
console.log(i, j);
let {allSq} = this.state;
let value = 'foo';
console.log('before', allSq);
allSq[i][j] = value;
console.log('after', allSq);
// this.setState({allSq});
}
The output in the console is this :
You can see the before and after values are same (why?!).
The state in the React developer tool:

In the chrome dev tools, objects aren't evaluated until you open them. Hover on the little icon to the right and you will see Object below was evaluated just now. . Try putting a debugger in there and I have a feeling you will see that your allSq in the before doesn't have foo in there. It seems as if everything is working correctly, you're just looking at the wrong things to see that.
Also, as another user said, .fill uses reference instead of value, so use .map instead.

In JavaScript, Arrays are a special kind of Object, and if you pass an object to fill(), it uses the same object each time. You should use map() instead, which will create a copy of the array.
allSq: Array(3).fill().map(() => Array(3).fill(null))
To prevent the state from being changed you can use the Object.freeze() method:
allSq: Object.freeze(Array(3).fill().map(() => Object.freeze(Array(3).fill(null))))
With this, the line allSq[i][j] = value; will ether throw an error (in strict mode) or silently fail (outside of strict mode).

Related

Props cloned to data variable updating when data variable changes [duplicate]

This question already has answers here:
How do I correctly clone a JavaScript object?
(81 answers)
Closed 2 years ago.
Problem: I have a prop in a Vue component.
props: {
user_info: {
type: Object,
default() {
return {}
}
}
}
I need to use this prop not only for read, but also to make some changes in it. But i cant change the prop, so i cloned it to a data variable
mounted() {
this.clonedUserInfo = {...this.user_info}
}
clonedUserInfo is an empty object in Data option.
But when i change clonedUserInfo, my prop user_info also changes.
So is there any way to brake this Data dependency from Props?
By the way if i am cloning not a Props but a Vuex state variables - it clones fine without changing the base State variable.
Yes there is a way.
mounted() {
this.clonedUserInfo = JSON.parse(JSON.stringify(this.user_info))
}
For now you are copying by reference. And thanks to my answer you will be copying by value.
Look at this SO answer to understand the difference : https://stackoverflow.com/a/430958/3956205
You are shallow copying user_info.
If you only have value only vars in user_info this will work, otherwise you'll update the original values.
You need to deep copy user_info.
LODASH
You can solve this using Lodash's CloneDeep.
npm i --save lodash.clonedeep
const cloneDeep = require('lodash.clonedeep');
NPM: https://www.npmjs.com/package/lodash.clonedeep
Docs: https://lodash.com/docs/4.17.15#cloneDeep
USE A FUNCTION
You can try this to deep clone objects:
function cloneDeepWithoutLodash(src) {
let target = {};
for (const prop in src) {
if (src.hasOwnProperty(prop)) {
if(src[prop] != null && typeof src[prop] === 'object') {
target[prop] = cloneDeepWithoutLodash(src[prop]);
} else {
target[prop] = src[prop];
}
}
}
return target;
}
const source = {a: { b:2 }, c:3};
const target = cloneDeepWithoutLodash(source);
source.a.b = 100; // Lets change the source to check if target changes
console.log(source.a.b); // 100
console.log(target.a.b); // 2
IMPORTANT
Always try to avoid JSON.parse(JSON.stringify(yourObject)).
By doing this you will lose any Javascript property that has no equivalent type in JSON, like Function or Infinity. Any property that’s assigned to undefined will be ignored by JSON.stringify, causing them to be missed on the cloned object.
Also, some objects are converted to strings, like Date objects for example (also, not taking into account the timezone and defaulting to UTC), Set, Map and many others.
You are creating a new object clonedUserInfo that is correct. But all objects inside of clonedUserInfo will be copied by reference.
Try smth like cloneDeep perhaps
https://lodash.com/docs/4.17.15#cloneDeep

How to change a vue data variable using this and (multiple) dynamic bracket notations

I am trying to achieve the following:
I start of with a p element that contains the data variable "reportText.Scope_1_T_1", this variable contains the string: "Text to change";
On creation of this component, created() gets called and it fires off a call to the method createObject. The method createObject requires multiple arguments, but the only relevant argument is the second one, which includes the location of the data variable I want to change (in this case: "reportText.Scope_1_T_1");
The method createObject splits this argument/location based on the dots and returns an array. So the string "reportText.Scope_1_T_1" returns the array ["reportText", "Scope_1_T_1"];
Following that this array gets looped through and combined with the context (=this). First loop results in context = this["reportText"], second loop returns in context = this["reportText"]["Scope_1_T_1"].
After this I assign a new String to context (context = reply.fields)
My expectation was that this code would result in a change of the data variable this.reportText.Scope_1_T_1, but unfortunately nothing happens to this variable.
I have tried playing around with dot notation and bracket notation, but nothing really worked. For example if I try to change the code in my createObject method to this:
this.reportText.Scope_1_T_1 = "New String"; or
this["reportText"]["Scope_1_T_1"] = "New String";
It suddenly does work? I don't understand why. I even tried to see if I somehow make a copy of 'this' so it doesn't reference the same object, but as far as I see it doesn't make a copy. It does seems to be a reference problem, because it somehow points to a different location when I use my dynamic brackets.
Here is my relevant code(if you need more, please let me know):
<template>
<p>{{ reportText.Scope_1_T_1 }}</p>
</template>
<script>
export default {
data: function() {
return {
reportText: {
Scope_1_T_1: 'Text to change'
}
}
},
created() {
this.$store.getters.getAppPromise.then(app => {
this.createObject(app, 'reportText.Scope_1_T_1', 'String', '=irrelevantExpression');
})
},
methods: {
createObject(app, location, type, expression) {
if (type === 'String') {
app.createGenericOjbect(
{
fields: {
qStringExpression: expression
}
},
reply => {
let context = this;
location = location.split('.');
location.forEach(item => {
context = context[item];
});
context = reply.fields;
}
)
}
}
}
}
</script>
I would greatly appreciate it if anyone could help me figure out what the difference is between using my dynamically created context and a static context (like this: this["reportText"]["Scope_1_T_1"]). I think that's the key in solving this problem.
My code is based on this stackoverflow question:
Javascript Square Bracket Notation Multiple Dynamic Properties
It's just the final step that won't work. Assigning a new value to context at the end will just update that local variable, not the property of the object.
Instead what you need to do is grab a reference to the relevant object and then update the property. To grab the object you need to drop the final section from the location path. That final section is then the property name that needs to be updated:
let context = this;
const path = location.split('.');
const property = path.pop()
path.forEach(item => {
context = context[item];
});
context[property] = reply.fields;
The syntax used for property access hides some asymmetry in how the parts of the path are interpreted.
Consider this example:
const a = b.c.d.e
What happens is:
Start with b.
Grab the value in property c.
Grab the value in property d.
Grab the value in property e.
Assign that value to a.
All nice and symmetric, c, d and e all seems to work the same way.
Now consider flipping that example:
b.c.d.e = a
This is very different.
Start with b.
Grab the value in property c.
Grab the value in property d.
Assign a to the property e.
In this scenario the c and d properties are still just read operations but the e is handled totally differently. That final part is a write operation instead.
The key thing to appreciate here is that the final part of a 'path' like this is special when you want to set the value. Normally this hides behind the syntax but when you want to break it down like in your example you need to be conscious of what is actually going on.
Whether you use . or [] notation makes no difference to this behaviour, though to access properties dynamically you have to use [].

Why is this object changing before the function is run? [duplicate]

This question already has answers here:
Is Chrome’s JavaScript console lazy about evaluating objects?
(7 answers)
Closed 3 years ago.
I have an object with an array (of class objects) as one of its values. I have a function that, in part, runs a class method on one of the objects inside that array (within the object).
When I run my code, printing the array before and after the function, the change is both present before AND after the function runs.
Why is this happening? Hoisting?
As a test, I created another key:value pair in the object such that the value is an integer, and changed my function to just bump that integer up 1. Here, it works fine - the print of my object before the function has that integer as 1, and then afterward has the integer as 2.
I also tried NOT using a class method on the object to make the adjustment, and it still failed.
class Book{
constructor (color, title, pagecount){
this.color = color;
this.title = title;
this.pagecount = pagecount;
}
changePages() {
this.pagecount += 50;
}
}
let book1 = new Book("Red", "Book1", 100);
let book2 = new Book("Blue", "Book2", 200);
let book3 = new Book("Green", "Book3", 300);
var myBookArr = [book1, book2, book3]
var myObj = {arr: myBookArr, integerTest: 0}
function thisDoesStuff(){
//other operations not related to myObj
myObj.arr[0].changePages();
}
When I run the below, in BOTH console.logs, it shows that arr[0] (which is book1) has 150 pages.
console.log(myObj);
changePages();
console.log(myObj);
I am expecting the first console.log to show book1 as its original value, then the function changes it.
Please, hover on the icon with i letter on it near your log.
You will see, that Chrome (if you using Chrome, of course) will say:
Value below was evaluated just now
It happens because objects and other complex entities are being passed by reference, not by value. So, when you are expanding you log, browser getting up-to-date value of the reference.
Try to console.log copy of your value (e.g. console.log({...myObj})), or use JSON.stringify or other string-like representation of your object.
Note, that it is not error in your code. It is just a feature of the console.log, so (if I interpreted it correctly) your code works just fine :)
Your code is correct, it's all based off when the browser decides to evaluate the object for console.log output.
See post here that another user was having a similar question:
Weird behavior with objects & console.log

Why my react state became not-immutable?

As I remember in past I could not update react-state manualy.
Now, I don't know why, I can do it.
My case:
and the property is changed here!It works! Why?
P.s: I use react16.02
the state does mutate in react. and u should be very careful that u don't override it like that or you will encounter unexpected result. it is always best to copy your state into another object, then change its properties as u like and after you are done use set state to set set your object state as copied object.
you can use Object.Assign or spread operator for this. spread operator is easier when you are using Arrays.
this.state = {
myObj : {propA: "A" , propB: "B"};
}
then at some point you want to change it
let myObj = this.state.myObj;
let copyMyObj = {...myObj};
copyMyObj.propA = "C";
this.setState({
myObj: copyMyObj
});
In javascript objects are mutable.
For example if
var a = {"something": 1}
and if you assign it to another variable b and if you change its value, the value of object a is also changed
var b = a;
b.something = 2;
console.log(b);
console.log(a);
//output {"something": 2}
both will print {"something": 2}. The value in a is also changed.
Similarly in your code you are directly assigning the state value in line number 61 and mutating it in line number 62.
So better way is to use
let newOptions = {...this.state.newOptions };
or
let newOptions = Object.assign({},this.state.newOptions}
This will create new object.
React state is mutable, so you can update react state directly. But it is recommended not to mutate react state due to multiple reasons. One of the reasons is, if you do this.state.count = 3, react DOM will behave in an unpredictable manner such as overwriting data and not re-rendering. Performance tuning is another reason.
React on calling setState() will call the render method in some near future which will cause the DOM to be updated with new values.

javascript's object property member change so strangely [duplicate]

This question already has an answer here:
Javascript Console Log reporting object properties incorrectly
(1 answer)
Closed 5 years ago.
var obj = {name:"小明"};
console.log("obj1",obj);
// 1. {name:"小明",age:10}
obj.age = 10;
console.log("obj2",obj);
// 2. {name:"小明",age:10}
why does the browser console display the result at the first? I think the reuslt should be an unchanged object {name:"小明"}.Now ,I have a headache about it . appreciate your response.
This is because you are seeing/expanding the object after adding the new property age.
Initially the object has only one property but by the time you are inspecting it , a new key will be added to the object
Another way is to verify using hasOwnProperty. In below snippet the first console.log statement won't be executed since the object does not have the key age
var obj = {
name: "小明"
};
if (obj.hasOwnProperty('age')) {
console.log("obj1", obj);
}
obj.age = 10;
console.log("obj2", obj);
This depends on the browser. If I recall correctly, chrome logs an unchanging object whereas firefox logs a changing one. Either way, obj.age will be undefined until you define it.
When you expand the object the values displayed in the console are evaluated at runtime.
Tooltip in Chrome console explaining that values are evaluated at runtime:
You could try JSON.stringify to print the current value to the console.
e.g.
console.log("obj1", JSON.stringify(obj));

Categories

Resources