the lifecycle componentWillReceiveProps was the only way to update state in response to a change in props without an additional render
In version 16.3, we introduced a replacement lifecycle, getDerivedStateFromProps to solve the same use cases in a safer way.
ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17.
componentDidMount()
componentDidMount() will be rendered immediately after a component is mounted. This method will render only once and all the initialization that requires DOM nodes should go here. Setting state in this method will trigger a re-rendering.
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate() is invoked immediately every time the updating occurs. This method is not called for the initial render.
To make it simple, the first is called at the beginning, and the second upon every change. They are absolutely not interchangeable.
About using setState inside componentDidUpdate: beware! Using setState calls componentDidUpdate, so you might end up with an infinite loop if you call setState at *every invocation of componentDidUpdate.
/*
componentDidMount() will be rendered immediately after a component is mounted. This method will render only once and all the initialization that requires DOM nodes should go here. Setting state in this method will trigger a re-rendering.
componentDidUpdate() is invoked immediately every time the updating occurs. This method is not called for the initial render.
*/
import React from "react";
class ComponentDidMountLifeCycles extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidMount() {
//This function will call on initial rendering.
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate(prevProps, prevState, snapshot) {
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>
);
}
}
export default ComponentDidMountLifeCycles;
React lifecycle methods diagram (wojtekmaj.pl)
You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop. If your component implements the getSnapshotBeforeUpdate() lifecycle (which is rare), the value it returns will be passed as a third “snapshot” parameter to componentDidUpdate(). Otherwise this parameter will be undefined.
import React, { Component } from "react";
import { connect } from "react-redux";
class Lifecycle extends Component {
constructor(props) {
super(props);
this.state = { count: 0, hasError: false };
console.log("Constructor: Component is being constructed");
}
componentDidMount() {
console.log("componentDidMount: Component has been mounted");
// Example: Simulate an API call and update the state
setTimeout(() => {
this.setState({ count: this.state.count + 1 });
}, 1000);
}
static getDerivedStateFromProps(nextProps, prevState) {
console.log(
"getDerivedStateFromProps: Synchronizing state with props " +
" prevState:",
prevState,
" NextProps:",
nextProps
);
// // Example: If props.externalCount is different from state.count, update state.count
// if (nextProps.externalCount !== prevState.count) {
// return { count: nextProps.externalCount };
// }
return nextProps;
}
shouldComponentUpdate(nextProps, nextState) {
console.log(
"shouldComponentUpdate Next Props:",
nextProps,
"Next State:",
nextState
);
// Example: Only update if count is even
let upd = nextState.count % 2 === 0;
console.log("shouldComponentUpdate: Should component re-render? " + upd);
return upd;
}
render() {
console.log("Render: Component is rendering");
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return (
<div
className="container"
style={{ backgroundColor: "rgba(34,34,34,0.3)" }}
>
<div>
<h1>Lifecycle Methods Example</h1>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>Increment</button>
</div>
</div>
);
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log(
"getSnapshotBeforeUpdate: prevProps.count " +
prevProps.count +
" prevState.count " +
prevState.count
);
return { prevCount: prevState.count };
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log("componentDidUpdate: Component has been updated");
console.log(
"Previous Props:",
prevProps,
"Previous State:",
prevState,
"Snapshot:",
snapshot
);
// Example: Log the previous count from snapshot
if (snapshot) {
console.log("Previous count from snapshot:", snapshot.prevCount);
}
return null;
}
componentWillUnmount() {
console.log("componentWillUnmount: Component is about to be unmounted");
}
static getDerivedStateFromError(error) {
console.log("getDerivedStateFromError: An error occurred");
// Example: Set hasError state to true
return { hasError: true };
}
componentDidCatch(error, info) {
console.log("componentDidCatch: Error caught");
console.log(error, info);
}
handleClick = () => {
this.setState((prevState) => ({ count: prevState.count + 1 }));
};
}
export default connect(null)(Lifecycle);
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
React 17
React 18
UseEffect
The useEffect hook lets you perform side effects in function components. It is a combination of multiple lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount.
useEffect(() => {
document.title = `You clicked ${count} times`;
return () => {
console.log('Cleanup on unmount');
};
}, [count]); // Only re-run the effect if count changes
------------------------------------------------
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => setData(data));
return () => {
// Cleanup function
};
}, []); // Empty dependency array means this effect runs once on mount
if (!data) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
}
import React, { useReducer } from 'react';
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useCallback returns a memoized version of the callback that only changes if one of the dependencies has changed.
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const incrementCount = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
return (
<div>
<ChildComponent onIncrement={incrementCount} />
<p>Count: {count}</p>
</div>
);
}
function ChildComponent({ onIncrement }) {
console.log('ChildComponent rendered');
return <button onClick={onIncrement}>Increment</button>;
}
useMemo returns a memoized value that only recomputes when one of its dependencies has changed.
import React, { useMemo, useState } from 'react';
function ExpensiveComputation({ a, b }) {
const result = useMemo(() => {
console.log('Computing...');
return a * b;
}, [a, b]);
return <div>Result: {result}</div>;
useRef returns a mutable ref object whose .current property is initialized to the passed argument.
import React, { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
useLayoutEffect is
similar to useEffect, but it fires synchronously after all DOM mutations.
getting DOM element attribute to state variable
import React, { useState, useLayoutEffect, useRef } from 'react';
function Tooltip() {
const [tooltipHeight, setTooltipHeight] = useState(0);
const tooltipRef = useRef();
useLayoutEffect(() => {
const height = tooltipRef.current.clientHeight;
setTooltipHeight(height);
}, []);
return (
<div>
<div ref={tooltipRef}>This is a tooltip</div>
<p>The tooltip is {tooltipHeight}px tall</p>
</div>
);
}