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.
Related
I want to put the data from API to Vue state. What is the quick approach to do this?
getProjectData(id) {
axios.get(`/api/project/${id}`).then((res) => {
console.log(res)
/* I don't want to manually assign the value one by one (since the name is the same, there should be a quick way */
this.formState.name = res.data.projects.name
this.formState.stage = res.data.projects.stage
this.formState.status = res.data.projects.status
this.formState.description = res.data.projects.description
this.formState.stations = res.data.projects.stations
});
},
You can use spread operator to achieve it:
this.formState = {...res.data.projects}
To point out, these properties are copied shallowly. If a property value is an object, any modification made to it will reflect in the res.data.projects object.
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"
I'm currently trying to add a React component into the third layer of my multidimensional array. My structure is as follows:
constructor(props) {
super(props);
/**
* The state variables pertaining to the component.
* #type {Object}
*/
this.state = {
structure : [ // Block
[ // Slot 1
[ // Layer 1
<Text>text 1</Text>, // Text1
]
]
],
};
}
this.state.structure contains an array for the slots, which contains an array for the layers which contains an array consisting of Text components.
I've tried to use both concat() and push() to try to set the structure array, but both give me errors like "cannot read property of undefined", etc. This is my current addText() function:
addText() {
this.setState((previousState) => ({
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}));
}
The addText() function is called upon pressing a button in my application.
I'm also rendering my array of Text components on the current layer directly through the array:
{ this.state.structure[0][0] }
I feel like I'm staring the problem directly in the face, but have no idea what's causing the issue. I'm expecting to be able to add another Text to the container, but nothing I've tried to do seems to be working.
For one thing, you really should NOT store instances of React components directly in state. Doing so is just asking for trouble down the line, as rendering, state updates, key management and data persistence get more and more difficult to wrangle.
Instead, store a model of your components in state, generally in the form of the props they should have. Then, at render time is when you translate those models into React components:
this.state = {
structure : [ // Block
[ // Slot 1
[ // Layer 1
"text 1", // Text1
]
]
],
};
render() {
return this.state.structure[0][0].map(ea => <Text key={ea}>{ea}</Text>);
}
Doing it this way is better because: what happens if you want to read or modify the contents of your "layer" arrays? If they are text values you can simply read/modify the text. But if they are instantiated React components... there is basically no way to effectively update your state without jumping through tons of hoops.
On to your specific problem: the issue is that when you evaluate a statement like this:
previousState.structure[0][0].concat(<Text>text2</Text>)
the concat() function actually returns the value of the array after concatenation. And the value of that array is (in this case) now [<Text>text 1</Text>, <Text>text2</Text>]. So, you are actually updating the value of your state.structure field to an entirely different array. I'm guessing the error you are seeing - "cannot read property of undefined" - is because when you try to access this.state.structure[0][0] you are trying to access it as if it were a two dimensional array but it is now in fact a one dimensional array.
As another replier put it much more succinctly: you are entirely replacing state.structure with the contents of state.structure[0][0] (after concatenation).
Updating deeply nested data structures in React state is super tricky. You basically want to copy all of the original data structures and change only parts of it. Fortunately, with ES6 "spread" operator we have a shorthand for creating new arrays with all of the same items, plus new ones:
addText() {
this.setState((previousState) => {
// Copy the old structure array
const newStructure = [...previousState.structure];
// Update the desired nested array by copying it, plus a new item
newStructure[0][0] = [...newStructure[0][0], "text2"];
this.setState({structure: newStructure});
});
}
Main takeaways:
Don't store React components in state! Store raw data and use it at render time.
Be careful about what you are updating state to - it needs to be the SAME state plus a few updates, not the result of some atomic operation.
Read up a bit on how array operations like concat() and push() actually work and what their return values are.
I dont agree with this arr structure, and hooks, with an object seems much more conventional, but it could be your this binding. as ray said below structure[0][0] is replacing the state.
if your using addText Method it needs to be bound to the class. If its not using arrow syntax, it will show as undefined as it is not bound, and will result in undefined. Hope it helps
for example
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: '',
};
this.addText = this.addText.bind(this);
}
addText() {
this.setState((previousState, props) => ({
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}));
}
// ES6
addTextEs6 = () => {
this.setState((previousState, props) => ({
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}));
}
}
//ES6
/* Also you do not need to use concat you can use the spread Op. ... See MDN Spread Op`
Without getting into any philosophy about whether you should do it the way you're doing it, the fundamental problem is that you're replacing structure with structure[0][0] in your state.
{
// this line sets the whole structure to structure[0][0]:
structure : previousState.structure[0][0].concat(<Text>text2</Text>),
}
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 know how to do this but trying this way and not sure why it wont work?
drawCard = () => {
const deck = this.state.cards;
deck.shift();
console.log(deck, 'deck'); //this is correctly logging one less everytime
this.setState({cards: deck})
}
cards is just an of objects
so even though the function is being called and the console log is working, why is it not updating state?
(console.log(state.cards.length) always returns 3)
Don't use methods that mutate the state (for instance, Array#shift). You could instead use Array#slice to return a shallow copy of a portion of the array:
drawCard = () => {
this.setState({ cards: this.state.cards.slice(1) })
}
The problem arises because you are directly modifying the state. Although you are assigning the state to 'deck' remember that in Javascript arrays and objects are references. So when you are modifying deck you are actually trying to modify the state itself. You can use other methods to create a copy of the states values and modify that.
You may find this helpful:
React - Changing the state without using setState: Must avoid it?
in react, you can't just change the state,
you need to create a copy of your state before updating,
the easiest way to do it is just replace this:
const deck = this.state.cards;
into this:
const deck = [...this.state.cards];