React Native Component Lifecycle
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 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:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
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.
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).