Used mostly for data fetching and other initialization stuff componentDidMount is a nice place for async/await in React.
The componentDidMount() method allows us to execute the React code when the component is already placed in the DOM (Document Object Model). This method is called during the Mounting phase of the React Life-cycle i.e after the component is rendered.
Yes, it is safe to use setState() inside componentWillMount() method.
These methods are considered “unsafe” because the React team expect code that depends on these methods to be more likely to have bugs in future versions of React. Depending on the objective of the code, you can remove the use of componentWillMount entirely with other lifecycle methods.
Let's start by pointing out the differences and determining how it could cause troubles.
Here is the code of async and "sync" componentDidMount()
life-cycle method:
// This is typescript code
componentDidMount(): void { /* do something */ }
async componentDidMount(): Promise<void> {
/* do something */
/* You can use "await" here */
}
By looking at the code, I can point out the following differences:
async
keywords: In typescript, this is merely a code marker. It does 2 things:
Promise<void>
instead of void
. If you explicitly specify the return type to be non-promise (ex: void), typescript will spit an error at you.await
keywords inside the method.void
to Promise<void>
async someMethod(): Promise<void> { await componentDidMount(); }
You can now use await
keyword inside the method and temporarily pause its execution. Like this:
async componentDidMount(): Promise<void> {
const users = await axios.get<string>("http://localhost:9001/users");
const questions = await axios.get<string>("http://localhost:9001/questions");
// Sleep for 10 seconds
await new Promise(resolve => { setTimeout(resolve, 10000); });
// This line of code will be executed after 10+ seconds
this.setState({users, questions});
return Promise.resolve();
}
Now, how could they cause troubles?
async
keyword is absolutely harmless.I cannot imagine any situation in which you need to make a call to the componentDidMount()
method so the return type Promise<void>
is harmless too.
Calling to a method having return type of Promise<void>
without await
keyword will make no difference from calling one having return type of void
.
Since there is no life-cycle methods after componentDidMount()
delaying its execution seems pretty safe. But there is a gotcha.
Let's say, the above this.setState({users, questions});
would be executed after 10 seconds. In the middle of the delaying time, another ...
this.setState({users: newerUsers, questions: newerQuestions});
... were successfully executed and the DOM were updated. The result were visible to users. The clock continued ticking and 10 seconds elapsed. The delayed this.setState(...)
would then execute and the DOM would be updated again, that time with old users and old questions. The result would also be visible to users.
=> It is pretty safe (I'm not sure about 100%) to use async
with componentDidMount()
method. I'm a big fan of it and so far I haven't encountered any issues which give me too much headache.
Update April 2020: The issue seems to be fixed in latest React 16.13.1, see this sandbox example. Thanks to @abernier for pointing this out.
I have made some research, and I have found one important difference: React does not process errors from async lifecycle methods.
So, if you write something like this:
componentDidMount()
{
throw new Error('I crashed!');
}
then your error will be caught by the error boundry, and you can process it and display a graceful message.
If we change the code like this:
async componentDidMount()
{
throw new Error('I crashed!');
}
which is equivalent to this:
componentDidMount()
{
return Promise.reject(new Error('I crashed!'));
}
then your error will be silently swallowed. Shame on you, React...
So, how do we process errors than? The only way seems to be explicit catch like this:
async componentDidMount()
{
try
{
await myAsyncFunction();
}
catch(error)
{
//...
}
}
or like this:
componentDidMount()
{
myAsyncFunction()
.catch(()=>
{
//...
});
}
If we still want our error to reach the error boundary, I can think about the following trick:
render
methodExample:
class BuggyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { error: null };
}
buggyAsyncfunction(){ return Promise.reject(new Error('I crashed async!'));}
async componentDidMount() {
try
{
await this.buggyAsyncfunction();
}
catch(error)
{
this.setState({error: error});
}
}
render() {
if(this.state.error)
throw this.state.error;
return <h1>I am OK</h1>;
}
}
Your code is fine and very readable to me. See this Dale Jefferson's article where he shows an async componentDidMount
example and looks really good as well.
But some people would say that a person reading the code may assume that React does something with the returned promise.
So the interpretation of this code and if it is a good practice or not is very personal.
If you want another solution, you could use promises. For example:
componentDidMount() {
fetch(this.getAuth())
.then(auth => {
if (auth) this.checkAuth(auth)
})
}
When you use componentDidMount
without async
keyword, the doc say this:
You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen.
If you use async componentDidMount
you will loose this ability: another render will happen AFTER the browser update the screen. But imo, if you are thinking about using async, such as fetching data, you can not avoid the browser will update the screen twice. In another world, it is not possible to PAUSE componentDidMount before browser update the screen
I think it's fine as long as you know what you're doing. But it can be confusing because async componentDidMount()
can still be running after componentWillUnmount
has run and the component has unmounted.
You may also want to start both synchronous and asynchronous tasks inside componentDidMount
. If componentDidMount
was async, you would have to put all the synchronous code before the first await
. It might not be obvious to someone that the code before the first await
runs synchronously. In this case, I would probably keep componentDidMount
synchronous but have it call sync and async methods.
Whether you choose async componentDidMount()
vs sync componentDidMount()
calling async
methods, you have to make sure you clean up any listeners or async methods that may still be running when the component unmounts.
Update:
(My build: React 16, Webpack 4, Babel 7):
When using Babel 7 you'll discover:
Using this pattern...
async componentDidMount() {
try {
const res = await fetch(config.discover.url);
const data = await res.json();
console.log(data);
} catch(e) {
console.error(e);
}
}
you will run into the following error...
Uncaught ReferenceError: regeneratorRuntime is not defined
In this case you will need to install babel-plugin-transform-runtime
https://babeljs.io/docs/en/babel-plugin-transform-runtime.html
If for some reason you do not wish to install the above package (babel-plugin-transform-runtime) then you will want to stick to the Promise pattern...
componentDidMount() {
fetch(config.discover.url)
.then(res => res.json())
.then(data => {
console.log(data);
})
.catch(err => console.error(err));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With