Exploring Advanced React Components and Lifecycle

29 Aug 2022
Intermediate
5.9K Views

Last time we defined the two kinds of components and their differences as well as use-cases of when are we going to use which is which. So today, let’s delve deeper into advanced concepts of what the components can do. Without further ado, let us elaborate and dissect the advanced components further.

Component Lifecycle

In my last article, I mentioned that only class-based components have “lifecycle hooks” that run once React starts to dock the component within the DOM. So, a component lifecycle refers to a series of processes a component will undergo once React starts to insert the component from initializing down to methods that correspond with the component’s current behavior. Let’s have a look at the image below for a reference to the lifecycle methods.

Now you noticed we have 3 kinds of lifecycle flow here along with methods that trigger along with it: Mounting, Updating, Unmounting Depending on the scenario and current state a component is into, one of the lifecycle flow will be triggered until it completes the process. Let’s explain them one by one :

Mounting

This is the most common lifecycle flow that will be triggered once React renders the components. The mounting lifecycle will be triggered when a component is being instantiated and inserted within the DOM tree. You could say this is also the starting point. The following methods will be called once a component is instantiated:

 constructor()
 getDerivedStateFromProps()
 render()
 componentDidMount()

Here's the code snippet for the mounting lifecycle :

import React, { Component } from 'react';
// import axios from 'axios';

import AnotherComponent from './AnotherComponent';
import Todo from './Todo';
import ParentComponent from './ParentComponent';

export default class App extends Component {

 constructor(props) {
 super(props);
 this.state = {
 val: "Click me to change me"
 }

 console.log("constructor executed");
 }

 componentDidMount = () => {
 console.log("componentDidMount() executed");
 }
 
 static getDerivedStateFromProps(nextProps, prevState) {
 console.log("getDerivedStateFromProps() executed");
 }
 
 render() {
 console.log("component rendered..");
 
 return (
 <div>
 <h2>
 <AnotherComponent />
 <Todo />
 </h2>
 </div>
 )
 }
}

When we put console.log in each method, we see an interesting and similar pattern

Updating

This lifecycle will be triggered once a change in props or state is initiated. Then, the component will be re-rendered while running the lifecycle.

In other words, This is the second phase that represents times where a component needs to be updated due to certain changes whether in its props or the state object. These changes can be occurred within the component or through the backend by using the response data from the API and all these changes will trigger the render function again.

The following methods will be called once a component is updated

 getDerivedStateFromProps()
 shouldComponentUpdate()
 render()
 getSnapshotBeforeUpdate()
 componentDidUpdate()

Here's the code for updating the lifecycle :

import React, { Component } from 'react';
// import axios from 'axios';

import AnotherComponent from './AnotherComponent';
import Todo from './Todo';
import ParentComponent from './ParentComponent';

export default class App extends Component {

 constructor(props) {
 super(props);
 this.state = {
 val: "Click me to change me"
 }

 console.log("constructor executed");
 }

 // componentDidMount = () => {
 // // throw "error";
 // console.log("componentDidMount() executed");
 // }
 
 // componentWillUnmount = () => {
 // console.log("component unmounted...");
 // }

 static getDerivedStateFromProps(nextProps, prevState) {
 console.log("getDerivedStateFromProps() executed", nextProps, prevState);

 return null;
 }

 getSnapshotBeforeUpdate = (prevProps, prevState) => {
 console.log("getSnapshotBeforeUpdate() executed", prevProps, prevState);
 }

 componentDidUpdate = (prevProps, prevState) => {
 console.log("componentDidUpdate() executed", prevProps, prevState);
 }
 
 render() {
 console.log("component rendered..");
 
 return (
 <div>
 <h2>
 <AnotherComponent />
 <Todo />
 </h2>
 </div>
 )
 }
}

When we put console.log inside each of the methods, we see an interesting and similar pattern as given below :

Unmounting

This lifecycle will be triggered once a component is removed from the DOM tree, and marks the end of a component's lifecycle.

The following methods will be called once a component is gets unmounted:

 componentWillUnmount()

Here's the code for unmountinglifecycle:

import React, { Component } from 'react';
// import axios from 'axios';

import AnotherComponent from './AnotherComponent';
import Todo from './Todo';
import ParentComponent from './ParentComponent';

export default class App extends Component {

 constructor(props) {
 super(props);
 this.state = {
 val: "Click me to change me"
 }

 console.log("constructor executed");
 }

 componentDidMount = () => {
 throw "error";
 }
 
 componentWillUnmount = () => {
 console.log("component unmounted...");
 }

 static getDerivedStateFromProps(nextProps, prevState) {
 console.log("getDerivedStateFromProps() executed", nextProps, prevState);

 return null;
 }

 // getSnapshotBeforeUpdate = (prevProps, prevState) => {
 // console.log("getSnapshotBeforeUpdate() executed", prevProps, prevState);
 // }

 // componentDidUpdate = (prevProps, prevState) => {
 // console.log("componentDidUpdate() executed", prevProps, prevState);
 // }
 
 render() {
 console.log("component rendered..");
 
 return (
 <div>
 <h2>
 This component will not be rendered because the error was thrown during mount...
 </h2>
 </div>
 )
 }
}

When we put console.log inside each of the above methods, we see an interesting and similar pattern as given below.

To trigger unmount lifecycle, I have to throw a dummy exception the moment the component was mounted. So this is usually triggered when something happens with your component logic or some exceptions that were thrown by other factors. It’s probably best to put your loggers in this area if you have one.

Nested Components

You can nest the various components within React component so easily just as nesting HTML DOM. Let’s see some examples which showcase that the component can be nested.

You can see that in the App component, we nested another two components: another component, Todo.

We can easily nest it in JSX by just how you nest HTML elements. You can nest and put more components for as long as you like regardless of whether it’s a functional or class-based component. React can effortlessly nest those components for all you like.

Components Inheritance

Just as how we inherit parent classes from one to another child in Object-Oriented Programming, we could also do the same for components in React. Take note that the Reactjs team doesn’t recommend component inheritance as what the official docs say :

"At Facebook, we use React in thousands of components, and we haven’t found any use cases where we would recommend creating component inheritance hierarchies. Props and composition give you all the flexibility you need to customize a component’s look and behavior in an explicit and safe way. Remember that components may accept arbitrary props, including primitive values, React elements, or functions."

Depending on the use-case of your application, you might be able to safely get away from using component inheritance. If you still insist on using it, let’s try to do some samples with this code snippet:

import React, { Component } from 'react';
// import axios from 'axios';

import AnotherComponent from './AnotherComponent';
import Todo from './Todo';
import ParentComponent from './ParentComponent';

export default class App extends ParentComponent {

 constructor(props) {
 super(props);
 this.state = {
 val: "Click me to change me"
 }

 console.log("constructor executed");
 }

 componentDidMount = () => {
 throw "error";
 }
 
 componentWillUnmount = () => {
 console.log("component unmounted...");
 }

 static getDerivedStateFromProps(nextProps, prevState) {
 console.log("getDerivedStateFromProps() executed", nextProps, prevState);

 return null;
 }

 getSnapshotBeforeUpdate = (prevProps, prevState) => {
 console.log("getSnapshotBeforeUpdate() executed", prevProps, prevState);
 }

 componentDidUpdate = (prevProps, prevState) => {
 console.log("componentDidUpdate() executed", prevProps, prevState);
 }
 
 render() {
 console.log("component rendered..");
 
 return (
 <div>
 <h2>
 This component will not be rendered because the error was thrown during mount...
 </h2>
 </div>
 )
 }
}

We have created a parent component that we can use to inherit into the child component. We simply have to use the parent component to “extend” the App component instead of using React.Component class.

When looking at the lifecycle behavior, we see an interesting pattern here in the console output :

We see here that the constructor is triggered from parent component, getDerivedStateFromProps() from App, render() from App, componentDidMount() from parent component.

Now, this is getting interesting and confusing at the same time. No wonder why React team almost never recommended inheriting components because this will disrupt the normal flow of the lifecycle. This is effectively interrupting the normal flow of lifecycle interchanging from the parent or the child component. They recommend using a composition model rather than an inheritance

Conclusion

React component lifecycle is one of the core features  Now that we have learned components in-depth, it’s time for more hacking in React!

Learn to Crack Your Technical Interview

Accept cookies & close this