Asynchronous call in componentWillMount finishes after render method - javascript

I am trying to perform an asynchronous call to an API in the componentWillMount method. Indeed I would like the render method to executed after the componentWillMount method as I need to pass props to the component in my render method.
Here is my code :
class TennisSearchResultsContainer extends React.Component {
componentWillMount () {
// TODO: Build markers for the map
// TODO: Check courtsResults object and database for tennis court
this.courtsMarkers = this.props.courtsResults.map((court) => {
return new google.maps.Marker({
position: new google.maps.LatLng(JSON.parse(court.LOC).coordinates[1], JSON.parse(court.LOC).coordinates[0]),
title: court.NAME,
animation: google.maps.Animation.DROP
});
});
}
render () {
return <TennisSearchResults criterias={this.props.criterias} courtsMarkers={this.courtsMarkers} />;
}
}
I don't understand then why my render method seems to do not wait for the asynchronous call to finish and pass undefined props to my child component...
Am I right? And what should I do to fix that? What is the way to handle this?

You might need to understand javascript async behavior better. Async means "don't wait". That the task will happen in the background and other code will continue to execute. A good way to manage this is to set state on your component. For example, when you enter componentDidMount set a loading state to true. Then when your async function completes, set that state to false. In your render function you can then either display a "loading..." message or the data.
Here is some code that shows a simplified example of fetching data async and how you could handle that in React. Open the developer tools in your browser and look at the console output to understand the React lifecycle better.
EDIT: Code has been updated to use the new React Lifecycle recommendations as of April 2018. In summary, I replaced componentWillMount with the safer componentDidMount.
It might seem inefficient to update the state after the component has already mounted, as 'componentDIDmount' correctly implies. However, per the official React documentation on componentDidMount:
"If you need to load data from a remote endpoint, this is a good place to instantiate the network request."
"Calling setState() in this method will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state."
Here's the complete example code:
class MyComponent extends React.Component {
constructor(props) {
super();
console.log('This happens 1st.');
this.state = {
loading: 'initial',
data: ''
};
}
loadData() {
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('This happens 6th (after 3 seconds).');
resolve('This is my data.');
}, 3000);
});
console.log('This happens 4th.');
return promise;
}
componentDidMount() {
console.log('This happens 3rd.');
this.setState({ loading: 'true' });
this.loadData()
.then((data) => {
console.log('This happens 7th.');
this.setState({
data: data,
loading: 'false'
});
});
}
render() {
if (this.state.loading === 'initial') {
console.log('This happens 2nd - after the class is constructed. You will not see this element because React is still computing changes to the DOM.');
return <h2>Intializing...</h2>;
}
if (this.state.loading === 'true') {
console.log('This happens 5th - when waiting for data.');
return <h2>Loading...</h2>;
}
console.log('This happens 8th - after I get data.');
return (
<div>
<p>Got some data!</p>
<p>{this.state.data}</p>
</div>
);
}
}
ReactDOM.render(
<MyComponent />,
document.getElementsByClassName('root')[0]
);
And here is the working example on CodePen.
Finally, I think this image of the modern React lifecycle created by React maintainer Dan Abramov is helpful in visualizing what happens and when.
NOTE that as of of React 16.4, this lifecycle diagram has a small inaccuracy: getDerivedStateFromProps is now also called after setState as well as forceUpdate. See this article from the official React blog about the Bugfix for getDerivedStateFromProps
This interactive version of the React lifecycle diagram created by Wojciech Maj allows you to select React version >16.04 with the latest behavior (still accurate as of React 16.8.6, March 27, 2019). Make sure you check the "Show less common lifecycles" option.

The above answer is probably overkill for what you are trying to do. All you need to do is make compoentDidMount an async function. Then you can use the await keyword on a function call that returns a promise.
class MyComponent extends React.Component {
async componentWillMount () {
await myAsyncCall();
}
render () {
}
}

Related

Why my function in componentDidMount is being called rapidly?

Im making a function in react that get some information from Youtube API which I want it to get called just once when I refresh the page and put that information in a state. But when I use it in componentDidMount, it wont save the information and my state is still empty. here is the code:
constructor(props) {
super(props);
this.state = { vid: [] };
this.vidSearch = this.vidSearch.bind(this);
}
vidSearch = async () => {
const youtubeVid = await Youtube.get("/search");
this.setState({ vid: youtubeVid.data.items });
};
componentDidMount() {
this.vidSearch();
console.log(this.state.vid);
}```
setState may be asynchronous, so you won't be able to immediately check the state. But it has an optional callback which you can use that is called after the process has completed.
vidSearch = () => {
const youtubeVid = await Youtube.get("/search");
this.setState({ vid: youtubeVid.data.items }, () => {
console.log(this.state.vid);
});
};
componentDidMount() {
this.vidSearch();
}
According to the official documentation:
You may call setState() immediately in componentDidMount(). It will
trigger an extra rendering, but it will happen before the browser
updates the screen. This guarantees that even though the render() will
be called twice in this case, the user won’t see the intermediate
state. Use this pattern with caution because it often causes
performance issues. In most cases, you should be able to assign the
initial state in the constructor() instead. It can, however, be
necessary for cases like modals and tooltips when you need to measure
a DOM node before rendering something that depends on its size or
position.
In this case I would change my code to this:
constructor(props) {
this.state = {
vid: Youtube.get('/search').data.items
}
}
vidSearch is async and you are not awaiting the returned promise in componentDidMount before calling console.log.
All async functions wrap the return value in a promise, even implicit returns.
You can try this.vidSearch().then(() => console.log(this.state)).

React JS : Get data from api and send to child component

I'm a little bit lost. I want to get a response from my API and send the data to a child component. It seems that I don't understand how to do that because it says to me : "TypeError: Cannot read property 'setState' of undefined"
Can you explain to me how can I do it ?
Here is a snippet of my code :
function GetData(url, params, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url + '?' + params, true);
xhr.onreadystatechange = function(e) {
if (this.readyState === xhr.DONE) {
if (this.status === 200) {
callback(JSON.parse(xhr.responseText));
} else {
console.log('error');
}
}
}
xhr.send(null);
}
class Utilisateurs extends Component {
constructor(props){
super(props);
this.state = {users:''}
GetData('http://localhost:3333/shop/users', 'login=true',
function(res) {
this.setState({users: res});
}
)
}
render() {
return (
<div id="center">
<UserListSorted data={this.state.users}></UserListSorted>
</div>
);
}
}
So I make a GET request and I want to change the state of 'users' with the response so I can print a list of users. But I don't know how to fix this.
Thank you for your help!
There is a couple things going on here.
First, you shouldn't make network requests in the constructor or componentWillMount(). Someone will probably call it an anti-pattern.
You shouldn't ever do any long running tasks from the constructor because it will make the component take longer to load. It is a good general rule. Just use it to set variables and properties and trigger other class methods, quick setup. I can't remember exactly, but it's probably an issue with synchronous vs. asynchronous behaviour. The constructor is a method. It is a function, so if you hang the thread waiting for network request, the component has to wait before it can continue.
The same is true for componentWillMount(). That lifecycle method kicks off before the component is mounted in the DOM, which means if you make a network request and it completes before the component is mounted, there is nothing to setState on. There is no component in the DOM yet to setState. This is what someone told me before and it stuck with me. I am just telling you now to hopefully give you that same moment of realization.
You could do it from componentDidMount() because the component is ready, but it will trigger another re-render if you call this.setState() from inside that lifecycle method. Some call "setState inside componentDidMount" an anti-pattern, but its not really that bad. I would say use it, but be aware that it does trigger a re-render. The render method will run at least twice. If you had a big, complex component or lots of them, it could cause performance issue. That's why people say "don't do it".
Moving forward, I would recommend creating a class method called getData() or something and call that from componentDidMount(). Something like this:
class Something extends Component {
constructor(props) {
super(props)
this.state = {
data: '',
}
}
componentDidMount() {
this.getData()
}
getData() {
// get data from server
const theData = 'we got some data'
this.setState({
data: theData,
})
}
render() {
return <div>{this.state.data}</div>
}
}
I actually can't exactly help you with the XHR request because its been al ong time since I used it. I can't verify if your syntax is correct. I would recommend using a library called request https://www.npmjs.com/package/request. I imagine the XHR request will work fine, but check out request as a learning exercise.
The this context in this.setState isn't what you think it is. You can reassign this to a new variable outside of the closure, but it's a lot easier if you use an arrow function (which has no this and borrows the context from the outer scope) instead:
GetData('http://localhost:3333/shop/users', 'login=true', (res) => {
this.setState({ users: res });
});
Aside: while you can perform fetch operations in the constructor the React team recommend that you use componentDidMount instead. There's more information in this article.
Oh, you you might think about using fetch instead of writing all that XMLHTTPRequest code.
Problem is with the context of this, inside GetData function it's undefined, so try using arrow function for your callback () => {} which automatically binds this context from Utilisateurs class.
GetData(
'http://localhost:3333/shop/users',
'login=true',
res => {
this.setState({users: res});
}
)
You should do your api calls in componentDidMount (in addition to use an arrow function as the callback of GetData()) :
class Utilisateurs extends Component {
constructor(props){
super(props);
this.state = {users:''}
}
componentDidMount() {
GetData('http://localhost:3333/shop/users', 'login=true',
(res) => {
this.setState({users: res});
}
)
}
render() {
return (
<div id="center">
<UserListSorted data={this.state.users}></UserListSorted>
</div>
);
}
}
Edit : Actually componentDidMount is recommended in React Docs, they don't explains why unfortunately, but some articles about best practices in React tells that fetching before the first render can lead in issues some cases.

React componentWillReceiveProps check not working if API responds too quick

I have a React container component which on page load dispatches an action (using the componentDidMount lifecycle method). The action calls an API, a successful API call results in the data being added to my Redux store via a reducer.
I have some other API's which require data from the response of the initial API call. I have these in the componentWillReceiveProps lifecycle method and I'm comparing this.props against nextProps to work out whether I have the data ready to call the API's.
90% of the time this code works and the API calls inside of componentWillReceiveProps are successfully called, but the other 10% of the API's don't get called because the code inside of the if statement which compares this.props to nextProps doesn't execute.
From testing I think I've found the issue... If the initial API call returns fast enough, the very first time componentWillReceiveProps is called this.props is already populated with data from the API call rather than returning my initial state, at which point the if statement doesn't execute.
What is the best way of bulletproofing this?
<Route
path={'Content/:id'}
component={DrillPage}
/>
class DrillPage extends Component {
componentDidMount() {
this.props.actions.getLoggedInUser();
}
componentWillReceiveProps(nextProps) {
console.log(this.props);
console.log(nextProps);
// Don't fire if user isn't logged in
if (this.props.loggedInUser.id !== nextProps.loggedInUser.id) {
this.props.actions.isFavourite(this.props.content.id, this.props.content.type);
this.props.actions.doIFollow([this.props.content.author.id]);
}
}
}
function mapStateToProps(state, ownProps) {
return {
contentId: ownProps.params.id,
loggedInUser: state.loggedInUser,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(Object.assign({}, contentActions, userActions), dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(DrillPage);
Maybe you can try a solution without using the React lifecycle and handle your logic in your sagas since, as I understand you're using componentWillReceiveProps to fire other API calls only if you have a userId
You could make a custom middleware for Redux that calls the API on an action dispatch from your component. Then it would directly modify the state and have no dependence on the component other than the initial dispatch.

Proper way to wait for async data to store in state in React or React Native

I need to get data from my SQLite database and then save it in my component's state. The database part is working fine, the problem is that it doesn't get saved to state quite quickly enough. If I add some artificial delay with setTimeout then it works fine. How could I better tie these things together so that it all works with the correct order and timing without a million callbacks?
This doesn't work:
let returnedData;
// This function works, the data comes back correctly eventually
returnedData = getDataFromDB();
this.setState({
dbData: returnedData //but returnedData is still empty at this point
})
// Data is not back in time to see in the variable or in state:
console.log(returnedData); // Undefined
console.log(this.state.dbData); // Undefined
But this does work:
let returnedData;
returnedData = getDataFromDB();
// If I add this tiny delay, it all works
setTimeout(function(){
this.setState({
dbData: returnedData
})
console.log(returnedData); // Shows correct data
console.log(this.state.dbData); // Shows correct data
},100);
I would like to try to find a way for this to work without the artificial delay. I will need to do about 3 of these database queries in componentWillMount as my component is loading and will need to know that I have the data back before I render the component.
Thanks!
Use the componentDidMount lifecycle hook to obtain async data when a component is initialized. This should be done in the component that is using the asynchronously obtained data, or the closest common ancestor for data that is used by multiple components. The reason for this is to reduce the amount of re-rendered components once the async retrieval has completed.
Keep in mind you will also need to account for a loading state, before your async data is available.
Below is a basic example of the principles.
class ComponentThatRequiresAsyncData extends PureComponent {
constructor( props ) {
super( props );
// initialize state
this.state = {
data: null
}
}
// handle async operation in this lifecycle method to ensure
// component has mounted properly
componentDidMount() {
axios.get( "some_url" )
.then( ( response ) => {
// once you have your data use setState to udpate state
this.setState( ( prevState, props ) => {
return { data: response.data };
})
})
.catch( ( error ) => {
// handle errors
});
}
render() {
const { data } = this.state;
return (
{
data ?
<WhatEverIWantToDoWithData data={ data } /> :
<p>Loading...</p>
}
);
}
}
Use the same idea for data that needs to be loaded because of an event, such as a button click. Instead of using componentDidMount you would make your own method, make your async call and update state via setState in the then method of your http call. Just make sure you bind the this context of your method in your constructor if you're using it as an event handler.
Hope this helps. If you have any questions please ask!

constructor vs componentWillMount; what a componentWillMount can do that a constructor cannot?

As far as I could see, the only thing a componentWillMount can do and a constructor cannot is to call setState.
componentWillMount() {
setState({ isLoaded: false });
}
Since we have not called render yet, a setState in componentWillMount will prepare the state object before we enter the first render() pass. Which is essentially the same thing a constructor does:
constructor(props) {
super(props);
this.state = { isLoaded: false };
}
But I see another use case where componentWillMount is useful (on server side).
Let's consider something asynchronous:
componentWillMount() {
myAsyncMethod(params, (result) => {
this.setState({ data: result });
})
}
Here we cannot use the constructor as assignment to this.state won't trigger render().
What about setState in componentWillMount? According to React docs:
componentWillMount() is invoked immediately before mounting occurs. It
is called before render(), therefore setting state in this method will
not trigger a re-rendering. Avoid introducing any side-effects or
subscriptions in this method.
So, here I think React will use the new state value for the first render and avoids a re-render.
Question 1: Does this means, inside componentWillMount, if we call setState in an async method's callback (can be a promise callback), React blocks initial rendering until the callback is executed?
Having this setup on client-side (yes I see that use case in server-side rendering), if I assume the above is true, I will not see anything until my asynchronous method completes.
Am I missing any concepts?
Question 2: Are the any other use cases that I can achieve with componentWillMount only, but not using the constructor and componentDidMount?
Does this means, inside componentWillMount, if we call setState in an
async method's callback (can be a promise callback), React blocks
initial rendering until the callback is executed?
No, see here.
The following code doesn't block render (bear in mind this would be an anti pattern anyways to call setState there)
componentWillMount: function() {
new Promise((resolve, reject) => {
setTimeout(()=> {
resolve();
}, 2000)
}).then(() => this.setState({ promiseResult: 'World' }));
},
Question 2: Are the any other use cases that I can achieve with
componentWillMount only, but not using the constructor and
componentDidMount?
No, for ES6 classes you can discard componentWillMount. It is only needed if you use React.createClass({... })
EDIT: Apparently, I'm wrong. Thanks to #Swapnil for pointing this out. Here is the discussion.
React throws a warning if there is a side effect in the constructor which modifies state in another component, because it assumes that setState in the constructor itself and potentially during render() is being called. So no side effects in the constructor are desired.
This is not the case if you do it in componentWillMount, no errors are thrown. On the other hand, the guys from facebook discourage side effects in componentWillMount also. So if you don't have any side effects, you could use the constructor instead of componentWillMount. For side effects it is recommended to use componentDidMount instead of componentWillMount.
Either way, you don't need componentWillMount.

Categories