React Native Component Lifecycle

Photo of Maks Kolodiy

Maks Kolodiy

Updated Aug 3, 2023 • 20 min read
bgImg

As an introduction, I would like to show you what exactly I will cover in this article, how lifecycles are divided into categories, and what each category is responsible for.

What are the React lifecycle methods?

All React class components have their own phases. When an instance of a component is being created and inserted into the DOM, it gets properties, orprops, and from now on they can be accessed using this.props. Then the whole lifecycle ‘thing’ begins.

Note: A React component may NOT go through all of the phases. The component could get mounted and unmounted the next minute — without any updates or error handling.

A component’s lifecycle can be divided into 4 parts:

  • Mounting —  an instance of a component is being created and inserted into the DOM.
  • Updating — when the React component is born in the browser and grows by receiving new updates.
  • Unmounting — the component is not needed and gets unmounted.
  • Error handling — called when there is an error during rendering, in a lifecycle method, or in the constructor of any child component.
lifecycle

Lifecycle Phases

As I have introduced the basic idea of each phase, let’s take a closer look at their methods.

Mounting

These methods are called in the following order when an instance of a component is being created and inserted into the DOM:

Updating

An update can be caused by changes to props or state. These methods are called in the following order when a component is re-rendered:

Unmounting

This method is called when a component is removed from the DOM:

Error Handling

These methods are called when there is an error during rendering, in a lifecycle method, or in the constructor of any child component.

Now that we know what are the lifecycle phases and their methods, we can get down to the next part — a detailed description of each method.

React.Component Lifecycle

Detailed Lifecycle


Mounting

constructor()

First of all, the constructor method is called before mounting to the DOM and rendering.

Usually, you’d initialize state and bind event handler methods within the constructor method. This is the first part of the lifecycle and is only called when it is explicitly declared, so there is no need to declare it in every component you create. If you need to make any side-effects or subscriptions in this method, you should use componentDidMount(). I will introduce it later on as we are going through each method the in order they are invoked.


constructor(props){
    super(props);
    this.state = {
      message: 'hello world',
    } // this is our initial data
  }

static getDerivedStateFromProps()

Instead of calling setState, getDerivedStateFromProps simply returns an object containing the updated state. Notice that the function has no side-effects - this is intentional. getDerivedStateFromProps may be called multiple times for a single update, so it’s important to avoid any side-effects. Instead, you should use componentDidUpdate, which executes only once after the component updates.

You can either return an object to update the state of the component or return null to make no updates:


// ...

static getDerivedStateFromProps(props, state) {
  return {
     message: 'updated msg',
  }
}

// or you can return null to make no update

static getDerivedStateFromProps(props, state) {
  return null
}

// ...

You are probably wondering why exactly is this lifecycle method important. It’s true that it is one of the rarely used lifecycle methods, but it comes in handy in certain scenarios.

Just because you can update state doesn’t mean you should go ahead and do this. There are specific use cases for the static getDerivedStateFromProps method, or you’ll be solving your problem with the wrong tool.

So when should you use the static getDerivedStateFromProps lifecycle method?

The method name getDerivedStateFromProps comprises five different words, “Get Derived State From Props”.

Essentially, this method allows a component to update its internal state in response to a change in props. The component state reached in this manner is referred to as a derived state.

As a rule, the derived state should be used with an understanding of what you are doing, as you can introduce subtle bugs into your application if you’re driving blind.

render()

Next, after the static getDerivedStateFromProps method is called, the next lifecycle method in line is the render method. The render method must return a React Native component (JSX element) to render (or null, to render nothing). In this step, the UI component is rendered.

An important thing to note about the render method is that the render function should be pure, so do not attempt to use setState or interact with the external APIs.


render(){
  return (
    <View>
      <Text>{this.state.message}</Text>
    </View>
  )
}

componentDidMount()

Just after render() is finished, the component is mounted to the DOM and another method is called — componentDidMount(). When this method is called, we know for sure that we have a UI element which is rendered. This is a good place to do a fetch request to the server or set up subscriptions (such as timers). Remember, updating the state will invoke the render() method again, without noticing the previous state. Until this method is called we are operating on the virtual DOM.


// good place to call setState here

componentDidMount(){
  this.setState({
    message: 'i got changed',
  });
}

---

// or to make request to the server

componentDidMount() {
    fetch('https://api.mydomain.com')
      .then(response => response.json())
      .then(data => this.setState({ message: data.message }));
  }

Second use case: componentDidMount() is a great place to make a fetch() to a server and to call our setState() method to change the state of our application and render() the updated data.

For example, if we are going to fetch any data from an API, then the API call should be placed in this lifecycle method, and then we get the response and we can call the setState() method to render the element with updated data.


import React, {Component} from 'react';
import { View, Text } from 'react-native';

class App extends Component {
  
  constructor(props){
    super(props);
    this.state = {
      message: 'hello world', 
    }
  }
  

  componentDidMount() {
    fetch('https://api.mydomain.com')
      .then(response => response.json())
      .then(data => this.setState({ message: data.message })); // data.message = 'updated message'
  }
  
  render(){
    return(
      <View>
        {/* 'updated message' will be rendered as soon as fetch return data */}
        <Text>{this.state.message}</Text>
      </View>
    )
  }
}

export default App;

In the above example, I have a fake API call to fetch the data. So, after the component is rendered correctly, the componentDidMount() function is called and we request data from the server. As soon as data is received, we re-render component with new data.

With this, we come to the end of the Mounting phase.

When our component gets new props, changes its state, or its parent component is updated, the updating part of the life-cycle begins. Let’s have a look at the next phase the component goes through — the updating phase.

Updating

Each time something changes inside our component or parent component, in other words, when the state or props are changed, the component may need to be re-rendered. In simple terms, the component is updated.

So what lifecycle methods are invoked when a component is to be updated?

static getDerivedStateFromProps()

Firstly, the static getDerivedStateFromProps method is invoked. That’s the first method to be invoked. I already explained this method in the mounting phase, so I’ll skip it.

What’s important to note is that this method is invoked in both the mounting and updating phases.

shouldComponentUpdate()

By default, or in most cases, you’ll want a component to re-render when the state or props change. However, you do have control over this behaviour.

Here is the moment React decides whether we should update a component or not.

Within this lifecycle method, you can return a boolean and control whether the component gets re-rendered or not, i.e upon a change in the state or props.This lifecycle method is mostly used for performance optimisation measures.

Example usage of shouldComponentUpdate:

If the only way your component ever changes is when the this.props.color or the this.state.count variable changes, you could have the shouldComponentUpdate check that:


class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <View>
        <Button
          color={this.props.color}
          onPress={() => this.setState(state => ({count: state.count + 1}))}
        />
        <Text>Count: {this.state.count}</Text>
      </View>
    );
  }
}

In this code, shouldComponentUpdate is just checking if there is any change in this.props.color or this.state.count. If those values don’t change, the component doesn’t update. If your component is more complex, you could use a similar pattern of doing a “shallow comparison” between all the fields of the props and state to determine if the component should update. This pattern is common enough that React provides a helper to use this logic — just inherit from React.PureComponent. So this code is a simpler way to achieve the same thing:


class CounterButton extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {count: 1};
  }

  render() {
    return (
      <View>
        <Button
          title="Press me"
          color={this.props.color}
          onPress={() => this.setState(state => ({count: state.count + 1}))}
        />
        <Text>
          Count: {this.state.count}
        </Text>
      </View>
    );
  }
}

Most of the time, you can use React.PureComponent instead of writing your own shouldComponentUpdate, but it only does a shallow comparison, so you can’t use it if the props or state may have been mutated in a way that a shallow comparison would miss. This can be a problem with more complex data structures.

render()

After the shouldComponentUpdate method is called, a render is called immediately afterwards depending on the returned value from shouldComponentUpdate, which defaults to true.

getSnapshotBeforeUpdate(prevProps, prevState)

Right after the render method is called and before the most recently rendered output is committed, for example to the DOM, getSnapshotBeforeUpdate() is invoked .

There is a common use case in React when you can use it, but it is useless in React Native. To cut a long story short, if you are building a chat application using React, you can use this method to calculate the scroll size and scroll to the bottom as new messages appear In React Native you can simply use the onContentSizeChange prop for ScrollView. A component with an auto-scroll feature can look like this:


<ScrollView 
  onContentSizeChange={()=>{this.scrollViewRef.scrollToEnd();}} 
  ref={(ref) => this.scrollViewRef = ref}
>
  <Chats chatList={this.state.chatList} />
</ScrollView>

When chatList is updated with new data, ScrollView is automatically scrolled to the very bottom, so you are up to date with new messages.

componentDidUpdate()

componentDidUpdate() is invoked immediately after updating occurs. This method is not called for the initial render. This is also a good place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed).


componentDidUpdate(preProps) {
  if(prevProps.selectedState !== this.props.selectedState){
    fetch('https://pathToApi.com')
    .then(resp => resp.json())
    .then(respJson => {
      // do what ever you want with your `response`
      
      this.setState({
        isLoading: false,
        data: respJson,
      });
    })
    .catch(err => {
      console.log(err)
    })
  }
}

Unmounting

componentWillUnmount()

componentWillUnmount() is invoked immediately before a component is unmounted and destroyed. Perform any necessary cleanup in this method, such as invalidating timers, canceling network requests, or cleaning up any subscriptions that were created in componentDidMount().


// e.g add event listener 
componentDidMount() { 
	el.addEventListener() 
} 
 
// e.g remove event listener  
componentWillUnmount() { 
    el.removeEventListener() 
}

Typical uses of componentDidMount with componentWillUnmountYou should not call setState() in componentWillUnmount() because the component will never be re-rendered. Once a component instance is unmounted, it will never be mounted again.

Error handling

Sometimes it pays to stay in bed on Monday, rather than spending the rest of the week debugging Monday’s code.
CHRISTOPHER THOMPSON

All of us would be more than happy to write code without any errors, but this is more of a dream. In real life, errors appear everywhere, sometimes we are aware of them, sometimes we are not. This part will introduce you to some basic ways of handling errors.

Let’s implement a simple component to catch errors in the demo app. For this, we’ll create a new component called ErrorBoundary.

Here’s the most basic implementation:


import React, { Component } from 'react'; 
 
class ErrorBoundary extends Component { 
 state = {
   hasError: false,
}; 
 render() { 
   return this.props.children; 
  } 
} 

export default ErrorBoundary;

static getDerivedStateFromError()

Whenever an error is thrown in a descendant component, this method is called first, and the error thrown is passed as an argument.

Whatever value is returned from this method is used to update the state of the component.

Let’s update the ErrorBoundary component to use this lifecycle method.


import React, { Component } from "react"; 

class ErrorBoundary extends Component { 
 state = {
  hasError: false,
 }; 
 
 static getDerivedStateFromError(error) { 
    console.log(`Error log from getDerivedStateFromError: ${error}`); 
    return { hasError: true }; 
 } 
 
 render() {
    if(this.state.hasError) {
      return <Text> Something went wrong :( </Text>
    } 
    return this.props.children; 
  } 
} 
 
export default ErrorBoundary;

Right now, whenever an error is thrown in a descendant component, the error will be logged to the console, console.log(error), and an object will be returned from the getDerivedStateFromError method. This will be used to update the state of the ErrorBoundary component with hasError: true.

Note
getDerivedStateFromError() is called during the “render” phase, so side-effects are not permitted. For those use cases, use componentDidCatch() instead.

componentDidCatch(error, info)

The componentDidCatch method is also called after an error in a descendant component is thrown. Apart from the error thrown, it passes one more argument which represents more information about the error. In this method, you can send the error or info received to an external logging service. Unlike getDerivedStateFromError, componentDidCatch allows for side-effects:


componentDidCatch(error, info) { 
  logComponentStackToMyService(info.componentStack);
  // logToExternalService may make an API call. 
}

Let’s update the ErrorBoundary component to use the componentDidCatch method:


class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // Example "componentStack":
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logComponentStackToMyService(info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <Text>Something went wrong.</Text>;
    }

    return this.props.children; 
  }
}

Also, since ErrorBoundary can only catch errors from descendant components, we’ll have the component render whatever is passed as children or render a default error UI if something went wrong.


Conclusion

That’s it. I tried to write this article for both React Native newcomers and developers who are familiar with React Native or React. In case of any questions, feel free to ask me in the comment section below.

Below is a short cheat sheet summarising the article and pointing out the principles of each life-cycle method:


constructor

DO

  • Assign the initial state to this.state directly
  • If not using class properties syntax — prepare all class fields and bind functions that will be passed as callbacks

DON’T

  • Cause any side effects (AJAX calls, subscriptions, etc.)
  • Call setState()
  • Copy props into state (only use this pattern if you intentionally want to ignore prop updates)

render

DO

  • Return a valid Javascript value
  • The render() function should be pure

DON’T

  • Call setState()

componentDidMount

DO

  • Set up subscriptions
  • Network requests
  • You may setState() immediately (use this pattern with caution, because it often causes performance issues)

DON’T

  • Call this.setState as it will result in a re-render

componentDidUpdate

DO

  • Network requests (e.g. a network request may not be necessary if the props have not changed)
  • You may call setState() immediately in componentDidUpdate(), but note that it must be wrapped in a condition

DON’T

  • Call this.setState, as it will result in a re-render

shouldComponentUpdate

DO

  • Use to increase performance of components

DON’T

  • Cause any side effects (AJAX calls etc.)
  • Call this.setState

componentWillUnmount

DO

  • Remove any timers or listeners created in the lifespan of the component

DON’T

  • Call this.setState, start new listeners or timers

static getDerivedStateFromError()

DO

  • Catch errors and return them as state objects

DON’T

  • Cause any side effects

componentDidCatch

DO

  • Side effects are permitted
  • Log errors

DON’T

  • Render a fallback UI with componentDidCatch() by calling setState (use static getDerivedStateFromError() to handle fallback rendering).
Photo of Maks Kolodiy

More posts by this author

Maks Kolodiy

React Native Developer
Codestories Newsletter  Check what has recently been hot in European Tech.   Subscribe now

We're Netguru!

At Netguru we specialize in designing, building, shipping and scaling beautiful, usable products with blazing-fast efficiency
Let's talk business!

Trusted by: