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).
Related
I’m working on a solo project using React and I’ve been stuck on something for the past 2 days…I'm starting and I'm very beginner so it's maybe something very basic, but I'm struggling...
To try to be concise and clear:
I have a searchBar component, that searches through a local database, and returns objects associated with the search keyword. Nothing complicated so far.
Each rendered object has a button that triggers a function onClick. The said function is defined in my App component and is as follow:
changeState(term){
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{idToRender[0].recoInfo.push(response)})
})
this.setState({
objectToRender: idToRender
});
to explain the above code, what it does is that first, it identifies which object’s button has been clicked on, and send said object to a variable called idToRender. Then, it takes the reco state of that object, and store it to another variable called recoToFind. Then it calls the map() method on recoToFind, make an API request (the discogs() method) for each element of the recoToFind array and push() the results into the recoInfo state of idToRender. So by the end of the function, idToRender is supposed to look like this:
[{
…
…
recoInfo: [{1stAPI call result},{2ndAPI call result}…]
}],
The array contains 1 object having all the states of the object that was originally clicked on, plus a state recoInfo equal to an array made of the results of the several API calls.
Finally, it updates the component’s state objectToRender to idToRender.
And here my problem is, onClick, I do get all the states values of the clicked on object that get rendered on screen (as expected with how I coded the nested components), BUT, the values of the recoInfo are not displayed as expected (The component who’s supposed to render those values is nested in the component rendering the clicked on object other states values). However, they get displayed properly after a SECOND click on the button. So it seems my problem boils down to an state update timing trouble, but I’m puzzled, because this function is calling setState once and I know for a fact that the state is updated because when I click on the button, the clicked on Object details get displayed, but somehow only the recoInfo state seems to not be available yet, but only becomes available on a second click…
Would anyone have a way to solve this issue? :(
It somehow feels like my salvation lies in async/await, but I’m not sure I understand them correctly…
thanks very much in advance for any help!
Is this someting you want to do?
changeState(term) {
let idToRender=[];
this.state.dealersDb.map(dealer=>{
if(term===dealer.id){
idToRender=[dealer];
}});
let recoToFind=idToRender[0].reco;
recoToFind.map(item=>{
Discogs.search(item).then(response=>{
idToRender[0].recoInfo.push(response)
this.setState({
objectToRender: idToRender
});
})
})
}
you can call setState once async call is done and result received.
I have a reset button in my app that resets a few variables of my functional component:
const [selectedItem, setSelectedItem] = useState(0);
const [a, setA] = useState('a');
const [b, setB] = useState('blue');
<button onClick={e => ???}>clicky</button>
<button onClick={e => ???}>clicky</button>
There are two ways I could 'reset' the data: monitoring selectedItem for changes using useEffect, or have a handler that does so:
<button onClick ={e => setSelectedItem(e.target.value)} />
useEffect(() => {
setA(Math.random())
setB(Math.random())
}, [selectedItem])
or
<button onClick ={e => handler(e.target.value)} />
const handler = item => {
setSelectedItem(Math.random())
setA(Math.random())
setB(Math.random())
}
What are the practical differences between these approaches? The hooks docs say to use useEffect for performing side effects, but I can't see why this approach wouldn't work as well.
What is the difference between these approaches?
I will try to answer this question in three points.
Mental model
You need to "think in effects". the UseEffect hook lets you perform side-effects that manly need to happen async like (fetch Data from API, manipulate the DOM).
based on that it's better to use UseEffect to handle side-effects so you are not confusing your colleges.
Async
You need to keep in your mind that useEffect is an async function but your event handler is sync function. That can lead to totally different behavior maybe you are not seeing a weird behavior here but maybe in other examples, you will start to notice that.
React mechanism
the last difference to notice it you need to understand React update state mechanism, react makes patches to update the state. That means in your event handler the three-state will cause one re-render because they will happen at the same time. In your useEffect that is not the case, you are updating one of them that case re-render then you are performing the effect that will case new re-render.
Maybe there are other differences but that what can I see right now.
I hope it’s a useful answer.
There are a few peculiar differences between the above two methods.
In the first method of using useEffect, you would be updating states a and b whenever selectedItem changes, be it by a button click or some other sideeffect such as a prop change. However in the second case, states a and b would only be updated if selectedItem is updated on button click and you would need to call setA and setB to update states everywhere you update selectedItem separately
Secondly, when you are using a useEffect to update state, the state update will happen after updating selectedItem, however in the second case state updates doesn't gurantee that selectedItem is updated before setting the other states and hence if the other state updated depend on selectedItem value, you need to pass the updated selectedItem value to the other state updaters separately
In short, making use of useEffect is better when you know you have to take other actions whenever a state change occurs no matter how it occurs. Also its useful when you want to take action after a particular state is updated.
I believe it's important to consider the semantics of what you're doing. For example:
<button onClick ={e => handler(e.target.value)} />
const handler = item => {
setSelectedItem(Math.random())
setA(Math.random())
setB(Math.random())
}
this means that whenever you click the button you want the 3 state variables to be changed.
On the other hand:
<button onClick ={e => setSelectedItem(e.target.value)} />
useEffect(() => {
setA(Math.random())
setB(Math.random())
}, [selectedItem])
this means whenever you click the button you want that one state variable to change and independently of that you want, whenever that one state variable changes, to change those other two state variables.
The real question you should be asking is what is it you really want to express with your code, given that it has the same end result. In short, what makes semantic sense to you? Does it make sense to say "this button can be used to change those 3 state variables" or does it make more sense to say "this button can be used to chanage the selectedItem state variable and this entire component will change the a and b state variables whenever the selectedItem changes?
It is usually important to make sure your code makes semantic sense so you don't land in the pitfalls of getting unintended side-effects when you make code changes. For example, if selectedItem ends up being changeable by other means, the 2nd method will ensure that a and b change at the same time. Do you really want that?
There's also a practical consideration. There's the eslint rule called react/no-did-update-set-state which states:
Updating the state after a component update will trigger a second render() call and can lead to property/layout thrashing.
Layout thrashing basically means there's multiple potential redraws of the layout before a user can interact with it again. In the case of useEffect this can be an issue because useEffect is triggered after a layout update and setting the state might trigger another one. It usually has no noticeable effect on very simple operations but if you have a complex component hierarchy and end up re-rendering large portions of it then you will end up with a less responsive layout.
There's also the additional consideration that with the useEffect you also need to be mindful to avoid cyclical dependency changes e.g. selectedItem changes a and a changes selectedItem or makes a change which ends up changing selectedItem somewhere further down the line.
So overall there are three notes:
Use whichever makes more semantic sense for your component
Be aware of potential layout thrashing
If useEffect does make more sense take a step back and really think about why it makes more sense and whether there is a better way to solve your problem and at the same time avoid using useEffect to set state variables.
Ok the scenario you are referring to here is not really a side-effect, or I would say the side-effect react refers to. React refers to side-effect like if you are doing a network request
In your case if you just want to reset some variables I think having a clickHandler is the way to do, you would use a useEffect like if you want to do a network request when the component loads or some props change
Hope it clarifies
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
I'm new to React testing and I'm having a hard time figuring out the following issue:
I'm trying to simulate an input onChange event. It's a text input that filters the results in a table. InteractiveTable has a controlled input field (ControlledInput) and an instance of Facebook's FixedDataTable.
This is the test:
let filter = ReactTestUtils.findRenderedComponentWithType(component, ControlledInput);
let input = ReactTestUtils.findRenderedDOMComponentWithTag(filter, 'input');
input.value = 'a';
ReactTestUtils.Simulate.change(input);
console.log(component.state);
On input change the component updates its state with the value of the input, but since setState is asynchronous, here the console.log will log out the previous state, and I can't query the structure of the component for testing, because it's not updated yet. What am I missing?
Edit: to be clear, if I make the assertion in a setTimeout, it will pass, so it's definitely a problem with the asynchronous nature of setState.
I found one solution, where I overwrite the componentDidUpdate method of the component:
component.componentDidUpdate = () => {
console.log(component.state); // shows the updated state
let cells = ReactTestUtils.scryRenderedComponentsWithType(component, Cell);
expect(cells.length).toBe(30);
done();
};
This wouldn't be possible if the component had its own componentDidUpdate method, so it's not a good solution. This seems to be a very common problem, yet I find no solution to it.
Normally when I run into similar scenarios when testing, I try to break things apart a little. In your current test, (depending on your flavor of test framework), you could mock the component's setState method, and simply ensure that it's called with what you expect when you simulate a change.
If you want further coverage, in a different test you could call the real setState with some mock data, and then use the callback to make assertions about what's rendered, or ensure other internal methods are called.
OR: If your testing framework allows for simulating async stuff, you could try calling that too and test the whole thing in one go.
Seriously, this is like the double slit experiment of Javascript.
Basically what I have is a series of steps. What I want to happen is that when a user adds a new step to the list, the state for activeStepId gets set to be the newly added step (it gets highlighted in the list of steps).
As you'll see below, I'm listening to changes in my store and triggering the onChange event in the component.
Now, here's the crazy part:
Whenever I do the comparison newState.steps.length > this.state.steps.length to see if the new state has more steps than the previous state, somehow the old state has already been updated to the new state, despite the fact that setState hasn't even been called yet!
Now, I know you're probably thinking there's some other place in my code where I'm updating the state without realizing it. Yeah, I thought that too, but when I removed the call to setState below and then inspected the result, it worked! this.state.steps.length was what it should have been: the old value, not the new one.
I've spent hours on this at this point, and I have literally not a clue what's going on.
componentDidMount: function(){
FlowStepStore.on('change', this.onChange);
},
onChange: function(){
var newState = this.getState();
// If we've just added a step, set it as active
if(newState.steps.length > this.state.steps.length){
newState.activeStepId = newState.steps[newState.steps.length - 1].id;
}
this.setState(newState);
},
getState: function(){
return {
activeStepId: this.activeStepId(),
steps: FlowStepStore.getSteps()
};
},
I also tried doing it a different way, performing the step count comparison by using componentDidUpdate(prevProps, prevState) and comparing prevState against this.state, but I had the exact same problem! Somehow, prevState already equals the current state.
Mind blown.
Help!
#Heap's comment was correct. I didn't realize my state had a direct reference to a variable in the store (JS passes by reference, not by value), which was getting updated prior to setting the component state. Modified my store to return _steps.slice() instead of just _steps and now it works!