Do React Components have a life too?

Well....Everything you see in this world follows a lifecycle. Say for example we human beings we too have a lifecycle. First we are born, we then live our lives and later at the end we die. And even chairs first they are built with wood, and then they are used for sitting, when they are old they break or are disposed.

You can see that even React components have a life too!
Every time they follow a lifecycle!

They have three phases!

  • Mounting: insert elements into the DOM

  • Updating: updating components in the DOM

  • Unmounting: Removing components from the DOM

Now next question might arise why do we need lifecycles in components?

Answer is simple sometimes you may want to run a function only after a React has updated the DOM or we could say rendered the content, like performing some side effect operations like Data fetching, setting subscription manually changing DOM.

Life Cycle of Class Component :

This is likely the more traditional life cycle way with the use of Class Component.

Pre-Mounting:

This is acts as an important role in the life of the component. It sets the initial state of the component and this is declared in the constructor of the class.

constructor() {
  super()
  this.state = {
    key: "value"
  }
}

Mounting:

Mounting consist of two lifecycle methods....

componentWillMount: This is called just once, just before the component is rendered. This can be used when you want to know the time of when the component is created.

componentWillMount() {
  this.setState({ startDateTime: new Date(Date.now())});
}

componentDidMount: This is also called just once immediately after the DOM has been rendered. Like you might want to retrieve or fetch some data after the component has been fully rendered.

componentDidMount() {
  this.setState({data:"Data is being fetched"});
}

Updating:

For updating in react the componentDidUpdate method is called. This method is called after every render except the first render. In this function the setState method can be called and used.

componentDidUpdate(){
   this.setState(data:"Data is being updated"});
}

UnMounting:

This consist of just one lifecycle method that is componentWillUnmount. It is the last function will be called just before the component is being removed from the DOM. This is to perform the clean up for the code which we would look in detail later.

componentWillUnmount(){
  unmountComponentAtNode(document.getElementById('root'));
}

So what's the issue in using lifecycle methods in Class Component?

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

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

Look at the above code you will notice that the side effects are placed separately into componentDidMount and componentDidUpdate. The reason why is it won't look good if the render method itself causes side effect, then the purpose of life cycle is a waste.

If you notice the code under componentDidMount and componentDidUpdate is relevant and yet they are put separately. this could be removed with the help of useEffect!

Life Cycle of Function Component:

With the help of useEffect we can combine the mounting and updating method into one and need not duplicate the code.

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

This makes the code looks cleaner and nice. Now lets see what useEffect does in detail

useEffect tells react that there is a function that needs to be performed after the DOM has been rendered. If you look closely the useEffect is placed inside the component so that is can access the count state directly. The useEffect runs after first and every other render it makes.

Clean Up:

Sometimes we would want to clean up the mess after using the component. This is better from the memory point of view and not waste them.

Clean Up in Class Component:

In class Component we would use a separate method for unmounting component along with mounting and updating.

class Modal extends React.Component {
  constructor() {
    super();
    this.state={
      x:0,
      y:0
      }
  }

  componentDidMount() {
   window.addEventListener('mousemove', this.logMousePosition)
  }
  componentWillUnmount() {
    window.removeEventListener('mousemove',this.logMousePosition)
  }
  handleAxisChange(axis) {
   this.setState({x:axis.clientx, y:axis.clienty})
  }
render(){
<div>
  X-{this.state.x} Y-{this.state.y}
</div>
}
}

As you can see here also the same logic is used in mounting and unmounting.

Clean Up in Function Component:

Again useEffect comes to the rescue to clean up the code. Simple just need add return function. React will run it when its time to clean up.

import React, { useState, useEffect } from 'react';

function Modal() {
const [x,setX]=useState(0)
const[y,setY]=useState(0)

const logMousePosition=axis=>{
setX(axis.clientX)
setY(axis.clientY)
}
useEffect(()=>{
window.addEventListener('mousemove',logMousePosition)

return()=>{
window.removeEventListener('mousemove',logMousePosition)
}
})
return(
<div>
         X-{x} Y-{y}
</div>
)
  }

React performs the clean up every time we want to unmount the component from the DOM. You can use the return function whenever you want to clean up after you done using your component.

As we know React always looks up into performance. This cleaning up and re-rendering might create a performance problem. Below we can see how the code can be optimized in both components(class and function).

Optimization in Class Component:

Here we would compare with the previous state in the update method.

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

Optimization in Function Component:

The performance is optimized by just adding a second parameter as array. It is to mention that if the values haven't changed you could skip applying the effect. If you don't add the second argument it will execute the effect after each render.

useEffect(() => {
  interval = setInterval(setCount,1000)};
}, [count]);

If you want the effect to be executed only on mounting and unmounting you can add an empty array. The empty array indicates the initial values of the props and state used inside the effect.

useEffect(() => {
  interval = setInterval(setCount,1000)};
}, []);

We use the same for return statement too. If we want to unmount something only if the value changes.

useEffect(() => {
  interval=setInterval(setCount,1000)
  return () => {
    clearInterval(interval);
  }
}, [count]);

End Thoughts...

We have seen that both class and function components have Life Cycles. They majorly involve mounting, updating and unmounting. In Class component these are performed by putting them into separate methods as componentDidMount, componentDidUpdate and componentWillUnmount. But in the case of Function Component all these three methods are singly handled by one that is the useEffect hook. Which is pretty cool as it helps to make our code smaller and simpler to handle.