In this article, we are going to explore React lifecycle methods. As for me, if you want to work with the React this knowledge is crucial because you need to be able to implement component logic in the right place.
I'm going to talk about React version 16.4+. But before we will dive in, let's discuss versions difference. If you are completely new to React, I believe you can skip this section and return to it later.
From the React 16.3 and above a few lifecycle methods were deprecated. They are still available to use with the
prefix UNSAFE_
, but will be removed in the next major release.
componentWillMount
renamed to UNSAFE_ComponentWillMount
. You can use the constructor
to replace this method.componentWillReceiveProps
renamed to UNSAFE_ComponentWillReceiveProps
. You can use static getDerivedStateFromProps
instead.componentWillUpdate
renamed to UNSAFE_ComponentWillUpdate
. You can use getSnapshotBeforeUpdate
instead.There is also one more difference between 16.3 and ^16.4:
static getDerivedStateFromProps
getDerivedStateFromProps
is called only once new props are received, but starting from the 16.4
this method is called every time regardless what caused the update (new props, setState(), forceUpdate()).This image represents React ^16.4 Components Lifecycle. It would be good if you could spend some time to study it, I also recommend you to return to this image during the article reading, so it will be easier to understand and to remember many things.
As we can see from the image, React component goes through the following phases:
This is the birth of our component. It means that the component goes through this phase during its initialization and first insertion into the DOM.
constructor(props) {}
This is just a class constructor, and it's the first method that is called when the component is created. The constructor is called only once in the whole component lifecycle.
When to use: initialize state, initialize properties, create refs, create methods bindings.
class ComponentDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
items: [],
};
this.onClick = this.onClick.bind(this);
this.wrapperRef = React.createRef();
}
}
If you don't know why do we use super(props)
, I recommend to check this article: Why Do We Write super(props)?.
Also, by using the class field proposal we can remove constructor
:
class ComponentWithoutConstructor extends React.Component {
// Create ref without constructor
wrapperRef = React.createRef();
// Create State without constructor
state = {
loading: false,
items: [],
};
// Use arrow function for callbacks to prevent
// Function.prototype.bind() usage.
onClick = () => {
/*...*/
};
}
static getDerivedStateFromProps(props, state) {}
The goal of this method is to make sure that state and props are in sync.
This method is static, so it doesn't have access to this and it should avoid any side effects. Instead, it's expected from you to return an object that will be merged with the state, or null if nothing to update.
When to use: update state based on props.
class ComponentDemo extends React.Component {
static getDerivedStateFromProps(props, state) {
if (props.loading !== state.loading) {
return { loading: props.loading };
}
// Return null if the state hasn't changed
return null;
}
}
Our returned object is similar to if we call this.setState in other non-static lifecycle methods.
this.setState({ loading: props.loading });
In the current phase (mounting) it's also possible to use the constructor
instead of this method.
This is the only required method in every react class component. In the same time, it's the most used lifecycle method; you will spend a lot of time here.
render() {
return <p>Hello, {this.props.name}</p>
}
As you can see in the example above, the render() method returns JSX that will be used to update the DOM. It can return JSX or null if nothing to display.
It's not recommended to change the state during rendering. React requires the render()
method to be pure (it doesn't modify the state, doesn't directly interact with the browser, and returns the same result for the same props and state).
When to use: return component representation using JSX.
This method is called right after our component did mount (was inserted into the tree). It's the right time to do API calls.
async componentDidMount() {
try {
const userDetials = await axios.get(`${apiUrl}/user?ID=12345`);
this.setState({ details: userDetials });
} catch(err) {
this.setState({ error: true });
} finally {
this.setState({ loading: false });
}
}
When to use: API calls, DOM manipulation, integration with 3rd-party libraries that need DOM access.
This phase starts when our component receives new props or if the current state was changed.
It's also possible to manually run the component updating phase by using this.forceUpdate() method. Note that in this case the shouldComponentUpdate method invocation will be ignored.
The behavior of this method is the same as described above. You can update your state based on props here.
It's not recommended to keep your state updated based on props, as it makes your component difficult to think about. Try to think first if you really need this inside the state, or you can just derive functionality from the props directly because getDerivedStateFromProps
should be used in rare cases.
shouldComponentUpdate(nextProps, nextState) {}
Once component receives new props or a new state it has to be updated. With the help of shouldComponentUpdate, we can tell react that we don't want to this when we think that this is not needed.
The shouldComponentUpdate has access to two arguments that represent next props and next state. We need to return boolean to tell React if we want to update the component,
by default it's always true
.
Usage: This method should be used only for performance optimization.
You can consider using built-in React.PureComponent instead of manually writing shouldComponentUpdate
.
render() {}
Now, our component rerenders to returns updated JSX.
getSnapshotBeforeUpdate(prevProps, prevState) {}
This method is called right before our component is ready to be inserted into the DOM. Consequently, it's the last time when you can access the DOM before it gets updated with the rerendered component.
In this method we have access to the previous props and previous state arguments. You can also get the current state and current props through this. Any value that you return here will be passed as a parameter to a componentDidUpdate (next method in this phase).
In many cases, you will not need this method, but sometimes it's very handy. Let's imagine that we have a chat app where new messages appear in the bottom. If user scrolled to the bottom of the list it would be good to keep this scroll position even if the list of messages is growing, so that the user would not need to scroll to the new messages manually to see them. To solve this, we can detect if the current scroll position is the bottom of the list and then return true or false, so that the componentDidUpdate can handle it and adjust scroll position if needed.
When to use: capture information from the DOM before it's changed, for example, scroll position or cursor selection.
Even though this method isn't static it's recommended to return a value that will be passed to the componentDidUpdate instead of changing the state.
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevState.messages.length < this.state.messages.length) {
const listRef = this.listRef.current;
// Here we detect if user scroll position is at the very bottom and ruturn boolean
return listRef.scrollTop + listRef.offsetHeight >= listRef.scrollHeight;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {}
This method is called right after the component gets updated inside the DOM.
In the componentDidUpdate we have access to the three arguments: previous props, previous state, and snapshot (data passed from the getSnapshotBeforeUpdate).
When to use: the purpose of the componentDidUpdate is similar to the componentDidMount. It's a good idea to do API calls, or 3rd-party libraries updating here.
Let's resume solving the issue I've described in the getSnapshotBeforeUpdate section. We are interested in the snapshot only if it equals true, it means that the user was in the bottom of the list before component was updated, and we need to update scroll position to keep the user at the bottom after
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot === true) {
const listRef = this.listRef.current;
// Adjust the position so scroll will be at the bottom again
listRef.scrollTop = listRef.scrollHeight - listRef.offsetHeight;
}
}
In this phase the component is not needed anymore and it will be unmounted from the DOM.
componentWillUnmount() {}
This is the last method in the React component lifecycle. It's executed right before our component removal from the DOM. This method should be used to perform the necessary cleanups.
When to use: clear timers, cancel network requests, destroy 3rd party UI library elements, and so on...
Think about it as a reverting action for things we did inside componentDidMount
or componentDidUpdate
.