The Update Lifecycle
The update loop segment of the lifecycle is something that lives for the entire lifetime of the component (where the lifetime of a component is if it needs to be displayed in the DOM). This loop repeats every time that the component needs to be updated, which is basically whenever any data that affects what needs to be loaded and displayed as the DOM changes which will then start a lifecycle method specifically to determine what changes need to be displayed. For example, if you modify the shouldComponentUpdate() lifecycle function to just always return false, then as far as React is concerned, no changes should ever retrigger the update loop. Similarly, if you always return true, like we did in our first exercise, then React will assume every state or props change needs to retrigger re-rendering.
render()
We have already discussed the render function, but it is worth mentioning that this is when the render function gets called each time the component updates. So, after the initial render that happens in the mount lifecycle, this gets called in the update lifecycle.
componentDidUpdate()
The componentDidUpdate() lifecycle method, despite having a similar function signature to getSnapshotBeforeUpdate, is used a little more frequently. The function definition is as follows:
componentDidUpdate(prevProps, prevState, snapshot)
It allows you to react to a component updating from changes in props and/or state, which can be helpful if you need to update some state management code or to make calls to remote services, for example.
Also, there are a few arguments here to deal with. The prevProps parameter is the shape of the component's props before the update happened and prevState is the shape of the component's state before the component updated. The snapshot argument is a bit trickier; picture it as a virtual representation of the component in the update. Also, the snapshot argument is only needed if your component also implements the getSnapshotBeforeUpdate() function, which is incredibly rare. If you are not using that, you can just leave the snapshot argument out completely.
One word of caution, however, is that if you are doing anything that would then update props or state, you need to wrap those calls in some sort of conditional. If you don't, you'd update the props or state, which would trigger the update lifecycle, leading to this function, where you'd update the props or state, which would trigger...well, you get the picture.
In the next exercise, we will perform an exercise that will simulate this example in greater detail.
Exercise 4.03: Simulating a Slow AJAX Request and Prerendering Your Component
As previously mentioned, in this exercise, we will simulate a long loading AJAX call and help demonstrate the importance of using the right lifecycle method to ensure the best possible UI experience for your app.
- Start off by creating a new React project, which we will call profile, start the project, and take a look at the browser window that should have opened for you automatically:
$ npx create-react-app profile
$ cd profile
$ yarn start
- Clean out the contents of the App component and replace it with a class component instead. Since we're using a class component here, we will need to change the import statement at the top to also import Component from React:
import React, { Component } from 'react';
import "./App.css";
class App extends Component {
render() {
return (
<p className="App">User Profile</p>
);
}
}
export default App;
- Create four lifecycle methods in your App component: constructor, componentDidUpdate, componentDidMount, and render (which we already have):
class App extends Component {
constructor(props) {
super(props);
}
componentDidUpdate() {
}
componentDidMount() {
}
render() {
return (<p className="App">User Profile</p>);
}
}
Note
You may receive a useless constructor warning message if you refresh the browser at this point. That is fine and expected; we'll add to the constructor soon.
- Set up an initial state of a dummy list of messages in the constructor that you built in the previous step:
// State will be messages: ["Hello World", "How are you"]
this.state = { messages: [] };
- Build a renderProfile() function:
renderProfile() {
if (this.state.messages && this.state.messages.length > 0) {
return (
<ul>
{this.state.messages.map((msg, index) => <li key={`msg-${index}`}>{msg}</li>)}
</ul>
);
} else {
return (<p>No messages!</p>);
}
}
- Next, we will fill in our componentDidUpdate lifecycle method: componentDidUpdate(prevProps, prevState). Remember, we are not implementing getSnapshotBeforeUpdate so we do not need to include the snapshot argument here. We will add two console.log statements, one for each of the passed-in arguments:
componentDidUpdate(prevProps, prevState) {
console.log('prevProps:', prevProps);
console.log('prevState:', prevState);
}
- In componentDidMount, simulate a long-loading profile:
componentDidMount() {
setTimeout(() => this.setState({ messages: ["Hello World", "How are you?"] }),
10000 // 10 seconds
);
}
When the page reloads, you should see No messages until the timeout completes, which is a bad user experience and makes the user distrust the information that this component displays to them. Let's update this to instead follow best practices and display a "loading" state instead.
- Go back to the state and add a new flag for a loading state:
this.state = { messages: [], loading: true };
- In renderProfile, change the render to also include a check for whether we are loading:
renderProfile() {
if (this.state.loading) {
return (<p>Loading...</p>);
}
if (this.state.messages && this.state.messages.length > 0) {
return (
<p>
<ul>
{this.state.messages.map((msg, index) => <li key={`msg- ${index}`}>{msg}</li>)}
</ul>
</p>
);
} else {
return (<p>No messages!</p>);
}
}
- Now add a loading state update to the componentDidMount function to update our new loading flag:
componentDidMount() {
setTimeout(() => this.setState({ messages: ["Hello World", "How are you?"], loading: false }),
10000 // 10 seconds
);
}
- Finally, go back to the render() method and add a call to renderProfile:
render() {
return (
<p className="App">
User Profile
<hr />
{this.renderProfile()}
</p>
);
}
Now when the page loads, we should see the loading message until the browser returns data. If you were to change the function inside of the setTimeout to instead return an empty list (if the user had no messages), it would instead display the no messages text to the user after loading, which means the user can trust the state that the component is returning to the user!
The final UI should look like this:
Figure 4.9: App component
With that, we have built a good simulation of something you, as a React developer, will absolutely have to account for at some point in your career: dealing with a slow-loading component. It's most likely will be due to some AJAX call, but regardless of the cause you are now well equipped to be able to handle it and know how to properly structure your code to prevent scenarios like that from affecting your overall page load performance.