In the reactjs docs for setState:
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.
The second (optional) parameter is a callback function that will be
executed once setState is completed and the component is re-rendered.
What if I just wanted to update my state, do I create a callback that does nothing?
As they write setState() does not immediately mutate this.state but creates a pending state transition. because it works in an asynchronous way. So if you want to perform an action immediately after setting state on a state variable then a callback will be useful.
For Example
setState(
{ name: "Hello World" },
() => console.log(this.state)
);
The callback is optional so you can do this.setState({ key: value });.
Related
I am confused with how setState works.
Here is a portion of my code:
handleBarsChange(value) {
this.setState({numberOfbars: value});
this.resetArray();
}
apparently, when the resetArray() function executes and tries to access the this.state.numberOfbars state variable, setState hasn't updated the value yet.
I want some explanation on how and where should the setState be executed and when can i expect to see the change.
Thank you!
setState is asynchronous. It has a second callback argument that can be used to run code after a state transition. This isn't always the best approach, but with the very narrow example provided, it's hard to say.
this.setState({numberOfBars: value}, () => { this.resetArray() })
From the react docs:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
https://reactjs.org/docs/react-component.html#setstate
The fact is that setState() function is async so even if you run code after you call it, some state may not be updated yet.
The solution is implement a callback when the state was updated succesfully (setState(<state>, <callback>)), for example:
handleBarsChange(value){
this.setState({numberOfbars: value}, () => {
this.resetArray();
});
}
I know that calling setState method means I don’t have to manually invoke the ReactDOM.render method. Below is some example code:
...
render() {
return (
<button onClick={this.handleClick}>
Click
</button>
)
}
handleClick = () => {
this.setState({ counter: this.state.counter + 1 }, () => this.setState({ hasButtonBeenClicked: this.state.counter > 0 }));
this.props.callback();
}
since there is another statement this.props.callback(); below the this.setState() method, so does ReactDOM.render method get called before or after this.props.callback();?
since there is another statement this.props.callback(); below the this.setState() method, so does ReactDOM.render method get called before or after this.props.callback();?
After, in your case. The state update and call to render is asynchronous if setState is called within a React event handler (and may be asynchronous even if not, more here). The sequence (within a React event handler) is:
You call setState with the state update.
You call this.props.callback();
React processes the state update (possibly combining it with other pending updates).
(Some handwaving here about shouldComponentUpdate.)
React calls render to have it render the current state.
If you want this.props.callback(); called after the new state has been rendered, put it in a function as the second argument of setState:
handleClick = () => {
this.setState(
{ counter: this.state.counter + 1 },
() => {
this.props.callback();
}
);
}
or
handleClick = () => {
this.setState(
{ counter: this.state.counter + 1 },
this.props.callback
);
}
...if this.props.callback doesn't care what this you call it with.
More here:
State Updates May Be Asynchronous (that title drives me nuts, because it's not that they may be asynchronous, it's that they are asynchronous)
setState API docs
If you look at my example of your code, this.props.callback() gets called immediately after the state has been updated.
handleClick = () => {
this.setState({ counter: this.state.counter + 1, hasButtonBeenClicked: true }, () => this.props.callback() );
}
You have chained setStates, which seem unnecessary for readability. These should be grouped into a single setState call automatically, however one is nested into the callback and this would trigger multiple re-renders.. depending on ShouldComponentUpdate.
To avoid guessing or leaving it susceptible to future React updates:
Using the setState callback for this.props.callback, is the best way to ensure it is executed after setState completes.
Updated, based on T.J Crowders feedback and research with event handlers:
The way your code was structured it is most probable that this.props.callback will be called prior to the setState actually completing state updates, it will trigger the re-render once state updates.
-Because, it is in an asynchronous call and within a React event handler. SetState should update state after.(I am still no expert on promises, and have to wonder if there is a chance state could update within the millisecond, depending on your browsers internal clock and the batch processing)
For clarity, to others. This example is asynchronous and that means the code continues to be executed, while waiting for resolution. While setState returns a promise. In this case, it should absolutely continue to process this.props.callback(), until the promise is resolved.
i am trying to get data from an external API and use it to change the state on my app. The data actually shows up in the console but when i run set state does not change the state of my app.
class App extends Component {
state={
jobs:[]
}
onTermSubmit=(term)=>{
const proxy=`https://cors-anywhere.herokuapp.com/`;
const api = `${proxy}https://--------/positions.json?description=${term}&page=1`;
fetch(api).then(res=>res.json()).then(data=>this.setState({jobs:data}))
console.log(this.state)
}
I am trying to get the state to change to the data returned from the API
Both fetch() and setState() are asynchronous so attempting put a console.log() statement after the expression will not wait for the fetch or setState() to complete/resolve. Instead you can use the callback argument of setState() to console.log() only after fetch has resolved and setState() has updated state. From the documentation:
setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.
fetch(api)
.then(res => res.json())
.then(data =>
this.setState({ jobs: data }, () => {
console.log(this.state);
})
)
.catch(err => console.log(err));
Hopefully that helps!
As mentioned by #Alexander Staroselsky fetch() and setState() are asynchronous operations and the application won't wait until they are done to continue to the next operation thus resulting in this.state.jobs to still be an empty array.
My solution would be to make them synchronous by adding the async/await keyword before calling he function like this.
onTermSubmit= async (term)=> {
const proxy=`https://cors-anywhere.herokuapp.com/`;
const api = `${proxy}https://--------/positions.json?description=${term}&page=1`;
let rawRes = await fetch(api)
let jsonRes = await rawRes.json()
await this.setState({jobs:rawRes}))
console.log(this.state)
}
Just another approach
Hope this is helpful
I am having issue with state as i'm not 100% i'm using componentDidMount and componentWillMount correctly.
I have set the constructor and super props, and I am getting a user object with the getCurrentUser() method and setting the user state with the new object.
componentWillMount() {
const current_user = getCurrentUser().then(user => {
this.setState({
user: user
});
console.log(user);
});
}
componentDidMount() {
console.log(this.state.user);
}
It logs the user correctly in componentWillMount, but logs an empty object in componentDidMount.
Any guidance would be massively appreciated!
Simply don't use componentWillMount,
Do it in componentDidMount.
In practice, componentDidMount is the best place to put calls to fetch data, for two reasons:
Using DidMount makes it clear that data won’t be loaded until after the initial render. This reminds you to set up initial state properly, so you don’t end up with undefined state that causes errors.
If you ever need to render your app on the server (SSR/isomorphic/other buzzwords), componentWillMount will actually be called twice – once on the server, and again on the client – which is probably not what you want. Putting the data loading code in componentDidMount will ensure that data is only fetched from the client.
getCurrentUser is an asynchronous method which calls another asynchronous method (setState).
I am pretty sure that you will first see the log entry from componentDidMount and only afterwards the log entry from componentWillMount.
What's happening is:
React calls componentWillMount
You start an async call (getCurrentUser)
componentWillMount returns immediatelly, without waiting for the promise to complete
React calls componentDidMount
Your promise resolves
The logs are due to the asynchronous nature of your method getCurrentUser. When you call getCurrentUser in componentWillMount, it might result in an output after the componentDidMount has finished executing and hence you see the initial state in componentDidMount. However the console.log in componentWillMount is in the getCurrentUser .then promise callback which will log the current value received from getCurrentUser()
RxJS has a nifty function, fromCallback that takes a function whose last parameter is a callback and returns an Observable. And I want to combine that with React's setState function so that I can do something analogous to:
const setState = Rx.Observable.fromCallback(this.setState);
setState({ myState: 'Hi there!' }).concat(....)
so that any operations chained to setState are guaranteed to happen after the state has been set and, most importantly, that setState isn't invoked until there's an active subscriber.
What I noticed though is that even without a subscribe, setState is being called right as it's defined and setting the state of my component. So if I have:
networkSignal.flatMap((x) => {
return setState({ myState: 'test' });
});
the function setState is immediately invoked but the observer it producers won't send a next until there's a subscriber. What I want is for the function to only invoke when there's a subscriber.
Looking into the source you can see that RxJS returns a function that when executed, creates an observable but immediately invokes the function - the callback argument.
fromCallback returns a function which, when executed, returns an observable. That observable is where the asynchronous result(s) of the function call will flow.
To delay the execution of the function, you can make use of .defer. For instance:
const setState = Rx.Observable.fromCallback(this.setState);
const deferred$ = Rx.Observable.defer(function (){return setState({ myState: 'Hi there!' }).concat(....)});
// Later on
deferred$.subscribe(...)
Question whose answers used the same technique were asked here and here