The React lifecycle: methods and hooks explained

Menard Maranan
Menard Maranan
Contributor

Mar 4, 2022

A React component undergoes three different phases in its lifecycle, including mounting, updating, and unmounting. Each phase has specific methods responsible for a particular stage in a component's lifecycle. These methods are technically particular to class-based components and not intended for functional components.

However, since the concept of Hooks was released in React, you can now use abstracted versions of these lifecycle methods when you’re working with functional component state. Simply put, React Hooks are functions that allow you to “hook into” a React state and the lifecycle features within function components.

In this post, you'll learn more about the React component lifecycle and the different methods within each phase (for class-based components), focusing particularly on methods and hooks.

Phases of a React component's lifecycle

A React component undergoes three phases in its lifecycle: mounting, updating, and unmounting.

  • The mounting phase is when a new component is created and inserted into the DOM or, in other words, when the life of a component begins. This can only happen once, and is often called “initial render.”
  • The updating phase is when the component updates or re-renders. This reaction is triggered when the props are updated or when the state is updated. This phase can occur multiple times, which is kind of the point of React.
  • The last phase within a component's lifecycle is the unmounting phase, when the component is removed from the DOM.

In a class-based component, you can call different methods for each phase of the lifecycle (more on this below). These lifecycle methods are of course not applicable to functional components because they can only be written/contained within a class. However, React hooks give functional components the ability to use states.

Hooks have gaining popularity because they make working with React cleaner and often less verbose.

React lifecycle methods

Let’s learn more about the methods that make up each of our three phases.

The mounting phase

In the mounting phase, a component is prepared for and actually inserted into the DOM. To get through this phase, four lifecycle methods are called: constructor, static getDerivedStateFromProps, render, and componentDidMount.

The constructor method

The constructor method is the very first method called during the mounting phase.

It's important to remember that you shouldn't add any side effects within this method (like sending an HTTP request) as it's intended to be pure.

This method is mostly used for initializing the state of the component and binding event-handler methods within the component. The constructor method is not necessarily required. If you don't intend to make your component stateful (or if that state doesn’t need to be initialized) or bind any method, then it’s not necessary to implement.

The constructor method is called when the component is initiated, but before it’s rendered. It’s also called with props as an argument. It's important you call the super(props) function with the props argument passed onto it within the constructor before any other steps are taken.

This will then initiate the constructor of React.Component (the parent of this class-based component) and it inherits the constructor method and other methods of a typical React component.

For example, below you can see a class-based component implementation of a counter with a constructor in it, where the state is initialized and an event-handler method is bound (ignore the render method for now):

1
2import React from 'react';
3
4
5class Counter extends React.Component {
6	constructor(props) {
7		super(props);
8		this.state = {
9			count: 0
10		};
11
12		this.setCount = this.setCount.bind(this);
13	}
14
15	setCount() {
16		this.setState({count: this.state.count + 1});
17	}
18
19	render() {
20		return (
21			<div>
22				<h1>Counter</h1>
23				<button onClick={this.setCount}>Click to add</button>
24				<p>Count: {this.state.count}</p>
25			</div>
26		)
27	}
28}
29
30

In the Counter component, you can see the component's state is initialized within the constructor method to keep track of the count state. The setCount method, which is an event-handler attached to your button in this case, is bound within the constructor.

The static getDerivedStateFromProps method

Props and state are completely different concepts, and part of building your app intelligently is deciding which data goes where.

In many cases though, your component’s state will be derivative of its props. This is where the static getDerivedStateFromProps method comes in. This method allows you to modify the state value with any props value. It's most useful for changes in props over time, and we’ll learn later that it’s also useful in the update phase.

The method static getDerivedStateFromProps accepts two arguments: props and state, and returns an object, or null if no change is needed. These values are passed directly to the method, so there’s no need for it to have access to the instance of the class (or any other part of the class) and thus is considered a static method.

In the mounting phase, the getDerivedStateFromProps method is called after the constructor method and right before the component is rendered. This is the most rarely used method in this phase, but it’s still important to know so you can use it if needed.

For reference, here's an example of how getDerivedStateFromProps is used:

1
2class UserPreview extends React.Component {
3	
4	constructor(props) {
5		super(props);
6		this.state = {
7			...
8			fullname: ""
9		};
10	}
11
12
13	static getDerivedStateFromProps(props, state) {
14		return {
15			...
16			fullname: `${props.firstname} ${props.lastname}`
17		}
18	}
19
20	render() {
21		// ...
22	}
23}
24
25

Another way of looking at it is just providing a default for your component state based on props.

The render method

The render method is the only required method for a class-based React component. It’s called after the getDerivedStateFromProps method and actually renders or inserts the HTML to the DOM.

Typically, the render method returns the JSX which will eventually be rendered, but it can also return other values.

It's important to remember that the render method is meant to be pure. This means you can’t modify the state, have any direct interaction with the browser, or any other kind of side effect like sending an HTTP request. Just think of it as writing HTML, but of course as JSX.

Below is an example of using the render method:

1
2class SubmitButton extends React.Component {
3	render() {
4		return (
5			<button
6				type="submit"
7				style={this.props.styles}
8			>
9				{this.props.child}
10			</button>
11		);
12	}
13}
14
15

The componentDidMount method

componentDidMount is the last lifecycle method called in the mounting phase. It’s called right after the component is rendered or mounted to the DOM.

With this method, you're allowed to add side effects like sending network requests or updating the component's state. Additionally, the componentDidMount method allows you to make subscriptions like subscribing to the Redux store. You can also call the this.setState method right away; however this will cause a re-render as it kicks in the update phase, since the state has changed.

You need to be careful with componentDidMount because it may cause unnecessary re-renders.

Here's an example using the componentDidMount method:

1
2class NASACounter extends React.Component {
3  constructor(props) {
4    super(props);
5    this.state = {
6      count: 10
7    };
8  }
9
10  componentDidMount() {
11    const myTimer = setInterval(() => {
12      this.state.count > 0
13        ? this.setState({ count: this.state.count - 1 })
14        : clearInterval(myTimer);
15    }, 1000);
16  }
17
18  render() {
19    return (
20      <div style={this.props}>
21        <h1>
22          NASA Countdown: <br /> {this.state.count || "🪐"} <br />
23          {"⭐".repeat(this.state.count) || "🚀"}
24        </h1>
25        {this.state.count === 0 && <h2>LIFT OFF!!!</h2>}
26      </div>
27    );
28  }
29}
30
31

A common use case is waiting until a component renders to start an animation, or fetching data from an external source.

The updating phase

The Updating phase is triggered when component props or state change, and consists of the following methods: static getDerivedFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, and componentDidUpdate.

The methods getDerivedFromProps and render are also part of the mounting phase. Since they’ve been covered previously, this section focuses on the other three methods.

The static getDerivedStateFromProps

In the update phase, the first lifecycle method called is getDerivedStateFromProps. This method is useful if you have updated props and you want to reflect that in the component's state.

For instance, a component’s state may depend on the value of its props. With getDerivedStateFromProps, before the component was even re-rendered, its state can reflect those changes and can be shown (if applicable) to the newly updated component.

Remember, this method is rarely used and isn’t ideal for most situations.

The shouldComponentUpdate method

shouldComponentUpdate is another rarely used lifecycle method. It’s specifically intended for performance optimization, and basically lets you tell React when you don't need to re-render when a new state or props comes in. While it can help avoid re-renders, you shouldn’t rely on it to prevent re-renders since you might skip a necessary update and encounter bugs.

To prevent renders, you can opt in to logical rendering instead, or use a PureComponent which is recommended by React.

This method can accept nextProps and nextState as arguments, however, they’re optional, and you can declare it without the arguments. This method then returns a Boolean value. The Boolean value defines whether a re-render happens. The default value is true, where re-render happens in all cases whenever state or props changes.

Note that the shouldComponentUpdate method is ignored when forceUpdate() is invoked.

Here's an example using the shouldComponentUpdate lifecycle method:

1
2class ShowWholeNumber extends React.Component {
3	shouldComponentUpdate(nextProps, nextState) {
4		return Math.round(nextProps.num) === Math.round(this.props.num);
5	}
6
7	render() {
8		return (
9			<div>
10				The Whole number is: {Math.round(this.props.num)}
11			</div>
12		)
13	}
14}
15
16

The getSnapshotBeforeUpdate method

The getSnapshotBeforeUpdate method gives you access to the previous props and state of the component before it's updated. This allows you to work or check on the previous values of the state or props. It’s another method that’s rarely used.

A good use case for this method is handling scroll positions in a chat app. When a new message comes in as the user is viewing old messages, it shouldn’t push the old ones out of view.

getSnapshotBeforeUpdate is called after the render method, and before componentDidUpdate. If the getSnapshotBeforeUpdate method returns anything, it will be passed as a parameter for the componentDidUpdate method:

1
2class ChatList extends React.Component {
3  constructor(props) {
4    super(props);
5    this.chatsList = React.createRef();
6  }
7
8  getSnapshotBeforeUpdate(prevProps, prevState) {
9    if (prevProps.list.length < this.props.list.length) {
10      const list = this.chatsList.current;
11      return list.scrollHeight - list.scrollTop;
12    }
13    return null;
14  }
15
16  componentDidUpdate(prevProps, prevState, snapshot) {
17     if (snapshot !== null) {
18      const list = this.chatsList.current;
19      list.scrollTop = list.scrollHeight - snapshot;
20    }
21  }
22
23  render() {
24    return (
25      <div ref={this.chatsList}>{/* ...contents... */}</div>
26    );
27  }
28}
29
30
31

The componentDidUpdate Method

The componentDidUpdate method is the last lifecycle method invoked in the update phase. It allows you to create side effects like sending network requests or calling the this.setState method. It’s important to remember that there should always be a way to avoid the setState (like some sort of logic), or it will result in an infinite loop of re-rendering.

This method can accept up to three parameters: prevProps, prevState, and snapshot (if you implement the getSnapshotBeforeUpdate method).

Here's an example of using componentDidUpdate method for implementing autosave functionality:

1
2class WritePost extends React.Component {
3	constructor(props) {
4		super(props);
5		this.state = {
6			postContent: ""
7		};
8
9		this.handleInput = this.handleInput.bind(this);
10		this.handleSubmit = this.handleSubmit.bind(this);
11	}
12
13	handleInput(e) {
14		this.setState({postContent: this.state.postContent + e.target.value})
15	}
16
17	handleSubmit() {
18		// ...
19	}
20
21	autosave() {
22		// send network request to save post...
23	}
24
25	componentDidUpdate() {
26		this.autosave()
27	}
28
29	render() {
30		return(
31			<form onSubmit={this.handleSubmit}>
32				<input type={text} value={this.state.postContent} onChange={e => handleInput(e)} />
33				<button type="submit">Post</button>
34			</form>
35		)
36	}
37}
38
39

The unmounting phase

The unmounting phase is the third and final phase of a React component. At this phase, the component is removed from the DOM. Unmounting only has one lifecycle method involved: componentWillUnmount.

The componentWillUnmount Method

componentWillUnmount is invoked right before the component is unmounted or removed from the DOM. It’s meant for any necessary clean up of the component, like unsubscribing to any subscriptions (i.e., Redux) or canceling any network requests. Once this method is done executing, the component will be destroyed.

Here's an example of using the componentWillUnmount to unsubscribe from a Redux store:

1
2class MyComponent extends React.Component {
3	constructor(props) {
4		super(props);
5		this.state = {
6			...
7		}
8
9		// whatever bindings...
10	}
11
12	componentDidMount() {
13		const { subscribe } = this.props.store;
14		this.unsubscribe = subscribe(this.forceUpdate);
15	}
16
17	componentWillUnmount() {
18		this.unsubscribe();
19	}
20
21	render() {
22		return (
23			<div>
24				...
25			</div>
26		)
27	}
28}
29
30

React Hooks and the component lifecycle

Versions of React before 16.8 consider two kinds of components based on statefulness: the class-based stateful component, and the stateless functional components (often referred to as a “dumb component”). But with the release of React 16.8, Hooks were introduced and empowered developers to access state from functional components, instead of writing an entire class. With this change, building components became easier and less verbose.

Hooks known as default hooks come with React, and you’re also able to create your own custom hook. A custom hook is just a function that starts with use, like useStore, or useWhatever.

The two most common default hooks are useState and useEffect. The useState hook gives state to the functional component, and useEffect allows you to add side effects within it (like after initial render), which aren’t allowed within the function's main body. You can also act upon updates on the state with useEffect.

React has released more default hooks, but useState and useEffect are the ones you should be most familiar with. Let’s take a look at how they work and compare them to the component lifecycle we covered above.

useState

The useState hook is used to store state for a functional component. This hook accepts one parameter: initialState, which will be set as the initial stateful value, and returns two values: the stateful value, and the update function to update the stateful value. The update function accepts one argument, newState, which replaces the existing stateful value.

Let's say you have this class-based component:

1
2class MyInput extends React.Component {
3  constructor(props) {
4    super(props);
5    this.state = { input: "" };
6
7    this.handleChange = this.handleChange.bind(this);
8  }
9
10  handleChange(e) {
11    this.setState({ input: e.target.value });
12  }
13
14  render() {
15    return (
16      <input
17        type="text"
18        value={this.state.input}
19        onChange={this.handleChange}
20      />
21    );
22  }
23}
24
25

Converting this to a functional component with useState eliminates a lot of code and makes things cleaner and shorter. Here's what the above component looks like with the useState hook. If you’ve read a React tutorial over the past 2 years, chances are you’ve seen some syntax like this.

1import { useState } from 'react';
2
3
4function MyInput(props) {
5	const [input, setInput] = useState('');
6
7	return (
8		<input
9			value={input}
10			onChange={e => setInput(e.target.value)}
11		/>
12	)
13}
14
15

With useState(), whatever you put in the parenthesis is the default state.

The simplicity and clarity of these functional components with Hooks popularized their use among developers who prefer using functional components rather than traditional, class-based ones.

But what if your class-based component’s state object has plenty of items like this:

1
2class MyComponent extends React.Component {
3  constructor(props) {
4    super(props);
5
6    // this component has multiple items in the state object
7    this.state = {
8      count: 0,
9      counterWeight: 1,
10      themeMode: "light"
11    };
12
13    // ...
14  }
15
16  // ...
17
18  render() {
19    return (
20      <div class={this.state.themeMode}>
21      	...
22      </div>
23    );
24  }
25}
26
27

You might be tempted at first to use one useState hook, and build some sort of dictionary to hold all of this state. But React allows you to have multiple useState hooks within a functional component. So instead of one single object, you can set each item of the state object into its own state:

1
2function MyComponent(props) {
3	const [count, setCount] = useState(0);
4	const [counterWeight, setCounterWeight] = useState(1);
5	const [themeMode, setThemeMode] = useState('light');
6
7	// the rest of the code...
8
9	return (
10		<div>
11			...
12		</div>
13	)
14}
15
16

With a lot of different “types” of state this can obviously get verbose, so exercise judgment for larger components.

useEffect

As with the render() method of a class-based component, the main body of a functional component should be kept pure. With the useEffect hook, you're able to create side effects while maintaining the component's purity. Within this hook, you can send network requests, make subscriptions, and change the state value.

The useEffect hook accepts a function as an argument, where you write all your side effects. This function is invoked after every browser paint and before any new renders (this will depend on the dependency array, which is explained in the next paragraph). This function can return another function called the clean-up function, which can be used to clean up the side effects (i.e. when the component is destroyed) like unsubscribing to a store. It’s kind of a mash up of several of the methods explained in the previous section.

This Hook accepts a second argument called the dependency array, which is an array of dependencies like state or props values, which the useEffect uses as reference to only run when these values change. If the dependency array is empty, then the useEffect will only run once, after the first paint.

The dependency array is optional, so if it's not defined, useEffect will fire first when the component is first mounted, and then on every re-render.

Here's an example of a functional component using useEffect that subscribes to a Redux store:

1import { useEffect } from 'react';
2
3
4function MyComponent(props) {
5	// ...
6
7	useEffect(() => {
8		const { subscribe } = props.store;
9		const unsubscribe = subscribe(...);
10		return unsubscribe
11	}, []);
12
13	return 
14}
15
16

This component's useEffect will only run once, since the dependency array is empty. Within the input function, the subscription to the Redux store is invoked, which returns an unsubscribe function. This unsubscribe function is returned, which serves as the clean-up function.

You might have noticed that the useEffect from the above component has similarities to componentDidMount. It holds the side effects, and only runs once, when the component is mounted. The only difference is that it's invoked after the first browser paint, whereas componentDidMount doesn’t wait for that. It’s also important to note that the clean-up function can be compared to the componentWillUnmount, as this function is invoked when the component was destroyed. So again, useEffect() is a sort of hybrid in this sense.

The useEffect hook works similarly to the three lifecycle methods: componentDidMount, componentDidUpdate, and componentWillUnmount. The componentDidMount and componentWillUnmount were discussed above, but what about componentDidUpdate?

If you add dependencies in the dependency array, the function passed into the useEffect hook will run every time the dependencies, like a piece of stateful data, changes. This behavior of the useEffect hook is comparable to the componentDidUpdate method since it’s invoked on every state/props change. The difference is that you can specifically choose what state/props you want useEffect to depend on, rather than the componentDidUpdate which acts upon every state or props change.

Conclusion

In React, a component can enter three different phases that make up its lifecycle. These phases are mounting, updating, and unmounting. Each phase has lifecycle methods invoked, where you can work on different things related to the component, like its props and state, or actually mounting the component to the DOM (with the render method). However, these methods are only for class-based components.

With the release of React 16.8, developers can now write stateful functional components with Hooks, which eliminates a lot of verbosity in a class-based component, and makes the code easier and simpler to write and read. The two most commonly used default hooks are useState and useEffect which are used to handle stateful data in a functional component and for creating side effects within. useEffect works similarly to the three lifecycle methods: componentDidMount, componentDidUpdate, and componentWillUnmount.

Menard Maranan
Menard Maranan
Contributor
Mar 4, 2022
Copied