Every React class component moves through a lifecycle made up of four phases:
This is the phase where a component instance is created and inserted into the DOM.
import React from "react";
class Profile extends React.Component {
constructor(props) {
super(props);
console.log("1: constructor");
}
static getDerivedStateFromProps(props, state) {
console.log("2: getDerivedStateFromProps");
return null;
}
render() {
console.log("3: render");
return <h1>{this.props.rollNumber}</h1>;
}
componentDidMount() {
console.log("4: componentDidMount");
}
}
export default Profile;
Call order and purpose:
Example of state being altered via getDerivedStateFromProps before mount:
import React from "react";
class Profile extends React.Component {
constructor(props) {
super(props);
this.state = { age: 24 };
}
static getDerivedStateFromProps(props, state) {
return { age: 25 }; // overrides state before first render
}
increment = () => {
this.setState({ age: this.state.age + 1 });
};
render() {
return (
<>
<h1>{this.state.age}</h1>
<button onClick={this.increment}>Click me</button>
</>
);
}
}
export default Profile;
Here, age becomes 25 before the component ever appears on screen, overriding the constructor's initial value of 24.
Note: whenever props or state change, getDerivedStateFromProps() and render() both run again.
Any change to state or props causes the component to re-render — this is the "updating" phase. The methods run in this order:
import React from "react";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { age: 24 };
}
static getDerivedStateFromProps(props, state) {
console.log("1: getDerivedStateFromProps");
return null;
}
shouldComponentUpdate() {
console.log("2: shouldComponentUpdate");
return true; // false/null would block the re-render
}
componentDidMount() {
console.log("componentDidMount");
}
increment = () => {
this.setState({ age: this.state.age + 1 });
};
render() {
console.log("3: render");
return (
<>
<h1>{this.state.age}</h1>
<button onClick={this.increment}>Click me</button>
</>
);
}
}
export default Counter;
Putting all five update-phase methods together:
import React from "react";
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { age: 24 };
}
static getDerivedStateFromProps(props, state) {
console.log("1: getDerivedStateFromProps");
return null;
}
shouldComponentUpdate() {
console.log("2: shouldComponentUpdate");
return true;
}
componentDidMount() {
console.log("componentDidMount");
}
getSnapshotBeforeUpdate() {
console.log("4: getSnapshotBeforeUpdate");
return null;
}
componentDidUpdate() {
console.log("5: componentDidUpdate");
}
increment = () => {
this.setState({ age: this.state.age + 1 });
};
render() {
console.log("3: render");
return (
<>
<h1>{this.state.age}</h1>
<button onClick={this.increment}>Click me</button>
</>
);
}
}
export default Counter;
componentWillUnmount() fires right before a component is removed from the DOM and torn down — the standard place to clean up timers, subscriptions, or event listeners.
These methods trigger when a descendant (child) component throws an error:
import React, { Component } from "react";
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
console.log(`Caught in getDerivedStateFromError: ${error}`);
return { hasError: true };
}
componentDidCatch(error, info) {
console.log(`Caught in componentDidCatch: ${error}`);
console.log(info);
}
render() {
if (this.state.hasError) {
return <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
export default ErrorBoundary;
Advertisement