React/ReactJS: Difference between componentWillMount() and componentDidMount()
The comparison between
componentWillMount() and
componentDidMount() methods would have
made much more sense prior to React 16.3, when
componentWillMount() was listed as any
other lifecycle method and their mounting order were as follows:
-
constructor() -
componentWillMount() -
render() -
componentDidMount()
But since v. 16.3, the
componentWillMount() method has been
marked for removal in future versions, but at the same time, an alias
UNSAFE_componentWillMount() was
introduced. Check
changelog.
As per the new revised official React document here, the mounting order has been reinstated as follows:
-
constructor() -
getDerivedStateFromProps() -
render() -
componentDidMount()
The legacy method componentWillMount() is
heading for deprecation and will not be available from v. 17. But for
the time being, it is available under the alias
UNSAFE_componentWillMount().
componentWillMount()
componentWillMount() is mounted before
the render() method and is essentially a
constructor, akin to the
ES6 constructor() method. This is where you set properties and states (using
setState()) and pull
synchronous data. If the application is rendered on the
server (with the likes of
Next.js and such)
it is invoked once on the server side and also on the client side.
But it is not exactly a constructor, as you can
setState() inside
componentWillMount() but not inside a
constructor(). If you try to
setState() inside a
constructor(), it will throw the
following warning message.
Can't call setState on a component that is not yet mounted.
But the setState() inside
componentWillMount() will not rerender
the component (unless it is inside some async methods like
fetch() or
axios()).
Since it is loaded before render(),
there is quite a misconception that
componentWillMount() is the right place
to fetch data to avoid initial empty rendering of states, so that
around the time when the elements render, the data would already be
available. However, this is not the case as
render() is called immediately after
componentWillMount(); it does not wait
for the availability of the data being fetched by the async method
inside it.
We will illustrate this fallacy with an example below. We will fetch
data inside componentWillMount() from an
external source, the free online REST API for fake data:
https://jsonplaceholder.typicode.com/users
class Users extends React.Component{
constructor(props) {
super(props);
this.state = {
users: []
}
}
componentWillMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(users => this.setState({ users }));
}
render() {
return (
<div>
{this.state.users.length == 0?
<p>EMPTY</p>:
<ul>
{
this.state.users.map(usr =>
<li key={usr.id}>{usr.name}</li>
)
}
</ul>}
</div>
);
}
}
export default Users;
If you observe carefully, you will notice the "EMPTY" text flashing
very quickly on first load (meeting the condition
this.state.users.length == 0), before
loading the names of the people listed in the JSON next. That was
indication that the render() method was
called irrespective of the status of the async call inside
componentWillMount(). But this fetched
async data cannot be used in server-side rendering, as it is
necessary to provide synchronous data for it. But providing
synchronous data can be achieved inside a
constructor() also. Besides, the
component's DOM has not yet been materialized to do anything and
nothing much has happened since the load of
constructor(). In short, there is not
much need of this method. That is why, currently, it is advised not
to use componentWillMount() anymore; the
ES6 constructor() method is recommended
to be used in its place for initializing state. And for fetching
remote data, the other lifecycle method
componentDidMount() is recommended.
NOTE: The
componentWillMount() method is now
considered a legacy. It is advised not to use this method in your
code anymore.
componentDidMount()
This method is called just after
render(). And since the
render() function is called before
componentDidMount(), the component has
already been rendered once by the time it invokes
componentDidMount(). Also, unlike the
earlier componentWillMount() method,
this method is not called on the server-side, but only on the
client-side. This is the right place for API calls. Besides API
calls, this is where you initialize third-party DOM libraries and
add event listeners.
The example code in the previous section can now be corrected by
fetching external data inside
componentDidMount() as shown below.
class Users extends React.Component{
constructor(props) {
super(props);
this.state = {
users: []
}
}
componentDidMount() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(users => this.setState({ users }));
}
render() {
return (
<div>
{this.state.users.length == 0?
<p>EMPTY</p>:
<ul>
{
this.state.users.map(usr =>
<li key={usr.id}>{usr.name}</li>
)
}
</ul>}
</div>
);
}
}
export default Users;