Getting Started With the React lifecycle methods

In the previous part of the series, you learned what the local storage is and how you can get started with it. In this part, you’ll learn how to use the storage APIs to store and persist your React to-dos data.

To do that, you’ll need to understand the lifecycle logic. Don’t forget, we will also cover all of these using the modern React Hooks later in the series.

For now, our focus is on the fundamentals.

So get started!

The Lifecycle methods

Every React component you create always goes through a series of phases from its birth to death. You can think of these components going through a cycle of birth, growth and finally death.

At every phase, there are lifecycle methods that React calls to perform some certain functions. Now, React allows us to override these methods. But before we get to that, let’s briefly discuss the component phases.

In React, these phases are mainly three.

  1. Mounting: As the name implies, this is the phase when React component mounts (created and inserted) the DOM. In this phase, the component is birthed.

  2. Updating: Once the component is mounted, it could get updated. Remember that component gets updated when there is/are state or prop changes, hence trigger re-rendering. All of that happens in this phase.

  3. Unmounting: This phase ends the component lifecycle because, in it, the component is removed from the DOM.

In each of these phases, React provides lifecycle methods that we can use to monitor and manipulate what happens within the component.

Though, we have been using one of these lifecycle methods – the render() method. This method is the only required lifecycle method within a React class component. It’s responsible for rendering React elements in the Virtual DOM and it is called during the mounting and updating phase.

React has several optional lifecycle methods, of which some are deprecated or exist for a rare use case.

But the common once in addition to the render() method include:

  • componentDidMount() – This lifecycle method is called immediately after a component is rendered.

  • componentDidUpdate() – This is called immediately after updating occurs.

  • componentWillUnmount() – This is called just before a component is unmounted or destroyed.

Let’s take a deeper look at each of the method.

The componentDidMount() method

As mentioned earlier, it is called immediately after the component is rendered. Like the render() method, it belongs to the mounting phase. Just that the render() is invoked before React updates the actual DOM.

As a beginner, you will most likely be working with the componentDidMount(). This is because you’ll often perform some sort of effects (or side-effects). For instance, making a network request, subscriptions, setting up a timer, setting up event listeners etc are examples of side effects that you can perform in this method.

We’ll discuss them in details when we get to functional Hooks. For now, know that a function is said to have side effects if it modifies anything outside its body. This makes it an impure function.

As you may know, classes are a type function as well.

These effects are not allowed in the render() method. Not only because the render should be kept pure but also it is invoked too early. And you’d want to perform any of these side effects after React has updated the DOM.

So a good place to perform these type of operations is the componentDidMount() method. It ensures that your data execute immediately the component mount the DOM.

We will see later how we can utilize this method and read our todos items from the local storage. For now, let’s see a common use case real quick.

Fetching data from a remote endpoint

In our todos app, we can make all sort of HTTP request (like GET, POST or DELETE data) to and from a remote endpoint. So instead of manually adding the default to-dos items, we can request the data from the server and list them on the frontend.

We can get this data from any backend, but here, we will make use of a free online REST API called JSONPlaceholder. It allows us to mimic a real live server and have a backend to work with. From there, we can get a list of fake todos items into our to-dos app.

To fetch the data, we will make use of the fetch() method from the native Fetch API to perform this request and handles the responses. We can also make use of a library like Axios to do the same.

Let’s quickly jump in.

If you open the JSONPlaceholder website and click on todos link under the Resources section, you will see a list of 200 todos that we will be working with. Take note of the URL, we will make an HTTP request to it.

Back to our code.

Open the TodoContainer component and delete all the hardcoded todos data. The state object now looks like this:

state = {
  todos: [],
}

Save the file and see your app displaying empty default items. Still in the file, add the following code anywhere above the render().

componentDidMount() {
  fetch("https://jsonplaceholder.typicode.com/todos")
    .then(response => response.json())
    .then(data => console.log(data));
}

Save the file and take a look at the Console. You’ll see a list of objects containing the todos data.

Make sure you are connected to the internet.

This is the simplest use of the fetch() method.

The code is simple and straight forward. We already know that when the component gets rendered to the DOM, whatever is placed in the componentDidMount() lifecycle method gets executed. In our case, we are getting a list of to-dos data displayed in the console.

In the lifecycle method, we started by making a request to the specified URL. This then returns a promise containing an HTTP response. The data here is not useful to us. So we resolved the response to JSON format where we then receive the data in the format we can work with.

For brevity, we can limit the number of to-dos to 10 instead of the 200 items we are fetching.

So update the request URL by appending a query string.

componentDidMount() {
  fetch("https://jsonplaceholder.typicode.com/todos?_limit=10")
    ...
}

Please take a look at the Console to see your data.

Now, we can update the state with these data. As expected, we use the setState() method.

Update the code so you have:

componentDidMount() {
  fetch("https://jsonplaceholder.typicode.com/todos?_limit=10")
    .then(response => response.json())
    .then(data => this.setState({ todos: data }));
}

Save the file and reload your app. You should see your todos items.

In the same way, you can use the fetch() method to make a post and delete request to the JSONPlaceholder or whatever backend.

For now, let’s discuss another important lifecycle method. We will revisit the componentDidMount when we start persisting the todos data in the browser storage.

The componentDidUpdate() method

This is another important lifecycle method that allows you to define side effects. As I mentioned earlier, it is called immediately after updating occurs – i.e after state or props changes.

So it is ideal when you need to perform an operation after the DOM is updated. DOM manipulation, data fetching that only occurs when a specific condition is met etc. are operations that can go inside this lifecycle.

Be aware that this method is not called for the initial render.

To use it, you’ll have something like this:

componentDidUpdate(prevProps, prevState) {
  // update logic here
}

The method arguments allow us to compare the prevProps or prevState with their current snapshot inside the componentDidUpdate. This ensures we can control how often the lifecycle runs after the component update. Without these parameters, it may run infinitely if it has a call to state update. This is because the setState causes a new render.

We can control it by writing an extra comparison like so:

componentDidUpdate(prevProps, prevState) {
  if(prevState.todos !== this.state.todos) {
    // logic here
  }
}

The prevProps and prevState are simply the state and props before the update. We are using them to skip applying an effect if certain values haven’t changed between re-renders.

Whether or not you are having a call to update the state, always makes it your duty to have a check in the lifecycle. This saves you the stress associated with the method.

Like the state, you can also compare the prevProps with its current snapshot if the component has access to the prop.

Persisting the todos data to local storage

Now that you know what the componentDidMount and the componentDidUpdate are. We will use them in the process of saving and retrieving data from the browser storage.

The logic here is simple. Whenever our application mounts on the screen and the user interact with the app by inputting the to-dos data, we will save the to-dos item(s) in the local storage. This sounds like the logic should be in the componentDidUpdate lifecycle. Yes, it is.

However, on component mounts (i.e on page reload or on a subsequent visit), we will check if there are to-dos items present in the local storage, then, we grab them. This sounds like the logic should be in the componentDidMount lifecycle.

We will be using the local storage methods to achieve this. Please revisit the last part if you need a refresher.

So let’s start by storing the todos (if exist) in the local storage.

Remember, earlier on this page, we added a componentDidMount method in the TodoContainer component. Let’s delete it. You can leave the todos state array empty as it is.

Now, let’s add the following code anywhere above the render() method.

componentDidUpdate(prevProps, prevState) {
  if(prevState.todos !== this.state.todos) {
    const temp = JSON.stringify(this.state.todos)
    localStorage.setItem("todos", temp)
  }
}

The code should make sense if you read the last part of the series. Anyway, all we are doing in the lifecycle is getting the present todos and storing them in the local storage. Remember, React will run the code once it detects an update.

Save the file and open the browser storage. Once you add todos item to the view, you’ll see it in the local storage. While you lose the item in view after page reloads, you have it stored in the storage.

That is out of the way.

Next, we will get the stored item(s) and add back to the view once component mount.

Still in the file, add the following code anywhere above the render() method.

componentDidMount() {
  const temp = localStorage.getItem("todos")
  const loadedTodos = JSON.parse(temp)
  if (loadedTodos) {
    this.setState({
      todos: loadedTodos
    })
  }
}

Save the file and reload the page.

You should get your saved data once the component mount. Now, any data you add or modify will persist in the local storage and available at any time.

In the code, we simply get the data from the storage and update the state using the setState() method.

Using setState directly in the componentDidMount lifecycle

Calling the setState() in this method would trigger an extra rendering. That is, the render() will be called twice. Though it’s fine because it will happen before the browser updates the view. However, we should always use with caution or simply avoid it if you could to prevent any performance issue.

Since in our case, we want to display the stored item(s) after component render, we can simply assign the item(s) directly as the initial state.

We will take a look at that when we get to the functional Hooks.

The componentWillUnmount() method

Some component in React requires resources to be freed-up once they are destroyed and removed from view. Earlier, we mentioned how the componentDidMount allows us to perform side effects like the network request, setting up the timer or subscription. Now, once the component (performing these effects) is being removed from the DOM, we would like to do a clean-up like cancelling the network request, invalidating the timers, removing event listeners or cleaning up any subscription. This is necessary to prevent a memory leak.

The lifecycle method that React calls in this phase (i.e when a component is about to be destroyed or removed from the DOM) is the componentWillUnmount() method.

To show you how it works, we will be logging a message to the console whenever any of the todos items are being removed from the DOM.

So open the TodoItem.js file and add and add this code anywhere above the render() method:

componentWillUnmount() {
  console.log("Cleaning up...")
}

Save the file.

Now, React calls and execute this method whenever the delete button is clicked.

Open the Console of your browser and try to delete any of the todos items. You should see the log message.

This is just a sample. In your case, you may have some cleanups to do. This lifecycle method is a perfect place for that.

So far, on this page, you have learned what React lifecycle methods are and how you can apply them in your application. You’ve also learned how to utilize them in the process of persisting data in the local storage.

At this point, we have covered a lot using the class component. Onward, we will get started with the functional React Hooks. You will learn how to manage all the stateful logic that we’ve learned so far only with function component.

This way, you will have the flexibility to write a fully functional React code without using the class syntax.

Next part: Learning the React Hooks

continue