# Lifecycle Methods

React has changed a lot over the past few years and it’s lifecycle methods are no exceptions. In fact, with the release of hooks, the idea of lifecycles were shaken once more!

React hooks can often be seen as a way to simplify the lifecycle process. While some people prefer funcitonal components and hooks, we also need to be comfortable with class lifecycle, so we will cover each.

Please read the official docs (opens new window)

As you can see in the following diagram, there are 3 main categories: Mounting, Updating and Unmounting.

Lifecycle

# Mounting

Mounting covers the adding the element to the tree, getting any derived state that it may have, and ultimately the first-pass rendering of the component.

# constructor

Typically, in React constructors are only used for two purposes:

  • Initializing local state by assigning an object to this.state.
  • Binding event handlers to a component instance.

TIP

You should not call setState() in the constructor(). Instead, if your component needs to use local state, assign the initial state to this.state directly in the constructor.

class Component {
	constructor(props) {
		super(props); //always call this
		setState({ name: 'Leni' }); //never do this
		this.state = { name: 'Leni' }; //do this instead
	}
}

Like almost all other lifecycle methods (excluding Render), the constructor is optional. If you don’t need to set initial state or bind event listeners, don’t define a constructor.

# getDerivedStateFromProps

getDerivedStateFromProps is a static method that is invoked right before calling Render. This method is intended to be used when you want to set state based off of the value of some props. You may be asking, can’t I just do that in the constructor? You can, however, the constructor is only called once. In the chart above, you’ll see that getDerivedStateFromProps is also called in the Updating category as well.

This lifecycle method is optional, but if you define it, the method should return an object that represents state or null if there are no updates to the state before render.

class Component {
	static getDerivedStateFromProps(props, state) {
		if (props.animate) {
			return { ...state, x: state.x + 50 };
		} else {
			return null;
		}
	}
}

# render

Every component needs to have this lifecycle method defined. render is the function that returns what the component displays, and can actually return quite a few types of objects.

For example, render can return React Elements (typically created with JSX). It can return Arrays or Fragments (multiple elements from one render). Lastly, it can return strings and numbers (rendered as text nodes) or booleans and nulls (render nothing).

The render() function should be pure, meaning that it does not modify component state. Additionally, it should return the same result each time it’s invoked, and it does not directly interact with the browser (or window).

class Component {
	render() {
		return <h1>Hello! 👋</h1>;
	}
}

In the “Updating” category, it’s worth noting that render won’t be invoked if shouldComponentUpdate returns false.

Once the render call succeeds, React will update the DOM and any refs (opens new window) that you’ve defined.

# componentDidMount

This is the last method in our “Mounting” category. This lifecycle method is simply called whenever that component has successfully mounted.

TIP

One common use case for this lifecycle method is to load data from a remote endpoint (make an HTTP request).

class Component {
	componentDidMount() {
		fetch('http://example.com/movies.json').then((response) => {
			this.setState({ movies: response.json() });
		});
	}
}

# Updating

We’ve covered some of the lifecycle methods that also trigger during an update (render, getDerivedStateFromProps), but there’s a lot we haven’t yet.

An update can be triggered by a couple of different scenarios. Most notably, when the props to the component change, when the state changes, or when forceUpdate() is called. The props and state changes are fairly similar, but forceUpdate() circumvents some lifecycle methods.

When an update happens, getDerivedStateFromProps is triggered once more.

# shouldComponentUpdate

shouldComponentUpdate is our next lifecycle method. If you don’t need this check, you can omit this method entirely. If you do decide to include it, however, it simply needs to return true or false. true indicates a re-render needs to occur, false indicates that no re-render is required by the component.

This function receives two parameters, the next props and the next state.

class Component {
	componentShouldUpdate(newProps, newState) {
		return this.state.someKey !== newState.someKey;
	}
}

# getSnapshotBeforeUpdate

This lifecycle method enables your component to capture some information from the DOM before it is potentially changed. Any value returned by this lifecycle will be passed as a parameter to componentDidUpdate().

A somewhat common example might be scroll position in a chat application. As new state (messages) come in, we want to be able to retain the current scroll position.

class Component {
	getSnapshotBeforeUpdate(prevProps, prevState) {
		if (prevState.list.length < this.state.list.length) {
			const list = this.listRef.current;
			return list.scrollHeight - list.scrollTop;
		}
		return null;
	}
}

# componentDidUpdate

Last in our “Update” category, we have componentDidUpdate. This lifecycle method triggers whenever a component has successfully updated.

You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition, or you’ll cause an infinite loop.

class Component {
	componentDidUpdate(prevProps) {
		if (prevProps.user.id != this.props.user.id) {
			loadUserData(this.props.user.id).then((user) => {
				this.setState({ userData: user });
			});
		}
	}
}

# Unmounting

This method triggers before a component is unmounted and destroyed. This is where you’ll cancel any timers, cancel any HTTP requests, remove any subscriptions in RxJS or similar. If you have an asynchronous operation that set’s state and that isn’t canceled by the componentWillUnmount method, you will see development warnings about setting state on an unmounted component (warning you that its an indicator of a memory leak).

# Hooks

Hooks are used with functional components. Often times, functional components are simpler than the class counter-parts and promote composition rather that inheritance to manage lifecycle methods.

We still have three main categories for our lifecycles: Mounting, Updating, and Unmounting, but our access to the direct lifecycle methods has been removed. Instead, we manage similar operations through the use of hooks.

# useEffect

useEffect is our solution to most of the lifecycle methods. Different configurations for that hook determine how and when it should run.

It simply takes in a function that should run and a dependency array that determines how often to run that function. This configuration can map directly to lifecycle methods.

# Replacing constructor

If, for some reason, you find yourself needing to port over constructor logic, you can simply do so in the function component body. If, for example, we were to take the following component:

class Component {
	constructor() {
		this.state = { food: 'tacos' };
	}
}

We can port it over to function components and hooks like so:

const Component = () => {
	[food, setFood] = useState('tacos');
};

# Replacing componentDidUpdate

We can add a depedency array to the hook as a second parameter to determine how often that hook’s function should be called. Since there’s no dependency array provided in the above example, this function will run on every render. In another example, if we want to call our function every time the prop user has changed, we can write something like this.

Lets take the example from componentDidUpdate section and transform translate that into hooks.

const Component = ({ user }) => {
	const [userData, setUserData] = useState(null);
	useEffect(() => {
		loadUserData(user.id).then((user) => setUserData(user));
	}, [user.id]);
};

This will only run once the user id is changed.

# Replacing componentDidMount

So an empty array will only run once. This can be used as a replacement for componentDidMount. Lets translate our example to hooks again.

const Component = () => {
	const [movies, setMovies] = useState(null);

	useEffect(() => {
		fetch('http://example.com/movies.json').then((response) => {
			setMovies(response.json);
		});
	}, []);
};

Since our dependency array is empty, our diff will never change (an empty list’s contents will always be an empty list!) which means our function will only run once.

# Replacing componentWillUnmount

The return function from useEffect is called when componentWillUnmount would normally be called. This means that it should handle canceling timers, http requests, or unsubscribing from observables just like it would in componentWillUnmount.

const Component = () => {
	useEffect(() => {
		return () => {
			// canceling logic here..
		};
	}, []);
};

Here (opens new window) you can see a full example from official docs.