I am using React.js and I'm trying to update the state of my component when the props change. Before I explain the problem, I should mention that I have used both getDerivedStateFromProps and componentDidUpdate, the result is the same. The problem is that when I try to access the value of an element in prop, it differs whether I access the value directly or I use the object itself.
let userTickets = nextProps.support.userTickets;
// userTickets[0].messages is different from nextProps.support.userTickets[0].messages
below is the whole function code.
let userTickets = nextProps.support.userTickets;
console.log(nextProps.support.userTickets); // this contains the correct, updated value
for (let index = 0; index < userTickets.length; index++) {
let userTicket = userTickets[index];
console.log(userTicket); // this contains old incorrect value
}
Any guidance would be appreciated. Thanks.
Try to not directly assign props to variables because it may assign by reference instead of copy. Try this instead:
const userTickets = [ ...nextProps.support.userTickets ];
console.log(nextProps.support.userTickets);
userTickets.map(ticket => {
console.log(ticket);
});
Notice the 3 dot assignment of the array, this creates a new array with the values of the array that is "spread"
Related
I have a SPA built with Vue and I'm trying to fetch an array of items from a custom API.
Once I've stored the items I'm trying to loop through that array and push in a custom path value, however I get the error
this.list[i].push is not a function
I'm not sure why this wouldn't be possible, but I'm sure I've missed something.
this.list: []
fetch(){
let url = 'http://core.com/api/v0/sources/'
axios.get(url).then((response) => {
this.list = response.data.data
for(var i = 0; i < this.list.length; i++){
let arr = { path: '/testPath' }
this.list[i].push(arr)
}
})
}
In the comments you mentioned your goal:
"this.list[i].path does not exist, that is what Im trying to create"
...if you literally want to add a property called "path" directly under the object at this.list[i] then you'd write
this.list[i].path = '/testPath'
This will create a new property for the object being held at this.list[i].
P.S.
You don't need your (badly-named) arr variable at all here.
Using .push() doesn't make any sense (and doesn't work) because this.list[i] contains an object rather than an array, and also doing that wouldn't create a property as you wanted.
push is the function of an array but you are trying to push an object in an object that's why you got this exception.
you can do as below.
this.list[i].path = '/testPath'
path property will be dynamically added in your object this.list[i].
I am having trouble maintaining the original value of a variable after making new changes to the original variable.
Code:
(...)
data = Illumination.calculate_N(data)
data = Illumination.calculate_pi(data)
data = Illumination.calculate_kwh(data)
data = Illumination.calculate_ca(data)
let data_base = data
let ca_base = data.ca
let kwh_base = data.kwh
let pi_base = data.pi
(...)
data = Illumination.calculate_N(data)
data = Illumination.calculate_pi(data)
data = Illumination.calculate_kwh(data)
data = Illumination.calculate_ca(data)
let data_proposto = data
let ca_proposto = data.ca
let kwh_proposto = data.kwh
let pi_proposto = data.pi
-----------------------------------
EXAMPLE:
static calculate_ai(data){
data.ai = data.areaTotal*data.au
return data
}
It was expected that the original variable (date) would have its values changed, and this happens correctly, however, the variables data_base and data_proposto are not keeping their values
Both variables at the end of the calculation have the same values as the variable date
The variables ca_proposto, ca_base, and the like store their values correctly
Any idea?
The only interactions of the variables data_base and data_proposto were their creations with the data variable and their return of the function
OBS: If I use console.log () to view the value of the data_base variable before redoing the new calculations (Illumination.calculate_N (data)), the value of the variable appears correctly as it should, it is changed shortly after these calculations.
Because in both cases you are assigning not the object itself in the current state, but a reference to that object. What you need to do is to clone the object so the state is frozen at that point.
Simple Clone (Shallow Copy)
let data_base = Object.assign({}, data); //you get a clone of data
let data_proposto = Object.assign({}, data);
The limitation here is that it only does a shallow copy. See Deep Copy below for further explanation.
JSON Clone
This is a quick-and-dirty way to clone as it converts a JSON object to a string, and then back. i.e. you are no longer getting a reference, but a new object.
let data_base = JSON.parse(JSON.stringify(data));
let data_postero = JSON.parse(JSON.stringify(data));
But this won't work if your object is not JSON-safe.
Deep Copy
The least elegant method is probably safest. It deep copies the properties over into a new object. The key difference with Object.assign() is that it copies the values of nested properties, whereas Object.assign() copies the reference to nested objects.
So with Object.assign() any subsequent changes in your nested objects will affect all versions of your "clones". This won't happen if your clones only have property values of those nested objects at the time of cloning – these values are not affected by any changes to the nested objects.
const deepCopy = function(src) {
let target = {};
// using for/in on object also returns prototype properties
for (let prop in src) {
// .hasOwnProperty() filters out these prototype properties.
if (src.hasOwnProperty(prop)) {
target[prop] = src[prop]; //iteratively copies over values, not references
}
}
return target;
}
let data_base = deepCopy(data);
let data_postero = deepCopy(data);
#chatnoir Defined the problem very well, But I do not agree with his JSON serialization solution due to the below probleam:
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.
My suggestion to perform deep copy is to rely on a library that’s well
tested, very popular and carefully maintained: Lodash.
Lodash offers the very convenient clone and deepclone functions to perform shallow and deep cloning.
Lodash has this nice feature: you can import single functions separately in your project to reduce a lot the size of the dependency.
Please find the running sample code here: https://glitch.com/edit/#!/flavio-lodash-clone-shallow-deep?path=server.js:1:0
You are using the same variable data inside and outside functions.
ie; data is in the global scope.
static calculate_ai(data){
data.ai = data.areaTotal*data.au
return data
}
even though you are expecting the scope of the variable data inside the method calculate_ai to be limited to that method, it is not the case. data is in global scope and therefore, the value changes inside the method for the variable affects outside as well.
An effective solution is to use a different variable inside the method.
A variable is like an octopus tentacle, and not as a box (as it’s commonly described). In this analogy, the variable's name can be thought of as the name of a tentacle.
A variable (tentacle) holds on to a value in what’s called a binding. A binding is an association of a variable to a value: x = 1.
In JavaScript, if a variable b holds on to variable a, changing the value to which variable a holds onto, will change the value to which variable b holds onto, as b and a are referencing to the same value:
let a = {key: 1}
let b = a
console.log(`a: ${a.key}`) // -> 1
console.log(`b: ${b.key}`) // -> 1
a.key = 2
console.log(`a: ${a.key}`) // -> 2
console.log(`b: ${b.key}`) // -> 2
a = {key: 3} // This will point variable 'a' to a new object, while variable 'b' still points to the original object.
console.log(`a: ${a.key}`) // -> 3
console.log(`b: ${b.key}`) // -> 2
I am trying to update the state value but first I have loaded it in another varand after changes made on the new var it is going to setState and update the original state value. but unexpectedly changes made to the temp var already changes the component state value!
var postData = this.state.postData;
postData.likes = postData.likes + 1;
console.log(postData.likes, this.state.postData.likes);
the console.log values:
(1, 1)
(2, 2)
...
Since postData is an object, don't save it directly in another variable since that will create a reference to the one in the state, not a copy. And if it's a reference, changing one will change the other cos they're both pointing to the same object.
Make a copy of it first"
var postData = Object.assign({}, this.state.postData)
and then change it. Then when you're done, use setState({postData})
you should never ever mutate state, always use setState, and for copying object user spread notation "..."
this.setState(((prevState) => ({
postData:{
...prevState.postData,
likes: prevState.postData.likes + 1,
}
});
You can also use ES2018 spread notation like var postData = {...this.state.postData}; to clone the object and manipulate it before assigning back to state.
I've read that it is not advisable to update state directly, e.g:
this.state.array = ['element'];
But, for example, consider this state:
this.state = {
array: [],
}
updateArray = element => {
temp = this.state.array;
temp.push(element);
this.setState({array: temp});
}
Isn't this essentialy the same as the first example? We are setting temp to point to this.state.array, thus mutating it and then overwriting it with this.setState().
I've seen this example in various tutorials, Wouldn't it be 'better' to make a copy of the array?
Check out the docs here, https://reactjs.org/docs/state-and-lifecycle.html.
According to the React team. Setting the state directly will not re-render a component. You must use setState.
As far as using a temp variable when setting state, that is used when something needs to be done to 'element' before setting the state variable. As setState can be called asynchronously. It is acceptable in your case to just do
updateArray = (element) => {
this.setState({ array: [ ...this.state.array, element ] });
}
Yes, you are right it's the same thing. You should read more about pass by value and pass by reference in JS.
When we say
let x = some.array
We are copying a reference(remember copy and paste as shortcut)? Both the things actually point to the same real thing and takes no extra space.
You should instead use some JS that can do this for you.
let x = some.array.slice()
This creates real copy of the array. On modern ES2015 you can do
let x = [...some.array]
a more elegent way.
I am receiving a JSON data from database which is
[{"table_cols":["id","stud_name","stud_school"]}].
console.log("Response is: " + resp._bodyText); {/*Response is: [{"table_cols":["id","stud_name","stud_school"]}]*/}
let parsedData = JSON.parse(resp._bodyText);
console.log(parsedData) //Object
this.setState({...this.state,cols_of_sel_tab:parsedData})
for(let i=0; i<this.state.cols_of_sel_tab.length;i++)
{
cols = [];
console.log(this.state.cols_of_sel_tab[i])
let cols = this.state.cols_of_sel_tab[i]
console.log("WOW")
for (let j = 0; j<cols.length; j++){
console.log(cols[j])
}
}
Output: {table_cols: Array(3)}
WOW
Expected: To iterate through the inner for loop
Desired output is: id, stud_name,stud_school
Tried: using forEach instead of inner for loop
Error: ...\node_modules\react-native\Libraries\ReactNative\YellowBox.js:82 Possible Unhandled Promise Rejection (id: 0):
TypeError: Cannot read property 'forEach' of undefined
TypeError: Cannot read property 'forEach' of undefined
I need help!
State updates are asynchronous. If you want to loop through the data you've received right then, use parsedData, not this.state.cols_of_sel_tab.
Separately, never do this:
this.setState({...this.state,cols_of_sel_tab:parsedData});
If you're setting state based on your current state, you must not pass in an object; instead, you must pass in a callback function that uses the state it receives as an argument (see the linked docs for details).
But in this case, you don't need the ...this.state at all, so it would be fine if you just did this:
this.setState({cols_of_sel_tab:parsedData});
...and then (again) looped through parsedData not this.state.cols_of_sel-tab.
Separately, you have a problem with cols: You're trying to use it in a block before it's declared with let. If you'd really run that code, it would have failed with an error saying cols is not defined (because you cannot access an identifier declared with let or const within a block prior to the declaration, even if it exists in an outer scope).
1. Solution to your question
The main problem why you (somehow without syntax error and not getting the empty state problem) get the
Output:{table_cols: Array(3)} WOW
and not the array items, because of the model of your response
[
{"table_cols":
["id",
"stud_name",
"stud_school"]
}
]
So, instead of
let cols = this.state.cols_of_sel_tab[i]
should be
let cols = this.state.cols_of_sel_tab[i].table_cols
And input will be:
{table_cols: Array(3)}
WOW
id
stud_name
stud_school
2. Syntax Error
cols = [];
let cols = this.state.cols_of_sel_tab[i]
You're trying to assign empty array to the variable, that not defined.
In this particular example you could just amend the first row
3. State handling
In your code example you've got two goals: update the state with new data and use the new data for output. Cause state updates are asynchronous is much safer to manipulate and output the data you already have right away (I amended debug console output).
this.setState({cols_of_sel_tab: parsedData})
for(let i=0; i<parsedData.length; i++) {
let cols = parsedData.cols_of_sel_tab[i].table_cols
for (let j = 0; j<cols.length; j++) {
console.log(cols[j])
}
}
Also, cause states updates are merged you can amend spread operator for this.state, like in the code above.
4.More improvements
There is a map function mention in header of the question, you could use it.
parsedData.map(
col_of_sel => col_of_sel.table_cols.map(
cols => console.log(cols)
)
)
or, if you only need the table_cols of the first object:
parsedData[0].table_cols.map(cols => console.log(cols))
You've got different code style in your example in question: semicolon presence, differences in indentations. I recommend to use some kind of prettier to pretty up the code, so it will be easier to read.
Finally
let parsedData = JSON.parse(resp._bodyText)
this.setState({ cols_of_sel_tab: parsedData })
parsedData[0].table_cols.map(cols => console.log(cols))
The problem is a slight misunderstanding of what your JSON data represents.
If we make the JSON a little prettier:
[
{"table_cols":
["id",
"stud_name",
"stud_school"]
}
]
The outer square brackets represent an array. This array contains one item at index 0, which is the table_cols object. This object also contains an array, with your table col fields in.
You're running a for loop on the outer array. The first item this loop will come across is the object at index 0. To reach the table columns you'd need to either run a second for loop on the table_cols, or run the original loop on this.state.cols_of_sel_tab[0].table_cols.