I have a button for pagination in my React component in render function. Can you help me to convert this conditional operator to setState? I have a lot of this yellow bugs in console trough conditional operator. I need to use it but I don't know how to use setState with it.
<button onClick={() => this.makeHttpRequestWithPage(this.state.current_page < this.state.total_pages ? this.state.current_page + 1 : this.state.current_page = this.state.total_pages)}>»</button>
Do not mutate state directly. Use setState()
I think you have misunderstood the concept of "immutability" in JavaScript.
Basically, if i have an immutable JS object, lets say state is:
state = {
name: "Dimitar",
age: 18,
hobbies: ["Programming", "Watching TV shows", "Studying"]
}
I cant directly mutate it this way:
state.name = "Something Else"
The object itself can be reassigned, but its values are set (constant). To get around this, React has created the setState function which basically makes a copy of the previous state object, reassigns new values to it and then overwrites the previous object.
So if you want to mutate the state, or alter its contents then you need to use the setState in such fashion:
setState({
name: "Dimitar2"
})
Here, you can also pass the prevState as a parameter and use it, but i will let you do the research on that. Also, you can use the spread operator etc.
<button onClick={() =>
this.setState({current_page: this.state.current_page < this.state.total_pages ? this.state.current_page + 1 : this.state.current_page}, () => this.makeHttpRequestWithPage(this.state.currentPage);)}>»</button>
Here you first update the current page based on the condition and after state is set you make the call.
Related
I wrote a piece of code that I don't quite remember how it works, embarrassing to say.
The state is as below:
state={
api:[
{
id: 1,
purpose: "For the Android phone",
link: "playgoogle/GooglePlay",
status: "1"
},
{
id: 2,
purpose: "For the Android phone",
link: "playgoogle/GooglePlay",
status: "0"
}
]
}
And this is the snippet of code that I don't quite understand.
activateAPI=(id)=>{
this.setState(prev => ({
api: prev.api.map(api => api.id === id ? { ...api, status: 1 } : api)
}))
this.changeStatus(id,1);
}
I understand that a value is passed into the activateAPI as id but I don't quite understand how the prev and api: prev.api works here. I understand that the api inside the map is like a foreach where it checks whether the current item's id in the json array matches the function id then what does {...api,status:1} mean?
Any clarification would be much appreciated. Thank you for reading.
The nature of setState is async. What you've written is pretty clear that when you call setState with its callback signature. Internally once the callback completes, React returns the callback to setState. And here's what you're doing,
(state, props) => stateChange
Above is the true signature of the setState that React provides.
State is a reference to the component state at the time the change is being applied.
For more info link
Also when you use spread operator, it spreads the object and updates the prop that is changed. So when you do {...api, status:1}. It will compare the API object from the initial one and updates the status value to 1.
Remember this will be the shallow comparison. For updating nested state objects you need to go to the nested level to observe proper state update.
api: prev.api.map(api => api.id === id ? { ...api, status: 1 } : api)
That mean , When mapping array if it find the api with the id matched, it with return the new api object with property 'status' change to '1' . If not it will just return the old api object
sorry for my bad english :D hope it help
What the Spread operator does is that it creates a new object.
So in this case when you are doing {...api, status: 1} a new object is getting created with all the keys present in api object and then overriding the status value with 1 when api.id === id is true.
let x = {key: "Key", val: 1, val2: 22}
//spread and update the value, it'll only update `val` all other values wil be as is
console.log({...x, val: 2})
//the original object will be as is
console.log(x)
let y = {z: {key: "Key", val: 3}, z1: 2}
//assign y to y1 by spreading
let y1 = {...y}
y1.z.val = 4;
//z.val in both y and y1 will be modified because spread will not spread deep objects
console.log(y1)
console.log(y)
Coming to the arrow function in setState will accept a function as first parameter. React will pass state and props as params to the passed function. This way you will be able to access the state in order to update it by using old state.
Below is the setState function
setState(updater, [callback])
The first argument is an updater function with the signature:
(state, props) => stateChange
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.
Hope this helps.
I'm trying to update the state of the id value in shoeList. Currently I have a textfield that allows for entering of a new ID and I want the state to update when the OK button is clicked.
Here is some of the relevant code:
state = {
editingToggle: false,
shoeList : [
{name: 'bob', id: '123213-0', shoeSize: 'L'}
],
}
<TextInput className='text-area-header' id="textM" width="m" type="text" placeholder={this.state.shoeList[0].id} />
<Button className="ok-button" variant="tertiary" size ='xs' type="button" handleClick={this.saveHeader}>OK</Button>
saveHeader(e) {
this.setState(state=> ({
shoeList[0].name:
}))
alert('Header changed to ' + this.state.shoeList[0].id);
e.preventDefault();
}
I'm not sure what to put in this.setState as I haven't found anything on how to update nested values through a google search. Also whenever I put a value attribute to the TextInput tags it doesn't allow me to edit in the textinput on the webpage anymore. Any help would be great
Consider this example:
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.setState({ shoeList: this.state.shoeList })
}
setState() checks if the values have changed, and if not, it does not update the component. Changing a nested value does not change the reference to the array, which means that simply calling setState() is not enough.
There are two ways around this. The first is to use forceUpdate():
saveHeader() {
this.state.shoeList[0].id = 'newid'
this.forceUpdate()
}
This forces the component to re-render, even though the state didn't change.
The second way is to actually change the shoeList array by creating a new one:
saveHeader() {
let newShoeList = this.state.shoeList.slice()
newShoeList[0].id = 'newid'
this.setState({ shoeList: newShoeList })
}
Using .slice() on an array creates a new array that is completely identical. But because it is a new array, React will notice that the state changed and re-render the component.
Is there a real disadvantage to modifying part of prevState and returning that part inside setState() ?
Example:
this.setState(prevState => {
prevState.myObject.isItTrue = !prevState.myObject.isItTrue;
return {myObject: prevState.myObject};
});
Rather than:
this.setState(prevState => {
const myObject = Object.assign({}, prevState.myObject);
myObject.isItTrue = !myObject.isItTrue;
return {myObject: myObject};
});
Is there any real disadvantage to the first code where I save myself the Object.assign() ?
EDIT: If I am correct, prevState.myObject is simply a reference to this.state.myObject, so changing prevState.myObject actually changes this.myObject.object as well! However, this doesn't seem to break anything, as long as I use setState() to pass an object that contains the new data, even if it's just a reference to the old objects inside this.state.
Do you agree that this is still ok, i.e. it won't break anything to do it like this?
Following documentation:
state is a reference to the component state at the time the change is being applied. It should not be directly mutated. Instead, changes should be represented by building a new object based on the input from state and props.
https://reactjs.org/docs/react-component.html
So you should not apply changes directly to that state.
Either way, why not do something like this?:
this.setState(prevState => ({
myObject : {
...prevState.myObject,
isItTrue: !prevState.myObject.isItTrue,
}
}));
This way will get all the elements from the prevState but also change all the ones you want to modify.
First prevState and the this.state are the same object. So you are modifying the actual state directly.
Secondly, down the road you might pass the myObject as a prop to another component, and since it will be the same object always that component will not know that something has changed and it will not re-render (for example PureComponent and ones that implement componentDidUpdate or shouldComponentUpdate and test for changes)
See https://codesandbox.io/s/zen-aryabhata-m07l4 for showcases of all issues.
So you should use
this.setState(state => ({
myObject: {
...state.myObject,
isItTrue: !state.myObject.isItTrue
}
}));
this is a piece of code I came across and I am not sure what it is doing actually?
it feels to me like it is mutating the state
Version 1:
this.state.items = [...this.state.initialItems];
Now, is the above code mutating state??
And can the above also be written as Version 2:
this.setState({
items:[...this.state.initialItems];
});
the thing is that version 1 is working but version 2 isnt.
So,what's wrong here?
what am I missing?
what does ...this.state.initialItems do? is that the spread operator?
This code is definitely mutating state. Don't do it:
this.state.items = [...this.state.initialItems];
Yes, the ... is a spread operator in this context.
For version 2, make sure you don't use the semicolon.
It should just be :
this.setState({
items:[...this.state.initialItems]
});
And of course like the poster below has said, it's asynchronous so you won't see the results right away.
Augmentation 1:
If you want to do things after your state is ready, you can add a callback as your 2nd argument to setState.
this.setState({
items:[...this.state.initialItems]
}, ()=>{
// do stuff
}
);
Augmentation 2:
Your first argument to setState can be a function as well, as the other poster has mentioned, so that when you access state it will be the "freshest" copy, and not the one that was captured when you created the object that you passed to setstate:
this.setState((stateNOW) => {
return {items:[...stateNOW.initialItems]};
}, ()=>{
// do stuff
}
);
Version 1 is wrong. But the real question is Why?
state is a special object in React and it is not to be mutated.
Say you declare an object:
const obj = {
label: 'doe'
value: 'foo'
}
Then, you update the obj with
obj.value = 'bar'
This is the process of mutation and should never perform it with a state variable. Instead we use setState as a request to change the state.
... is the spread syntax.
As the other answers have mentioned, the reason why you think it is not working might be because state updates are asynchronous.
You have to use the setState callback or componentDidUpdate lifecycle hook to log the value of state.
setState(stateChange[, callback])
Another issue in the code is the use of this.state.something inside setState. Like we already mentioned before setState is asynchronous and a number of state updates may be merged. So using this.state.initialItems may not always work out for you, if you miss a update.
this.setState((prevState) => {
return {items: [...prevState.initialItems]};
});
This would be the right way to use a value of previous state.
Version 1 is mutating your state and this is a bad practice.
Version 2 is not and the reason why it's not working (actually it is) is that calls to setState are asynchronous and therefore you will not have access to its up-to-date value in the next line.
Should you need access to the new state value, you can use:
this.setState(prevState => ({
items: [...prevState.initialItems],
}), () => {
console.log(this.state) // new state value
})
Please also refer to this link in ReactJS docs about how to set state properly.
The ... is indeed the spread syntax.
You can use it, for example:
To add items to an array
const arr = [1, 2, 3, 4]
const newArr = [...arr, 5, 6] // This will spread everything in arr and add numbers 5 and 6 to newArr
console.log('arr', arr)
console.log('newArr', newArr)
To get items from an array
const arr = [1,2,3,4]
const [firstItem, secondItem, ...others] = arr
console.log('firstItem:', firstItem)
console.log('secondItem:', secondItem)
console.log('others:', others)
To create a new object
const obj = {
name: 'Stack Overflow',
age: 10,
}
const newObj = {
...obj, // gets all properties from obj
age: 12, // but only change age
}
console.log('obj', obj)
console.log('newObj', newObj)
I have a component structure like this
<A>
<B>
<C/>
<C/>
</B>
<D>
<E/>
<E/>
</D>
</A>
Idea is that actions on components in block E are processed by a function of component A to state of A and than passed down to B and C as props. I know, that better way was to use Flux, pubsub-js or other Store-message system, but hope if someone can explain why correct to the best of my understanding solution doesn't work.
Calling this function of component A simalteneously from multiple instances of component E leads to race condition with only one change in state (instead of each function call providing a change)
updateState(value,index){
this.setState(update(this.state, {
a: {
[index]: {
b: {
$set: value
}
}
}
})
);
}
Function update here comes from
import update from 'react/lib/update';
Bad solution that goes against ReactJS reccomended practices, but works well:
updateState(value,index){
this.state.a[index].b=value;
this.forceUpdate();
);
}
My question is:
Is it a bug, that multiple simalteneous setState invokes a race condition, or I'm doing something wrong without understnding it?
You probably want to pass a function to setState which should remove such race conditions. Something like:
this.setState(prevState => {
return {
someProp: prevState.someProp + 1,
};
});
Even if two different parts of your application do this at the same time, both functions will still be called and someProp will be incremented twice.
If you were to do this instead: this.setState({someProp: this.state.someProp + 1}) it could only be incremented once because this.state.someProp isn't updated directly after calling setState. But when passing a function to setState you get the previous state as an argument, which lets you avoid data races.
According to React documentation, as mentioned in the first answer, you can pass a function to ensure the atomicity's operation. The thing is that you should not modify the state but return the new value inside the passed function:
setState(function(previousState, currentProps) {
return {myInteger: previousState.myInteger + 1};
});
https://facebook.github.io/react/docs/component-api.html