Why API call is recommended in componentDidMount()
It’s good to have basic knowledge of Event Loop, Component lifecycle methods and Virtual DOM for better understanding of this article.
For an end user the expected behavior is the site load with proper data in first go. But in case of API call it takes some time to fetch the data from server. In this scenario next expected behavior is a loader should appear till the data received. When data received, loader should disappear and page should appear with proper data.
Now in react Component mount lifecycle methods are:
- constructor()
- componentWillMount()/UNSAFE_componentWillMount() // obsolete
- getDerivedStateFromProps()
- render()
- componentDidMount()
Here we’ll consider only API call scenario.
So the ideal behavior is:
- First component should load with loader.
- Then component should make api call.
- On receiving response, it should re-render.
Which gives one good reason to make api call after render() i.e. componentDidMount().
This was more likely philosophical explanation. Let’s dig in deep and understand technical perspective of it..
React Component lifecycle methods are async. This means next method will execute immediately after previous without waiting for first to get completed.
Normally we use react and redux together for state maintenance. An action is fired for API call, state is updated in reducer, which triggers getDerivedStateFromProps() (componentWillReceiveProps() is outdated) where we update the state and component re-renders.This is irrespective of life cycle method where the API call is made.
But calling API in constructor() or componentWillMount() has its own complications.
In constructor say we have response(res) set the state as:
this.state={
prop1: res.props1,
prop2: res.props2
}
As soon soon as javascript will see api call, it will run the event loop and will cause “res” undefined so there will be an error “Uncaught TypeError: Cannot read property ‘props1’ of undefined”.
To handle that we must have to use.
this.state={
prop1: res.props1 || ‘default value’,
prop2: res.props2 || ‘default value’,
}
Or some library like lodash.
No matter how fast your server is, response will be undefined by the time constructor sets state, because as soon as JavaScript will see API call, it will have to go through event loop then component rendering and re-rendering will happen. So by calling API in constructor() is going to increase code complexity.
In componentWillMount()…
The best reason why you should not make call API in componentWillMount() is that, React team is going to retire the 3 componentWill… lifecycle methods (cWM, cWRP and cWU) from react 17 onward and have prefixed them with UNSAFE_ from react 16.3 onwards as these are often misused and not in line with the upcoming feature of async rendering. These cause unnecessary multiple time component re-rendering which hampers the performance.
In server side rendering componentWillMount() will do re-rendering which is not required. This is a very common known fact. But at the client side, at some point rendering of component takes more time than getting response from server and at this point ajax callback will trigger re-render on unmounted component, creating a problem. componentWillMount() was good for setting feature flags or default value of state before constructor(). React’s official documents suggest API call as below.
class ExampleComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
externalData: null,
};
}
componentDidMount() {
this._asyncApiRequest = asyncLoadData().then(
externalData => {
this._asyncRequest = null;
this.setState({externalData});
}
);
}
componentWillUnmount() {
if (this._asyncApiRequest) {
this._asyncApiRequest.cancel();
}
}
render() {
if (this.state.externalData === null) {
// Render loading state …
} else {
// Render real UI …
}
}
}
Summary:
Calling API in constructor() or componentWillMount() is not a syntax error but increases code complexity and hampers performance. So, to avoid unnecessary re-rendering and code complexity, it’s better to call API after render(), i.e componentDidMount().