All Ruby on Rails Node JS Android iOS React Native Frontend Flutter

React Native Component Lifecycle

Intro

For the introduction purpose, I would like to show you what exactly I would 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 components have their own phases, what is important, only class components have them. When an instance of a component is being created and inserted into the DOM it gets properties, or just, props, and from now 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.


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 has taken birth on the browser and grows by receiving new updates. 
  • Unmounting — the component is not needed and the component will get 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 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 being re-rendered:

Unmounting

This method is called when a component is being 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 when we know what are the lifecycle phases and their methods, we can get down to the next part — 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-effect or subscription in this method you should use componentDidMount(), I will introduce it later on as we are going thru each method 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 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
}

// ...

I guess now you are 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.

With this lifecycle method, 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 a 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. Component state in this manner is referred to as 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 aren’t sure of what you’re doing.

 

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 i.e 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 being 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, maybe, 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, we are going to fetch any data from an API, then 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, componentDidMount() function is called and we request data from the server, and 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 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 is changing inside our component or parent component, in other words, when state or props are changed, the component may need to re-render. In simple terms, the component is updated.

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

 

static getDerivedStateFromProps()

Firstly, the static getDerivedStateFromProps method is also 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. The same method.

 

shouldComponentUpdate()

By default, or in most cases, you’ll want a component to re-render when state or props changes. However, you do have control over this behaviour.
Here is the moment React decides whether we should update component or not.

Within this lifecycle method, you can return a boolean — true or false and control whether the component gets re-rendered or not i.e upon a change in 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 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 got more complex, you could use a similar pattern of doing a “shallow comparison” between all the fields of 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, 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 to e.g. the DOM the 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 the long story short, if you are building chat application using React, you can use this method to calculate the scroll size, and to scroll to the bottom as new messages are appearing, in React Native you can simply use onContentSizeChange prop for ScrollView. Component with autoscroll 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() 
}

Common use 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 a 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 some basic ways to handle 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 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 is returned from the getDerivedStateFromError method. This will be used to update the state of the ErrorBoundary component i.e 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 is passed 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, the 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 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 the 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.
Here is also a short cheat sheet summarising article and pointing out some main and common 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 valid javascript value
  • 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 for increasing performance of poor performing Components

DON’T

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

 

 

componentWillUnmount

DO

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

DON’T

  • Call this.setState, start new listeners or timers

 

 

static getDerivedStateFromError()

DO

  • Catch errors and return them as a state object

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 )

 

We're building our future. Let's do this right - join us
READ ALSO FROM Mobile
Read also
Need a successful project?
Estimate project or contact us