React lets us define components as classes or as functions.
We know that component class can be defined by extending React.Component and the only required method in that sub class is render().
But components also have various lifecycle methods that you can override to run your code at a particular time in the process. These methods are categorized into four groups as follows:
1) Mounting
2) Updating
3) Error Boundaries
4) Unmounting
In order to understand these methods better we’ll implement a counter component and utilize all the lifecycle methods.
Initially, the counter component is having a constructor method which after being called will be followed by the render method.
import React, { Component } from 'react'
class Counter extends Component {
constructor(props){
console.log('constructors');
super(props)
this.state = {
counter: 0
}
this.increment = () =>{
this.setState({counter: this.state.counter+1});
}
this.decrement = () =>{
this.setState({counter: this.state.counter-1});
}
}
render() {
console.log('render');
return (
<div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
<div className="counter">
Counter:{this.state.counter}
</div>
</div>
)
}
}
export default Counter
As long as you’ll be clicking the increment or decrement button, the same number of times render method will be called.
Now lets introduce a few life cycle methods.
First one is componentDidMount method and it is called right after the render method.
componentDidMount(){
console.log("Components Did Mount");
}
Note: This method is called after the render method but only when the component is constructed.
However, there is another method componentDidUpdate which takes few parameters i.e. previous props, previous state and snapshot.
componentDidUpdate(preProps, prevState, snapshot){
console.log("Components Did Update");
}
On constructing the component, componentDidUpdate will not be called. Once you click increment or a decrement button this method will be called after the render method as many times you click.
Now, lets add the ability to mount and unmount this counter. We need to do that in App.js
constructor(props){
super(props)
this.state = {
mount: true
}
this.mountCounter = () => this.setState({mount: true});
this.unmountCounter = () => this.setState({mount: false});
}
render() {
return (
<div>
<button onClick={this.mountCounter} disabled={this.state.mount}>Mount Counter</button>
<button onClick={this.unmountCounter} disabled={this.state.mount}>Unmount Counter</button>
{this.state.mount ? <Counter/> : null}
</div>
)
}
Let’s add another life cycle method componentWillUnmount. Add this method in the counter component.
componentWillUnmount(){
console.log("Components Will Unmount");
}
As soon as the component is kicked out this method will be called and you can achieve this by clicking the unmount counter button.
But when you click mount counter button the constructor is triggered again along with the render and componentDidMount method respectively.
These are the most frequently used life cycle methods.
Other methods are:
shouldComponentUpdate with two parameters next props and next state and generally returns true by default.
shouldComponentUpdate(nextProps, nextState){
return true;
}
This method helps react know if render should be triggered or not. Use case for it might be that your state or props are updated and you don’t need render to be triggered because you are not changing anything in the UI and your method is too expensive to be computed.
Modify the following in the App.js
this.state = {
mount: true,
ignoreProp: 0
}
Add a method in the constructor.
this.ignoreProp = () => this.setState({ignoreProp: Math.random()});
Pass this prop in the counter component and add a button to trigger.
<button onClick={this.ignoreProp}>Ignore Prop</button>
{this.state.mount ? <Counter ignoreProp={this.state.ignoreProp}/> : null}
If we click on ignore prop button its going to call render and component did update method because something changed on the parent and is passed down to the component.
shouldComponentUpdate(nextProps, nextState){
if(nextProps.ignoreProp && this.props.ignoreProp !== nextProps.ignoreProp)
{
console.log('Should Component Update - DO NOT RENDER')
return false;
}
console.log('Should Component Update - RENDER')
return true;
}
Now, adding this if statement to check whether the current ignore prop value is different from the next prop value and returning false thereby not allowing react to call render method because there is no change in the UI.
The next method is a static method and is called before every other method. getDerivedStateFromProps with two parameters props and state. The purpose of this method is to give you a chance to copy any values from props that you may be interested in transferring over to state.
To illustrate, add a random value in the state of App.js
this.state = {
mount: true,
ignoreProp: 0,
seed: 40
}
Add a method seed generator.
this.seedGenerator = () => this.setState({seed: Number.parseInt(Math.random*100)});
Pass the seed as a prop to the counter component and also a button to trigger.
<button onClick={this.seed}>Seed Generator</button>
{this.state.mount ? <Counter ignoreProp={this.state.ignoreProp} seed={this.state.seed}/> : null}
Anything you assign to this getDerivedStateFromProps gets assigned to state.
static getDerivedStateFromProps(props, state){
return null;
}
So, if you don’t want to change state you simply return null.
Also, in the counter component’s state add seed value as 0 for default.
this.state = {
counter: 0,
seed: 0
}
Now, to check its functioning we add a if statement to check if seed exists on the props and the seed that is in our state is not equal to that seed that’s passed then we’ll return seed value that’s props.seed and set the counter to props.seed as well.
static getDerivedStateFromProps(props, state){
if(props.seed && state.seed !== props.seed){
return {
seed: props.seed,
counter: props.seed
}
}
return null;
}
Now, the counter will start at 40 as seed value in App.js is initialized as 40. Clicking Seed Generator button will change the value of the counter.
So, basically it copies props into state.
Note: Since it is a static method, we don’t have access to instance of the object.
Next method is getSnapShotBeforeUpdate which is called just before render and takes previous props and previous state as arguments and returns a value. This method allows us to capture some properties that are not stored in the state before we re-render that component. Whatever is returned here is passed to componentDidUpdate as a third parameter called snapshot.
getSnapshotBeforeUpdate(prevProps, prevState)
{
console.log('Get Snapshot Before Update');
return null;
}
Finally, there is a method called componentDidCatch which is part of the error boundaries and takes two parameters error and info and gives a chance to gracefully handle errors that we run into.
componentDidCatch(error, info){
console.log('Component Did Catch');
this.setState({error, info});
}
Now, lets create a component which produces a error to check its functionality.
We’ll create a ErrorComponent and return a div with some non-existent value.
const ErrorComponent = () => <div>{props.ignore}</div>
Put that component to be rendered in the counter components render method.
When you’ll run this application, you’ll get an error and component did catch will be called. If you don’t use this method, you’ll lose everything which is rendered in the browser.
In the render method you can check for the error and get it displayed.
render() {
console.log('render');
if(this.state.error)
{
return <div>We have encountered an error! {this.state.error.message}</div>
}
return (
<div>
<button onClick={this.increment}>Increment</button>
<button onClick={this.decrement}>Decrement</button>
<div className="counter">
Counter:{this.state.counter}
</div>
<ErrorComponent/>
</div>
)
}
Now, comes a doubt that we can use these lifecycle hooks in a class component but how can we use it in a functional component. For that we can use useEffect hook which came in React 16.8. It will help us to detect the state changes and props changes also. It will perform all the functionalities of componentDidMount, componentWillUnmount and componentDidUpdate.
First of all you’ll have to import useEffect and useState from react.
import React, { useEffect, useState } from 'react'
Use the useState method for the counter functionality in the component.
const [count, setCount]=useState(0);
Add a button to increase the count using setCount function.
<button onClick={() => {setCount(count+1)}}>Click to increment count {count}</button>
In order to detect the state changes we use the useEffect method.
useEffect(()=>{
console.log(count);
})
So, as soon as the state will change this useEffect method will be triggered and you can perform any operation as per your wish.
But if you want it to be triggered at a specific change of state you can do the following:
useEffect(()=>{
console.log(count);
},[count===3])
This will trigger only when count is 3.
Similarly, you can also pass the count as a prop from the parent to the child component and use the useEffect method in the child class to detect the changes and perform some operation.
If you want to use this useEffect hook in such a way that you don’t want to execute componentDidMount and componentDidUpdate at the same time the following is the procedure to do so.
Initialize a reference hook which returns mutable object whose current property is initialized with false.
const didMountRef = useRef(false)
Now, modify the useEffect method as shown below.
useEffect(() => {
if (didMountRef.current) {
doStuff()
} else didMountRef.current = true
}
Implementing this will ensure that initially didMountRef.current is false and for the first call it acts as componentDidMount but after the mounting didMountRef.current will become true and for the next times it will work as componentDidUpdate.
Also, you can perform a clean up in the useEffect hook which basically ensures that after each update previous events listened or subscriptions are deleted to maintain better performance and avoid app from crashing.
useEffect(() => {
effect
return () => {
cleanup
}
}, [input])
Conclusion:
useEffect() runs after every render. So, it’s like componentDidMount(), componentDidUpdate() and componentWillUnmount() all at the same time. So, still you can find use cases for class components because of access to specific lifecycle methods though react hooks introduced in React 16.8 resolve many issues like managing of state and optimization.
That's it for the post.
Top comments (0)