How does react update state? - javascript

I'm getting a very strange issue that I don't know how to address.
I've got a fairly straightforward method: When a button is pressed it called toggleInverted()
toggleInverted() {
if(this.state.inverted){
this.setState({inverted: false});
} else{
console.log("got to else...");
this.setState({inverted: true});
console.log(this.state.inverted);
}
}
The inverted field is initialized in the constructor to false. However the first time I click the button when I load the page, it doesn't correctly reset the state. The output is:
got to else...
false.
So somehow it is getting into this else statement, executing the setState, and yet not setting inverted to be true...
Is there something about setState that I am missing?

setState does not change your components state immediately. Your state change may be asynchronous and thus no guarantee that your console.log statement will print the changed state. setState however does always lead to the render method being called, so if you console log your state inside the render method of your component, your state should be true.
Also, this.setState({inverted: !this.state.inverted}) is a shorter version of your code snippit

setState may be asynchronous
Meaning you cannot expect the values of this.state to change immediately after setState is called.
To quote the docs:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value...
As an aside, you can figure your code by using the negative expression of the current inverted value as follows:
toggleInverted() {
this.setState({inverted: !this.state.inverted});
}

Related

Calling setState without triggering re-render

I am storing a UI state in the React component's state, say this.state.receivedElements which is an array. I want re-renders whenever an element is pushed to receivedElements. My question is, can I not trigger rendering when the array becomes empty ?
Or in general, can I call setState() just one time without re-render while re-rendering all other times ? ( are there any options, work-arounds ? )
I've read through this thread: https://github.com/facebook/react/issues/8598 but didn't find anything.
I want re-renders whenever an element is pushed to receivedElements.
Note that you won't get a re-render if you use:
this.state.receivedElements.push(newElement); // WRONG
That violates the restriction that you must not directly modify state. You'd need:
this.setState(function(state) {
return {receivedElements: state.receivedElements.concat([newElement])};
});
(It needs to be the callback version because it relies on the current state to set the new state.)
My question is, can I not trigger rendering when the array becomes empty ?
Yes — by not calling setState in that case.
It sounds as though receivedElements shouldn't be part of your state, but instead information you manage separately and reflect in state as appropriate. For instance, you might have receivedElements on the component itself, and displayedElements on state. Then:
this.receivedElements.push(newElement);
this.setState({displayedElements: this.receivedElements.slice()});
...and
// (...some operation that removes from `receivedElements`...), then:
if (this.receivedElements.length) {
this.setState({displayedElements: this.receivedElements.slice()});
}
Note how we don't call setState if this.receivedElements is empty.
What about useRef?
Documentation says:
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
So if you change ref value inside useEffect it won’t rerender component.
const someValue = useRef(0)
useEffect(() => {
someValue.current++
},[])

React keep to single setState call?

Supposed that in a function, i always need to set someState, and only need to set someOtherState if condition is true.
Is it preferable to do it like this:
this.setState({ someState });
if (condition) {
this.setState({ someOtherState });
}
Or this?
if (condition) {
this.setState({ someState, someOtherState });
} else {
this.setState({ someState });
}
I know React is optimized such that calling setState in quick succession will usually not result in a re-render. But is that behavior guaranteed or should the code make such assumption?
eg. supposed it works by re-rendering on a fixed time interval, if the first setState get called right before that interval block ends, then the second setState will result in a re-render?
Why don't you use ternary operator? If condition is true, set it to new state. Otherwise, use the old one.
this.setState(prevState => ({
someState,
someOtherState: condition ? newSomeOtherState : prevState.someOtherState
}))
React batches setState calls, so doing sequential update calls should only trigger one call to render.
You can assume that it will be batched provided you are within React managed event functions (React event system). For example, if it's an AJAX call, or some other delayed function like a promise or setTimeout, they will not be batched.
EDIT
This post has a pretty good summary of the order of events in most situations. You will find the state section about halfway down, but I'll try to summarise here:
Order goes :
Updating State
ShouldComponentUpdate

ComponentWillUpdate
Render
...
If you're calling multiple functions within one of these, React is smart enough to wait until they're complete before running the batched updates.
See the React docs here for details on setState:
(Link limit: facebook.github.io)/react/docs/react-component.html#setstate
and also a discussion on batching here:
https://groups.google.com/forum/#!topic/reactjs/G6pljvpTGX0

How to ensure I am reading the most recent version of state?

I may be missing something. I know setState is asynchronous in React, but I still seem to have this question.
Imagine following is a handler when user clicks any of the buttons on my app
1. ButtonHandler()
2. {
3. if(!this.state.flag)
4. {
5. alert("flag is false");
6. }
7. this.setState({flag:true});
8.
9. }
Now imagine user very quickly clicks first one button then second.
Imagine the first time the handler got called this.setState({flag:true}) was executed, but when second time the handler got called, the change to the state from the previous call has not been reflected yet -- and this.state.flag returned false.
Can such situation occur (even theoretically)? What are the ways to ensure I am reading most up to date state?
I know setState(function(prevState, props){..}) gives you access to previous state but what if I want to only read state like on line 3 and not set it?
As you rightly noted, for setting state based on previous state you want to use the function overload.
I know setState(function(prevState, props){..}) gives you access to previous state
So your example would look like this:
handleClick() {
this.setState(prevState => {
return {
flag: !prevState.flag
};
});
}
what if I want to only read state like on line 3 and not set it?
Let's get back to thinking why you want to do this.
If you want to perform a side effect (e.g. log to console or start an AJAX request) then the right place to do it is the componentDidUpdate lifecycle method. And it also gives you access to the previous state:
componentDidUpdate(prevState) {
if (!prevState.flag && this.state.flag) {
alert('flag has changed from false to true!');
}
if (prevState.flag && !this.state.flag) {
alert('flag has changed from true to false!');
}
}
This is the intended way to use React state. You let React manage the state and don't worry about when it gets set. If you want to set state based on previous state, pass a function to setState. If you want to perform side effects based on state changes, compare previous and current state in componentDidUpdate.
Of course, as a last resort, you can keep an instance variable independent of the state.
React's philosophy
The state and props should indicate things the components need for rendering. React's render being called whenever the state and props change.
Side Effects
In your case, you're causing a side effect based on user interaction which requires specific timing. In my opinion, once you step out of rendering - you probably want to reconsider state and props and stick to a regular instance property which is synchronous anyway.
Solving the real issue - Outside of React
Just change this.state.flag to this.flag everywhere, and update it with assignment rather than with setState. That way you
If you still have to use .state
You can get around this, uglily. I wrote code for this, but I'd rather not publish it here so people don't use it :)
First promisify.
Then use a utility for only caring about the last promise resolving in a function call. Here is an example library but the actual code is ~10LoC and simple anyway.
Now, a promisified setState with last called on it gives you the guarantee you're looking for.
Here is how using such code would look like:
explicitlyNotShown({x: 5}).then(() => {
// we are guaranteed that this call and any other setState calls are done here.
});
(Note: with MobX this isn't an issue since state updates are sync).

Issue with setState() in if statement

setState() in if statement:
// morePage = true
// pageNum = 1
if(morePage){
this.setState({pageNum: this.state.pageNum+1})
}
console.log(this.state.pageNum); // return: 1
setState() out if statement:
// morePage = true
// pageNum = 1
if(morePage){
// ...
}
this.setState({pageNum: this.state.pageNum+1})
console.log(this.state.pageNum); // return: 2
I'm facing that right now and I would like to know why...
Thanks :)
EDIT AFTER CORRECT ANSWER:
So yeah I should spend more time reading React's Doc :P
If someone is interested in knowing how I finally did, here is the answer:
In the docs, they say there is no guarantee that your state will have its new value before re-rendering. So you have to use "componentDidUpdate()".
So what I did is, I put the:
this.setState({pageNum: this.state.pageNum+1})
inside a random function I've created, after that in the "componentDidUpdate(prevProps, prevState)" function I can access the new and the old props values, and there I can use the previous and the current value of "pageNum" :)
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
As mentioned it is not guaranteed that the state value will be updated in your next line of code.
If you want to check the state for debugging try logging it in render method, as this method is invoked after the state is updated
So basically you problem is what others quoted from setState. But you can easily console.log too doing:
this.setState({pageNum: this.state.pageNum+1}, () => {
console.log(this.state.pageNum);
})
See the setState definition:
void setState(
function|object nextState,
[function callback]
)
With setState the current and previous states are merged. Hence the value. Also, its a synchronous so may give unexpected results.
NOTES:
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.
setState() will always trigger a re-render unless conditional rendering logic is implemented in shouldComponentUpdate(). If mutable objects are being used and the logic cannot be implemented in shouldComponentUpdate(), calling setState() only when the new state differs from the previous state will avoid unnecessary re-renders.
you can get more information from the official documentation
https://facebook.github.io/react/docs/component-api.html

How do I update the state (using ReactJS) if I should not call setState in componentWillUpdate?

When I setState in componentWillUpdate, componentWillUpdate runs in an infinite loop that doesn't stop getting triggered.
This never gives my render a chance to reflect my changes. How can I change the state if I shouldn't use componentWillUpdate?
Edit: I already have some understanding that setState should not be called in componentWillUpdate. I'm just confused what I should do as an alternative.
Edit #2: I started with componentWillReceiveProps but I can't seem to trigger this function when my Parent component changes state. I provide that state from the parent as a props to my child.
First thing to do is to check official documentation for this method (link). Where you can read when the function is actually called.
Then read common mistake(note):
You cannot use this.setState() in this method. If you need to update state in response to a prop change, use componentWillReceiveProps instead.
You change the state and React automatically calls componentWillUpdate.
I understand this is cautioned against in the guide but I am not sure I can see the problem with calling setState from within componentWillUpdate. True, it may result in infinite recursion unless of course you provide a bottom to that recursion (a way to break out of it). E.g. a way could be to check the second (nextState) parameter in componentWillUpdate and not invoke setState again if some condition is met (which is when the recursion ends).
As a minimal example, imagine a component that has no properties at all, only two pieces of state. Further imagine that the second piece of state is asynchronously obtained from the first. E.g. the first piece of state could be some parameter to provide to an Ajax call, and the second piece of state is the result of that call. So basically you call this.setState to configure the parameters and then inside componentWillUpdate you can do something like the following (to keep things simple I use a window.setTimeout as a placeholder for an Ajax call):
const ComponentWithAsyncState = React.createClass({
getInitialState: function() {
return {
ajaxParams: '',
ajaxResult: ''
};
},
setAjaxParams: function(params) {
this.setState({ajaxParams: params});
},
componentWillUpdate: function(_, nextState) {
if (nextState.ajaxParams!=this.state.ajaxParams)
window.setTimeout(function imagineThisIsAjax() {
this.setState({ajaxResult: `result from ${nextState.ajaxParams}`});
}.bind(this), 2000);
},
When, (e.g. through some controls managed by this component) the ajaxParams change, the sequence of actions will be something like (where ~~> denotes asynchronicity):
setAjaxParams --> this.setState --> componentWillUpdate ~~> imagineThisIsAjax --> this.setState --> componentWillUpdate
I.e. the second call to componentWillUpdate will not result in a further this.setState and thus the recursion will end there.

Categories

Resources