DEV Community

Cover image for React Lifecycle Methods
Khushboo Tolat Modi
Khushboo Tolat Modi

Posted on

React Lifecycle Methods

In React, lifecycle methods are special methods that get called at different stages of a component's existence. They allow you to control what happens to a component at various stages like mounting, updating, or unmounting. With the introduction of React Hooks in React 16.8, functional components can also manage their own side effects, but lifecycle methods are still important in class components. Here’s a detailed look at the most commonly used lifecycle methods:

Mounting

The mounting phase in React refers to the process where a component is created and inserted into the DOM. This phase involves a series of lifecycle methods called in a specific order, allowing developers to initialize and configure the component before it is rendered. Here’s a detailed breakdown of each method in the mounting phase in order of their execution:

1. constructor(props)

Purpose:

  • The constructor is the first method called when a component instance is created. It is used to initialize the component’s state and bind event handlers.

  • In the constructor, you can set the initial state by directly assigning an object to this.state. You also typically pass props to the base Component class using super(props) to ensure the component is properly initialized.

Example:

Image description

Notes:

  • The constructor is only called once during the component's lifecycle.

  • You should avoid side effects in the constructor (e.g., network requests or subscriptions) and reserve those tasks for componentDidMount.

2. static getDerivedStateFromProps(props, state)

Purpose:

  • This is a static method that is called right before rendering, both during the initial mount and during updates. It allows the component to update its state based on changes in props.

  • It returns an object to update the state or null if no state updates are needed.

Example:

Image description

Notes:

  • This method is rarely needed, as React's data flow is usually handled by passing props directly.

  • It's used in special cases where state needs to be derived from props.

3. render()

Purpose:

  • The render method is the only required lifecycle method in a class component. It determines what the UI of the component should look like by returning React elements.

  • This method is pure, meaning it should not modify the component state or interact with the DOM.

Example:

Image description

Notes:

  • The render method can return a variety of values, including JSX elements, arrays, fragments, portals, strings, numbers, or null.

  • Since render is pure, avoid side effects or state mutations within this method.

4. componentDidMount()

Purpose:

  • This method is invoked immediately after a component is mounted (i.e., inserted into the DOM). It’s the perfect place to run side effects, such as fetching data from an API, setting up subscriptions, or initializing third-party libraries.

  • componentDidMount is the last method called in the mounting phase, making it ideal for any DOM manipulations.

Example:

Image description

Notes:

  • Since componentDidMount is called after the initial render, updating the state within it will trigger a re-render. This is common when fetching data or interacting with the DOM.

  • If you set up subscriptions or event listeners here, remember to clean them up in componentWillUnmount to avoid memory leaks.

Updating

The updating phase in React refers to the process when a component is re-rendered due to changes in its state or props. During this phase, several lifecycle methods are invoked in a specific order, allowing you to control how your component reacts to these changes. Here's a detailed look at each method involved in the updating phase in order of their execution:

1. static getDerivedStateFromProps(props, state)

Purpose:

  • This static method is called right before rendering the component when new props or state are received. It allows the component to update its internal state based on changes in props.

  • It returns an object that updates the state or null if no updates are needed.

Example:

Image description

Notes:

  • This method is useful in scenarios where the state needs to be synchronized with props.

  • It is called on every update, so avoid heavy computations here.

2. shouldComponentUpdate(nextProps, nextState)

Purpose:

  • This method is called before rendering when new props or state are received. It allows you to control whether the component should update or not. Returning true (default) means the component will update; returning false means it will not.

  • It is mainly used to optimize performance by preventing unnecessary re-renders.

Example:

Image description

Notes:

  • This method is not called during the initial render or when forceUpdate() is used.

  • Avoid complex logic here, as it can lead to performance issues or bugs if not handled carefully.

3. render()

Purpose:

  • The render method is called to produce the next version of the virtual DOM based on the component's current state and props.

  • It’s pure, meaning it should not modify component state or interact with the DOM.

Example:

Image description

Notes:

  • Since render is pure, any state or prop changes should be reflected in the returned JSX.

  • Avoid side effects (like modifying the DOM directly or making network requests) within render.

4. getSnapshotBeforeUpdate(prevProps, prevState)

Purpose:

  • This method is called right before the changes from the virtual DOM are actually reflected in the real DOM. It allows you to capture some information (like the current scroll position) before it is potentially changed.

  • The value returned from this method is passed as a third argument to componentDidUpdate.

Example:

Image description

Notes:

  • This method is particularly useful for capturing information about the DOM before it changes, such as maintaining scroll position during updates.

  • It is often used together with componentDidUpdate.

5. componentDidUpdate(prevProps, prevState, snapshot)

Purpose:

  • This method is invoked immediately after updating occurs. It’s a good place to perform any DOM operations, network requests, or other side effects based on the update.

  • It receives the previous props and state, as well as the value returned by getSnapshotBeforeUpdate (if any).

Example:

Image description

Notes:

  • This method is useful for performing operations that need to happen after the DOM has been updated.

  • Avoid setting state within componentDidUpdate unless it's wrapped in a condition to prevent an infinite loop of updates.

Unmounting

The unmounting phase in React occurs when a component is being removed from the DOM. This phase has a single lifecycle method that allows you to perform any necessary cleanup tasks before the component is destroyed. Proper handling of this phase is crucial to prevent memory leaks, dangling event listeners, or other side effects that could persist after the component is removed.

1. componentWillUnmount()

Purpose:
componentWillUnmount is invoked immediately before a component is unmounted and destroyed. This method is used for cleanup activities, such as:

  • Canceling network requests.

  • Clearing timers or intervals.

  • Removing event listeners.

  • Cleaning up subscriptions (e.g., from Redux, WebSockets, etc.).

  • Invalidating or cleaning up any side effects created in componentDidMount or other lifecycle methods.

Example:

Image description

In this example:

  • A timer is started when the component mounts (componentDidMount).

  • The timer is cleared in componentWillUnmount to ensure it doesn’t continue running after the component is removed from the DOM. This is crucial to prevent potential memory leaks or unexpected behavior.

Key Considerations:

  • Preventing Memory Leaks: If you set up event listeners or intervals in componentDidMount, you must remove them in componentWillUnmount to prevent memory leaks. Failing to do so could lead to your application consuming more memory over time or behaving unexpectedly.

  • Cleaning Up Subscriptions: If your component subscribes to external data sources (like Redux stores, Firebase, WebSocket connections, etc.), you should unsubscribe in componentWillUnmount. This ensures that your component no longer reacts to updates from those sources after it has been removed.

  • No setState: Since the component is about to be destroyed, you should not call setState within componentWillUnmount. Doing so will have no effect because the component will not re-render.

  • Asynchronous Cleanup: If your cleanup involves asynchronous operations (like canceling a network request), ensure that those operations are properly handled to avoid race conditions or trying to interact with a component that no longer exists.

Common Use Cases:

  • Timers and Intervals: Clearing setTimeout or setInterval instances to avoid them running after the component is unmounted.

  • Event Listeners: Removing event listeners attached to the window, document, or any DOM element to prevent them from firing after the component is unmounted.

  • Subscriptions: Unsubscribing from data streams or external services (e.g., WebSockets, Firebase, Redux stores).

  • Network Requests: Cancelling ongoing network requests if the component is unmounted before the request completes. This can be done using libraries like Axios, which provide cancellation tokens.

Best Practices:

  • Always clean up side effects in componentWillUnmount if they were set up in componentDidMount or any other lifecycle method.

  • Be mindful of asynchronous operations to ensure they don’t inadvertently interact with a component that has been unmounted.

  • Avoid any logic that assumes the component will continue to exist after componentWillUnmount is called.

Error Handling

The error handling phase in React is designed to catch and handle errors that occur during rendering, in lifecycle methods, and in constructors of the whole tree below a component. This is accomplished using special lifecycle methods in class components known as error boundaries.

Error Boundaries Overview

  • Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of crashing the entire application. This makes the app more resilient by preventing errors from propagating to the root of the application.

1. static getDerivedStateFromError(error)

Purpose:

  • This static method is called when an error is thrown during the rendering phase, in a lifecycle method, or in a constructor of any child component.

  • It allows you to update the state so that the next render will show a fallback UI.

Usage:

  • The method receives the error that was thrown as a parameter and returns an object that updates the component's state.

  • By setting the state in this method, you can render a fallback UI that informs the user something went wrong.

Example:

Image description

Notes:

  • This method allows you to control what is rendered when an error occurs. For example, you might choose to render a generic error message or a custom error component.

  • It’s typically used to set an error state that can trigger the rendering of a fallback UI.

2. componentDidCatch(error, info)

Purpose:

  • This method is called after an error has been thrown by a descendant component. It’s used for logging error information or performing side effects like reporting the error to an error tracking service.

  • Unlike getDerivedStateFromError, this method can be used to capture additional details about the error and the component stack trace where the error occurred.

Usage:
The method receives two arguments:

  • error: The error that was thrown.

  • info: An object with a componentStack property that contains a string with information about which component threw the error.

Example:

Image description

Notes:

  • componentDidCatch is particularly useful for logging errors or sending them to a monitoring service (e.g., Sentry, LogRocket).

  • While getDerivedStateFromError helps with rendering a fallback UI, componentDidCatch focuses on capturing and logging error details.

How to Use Error Boundaries

  • Wrapping Components: Error boundaries can be used to wrap any component or set of components. This can be done globally (e.g., around the entire application) or more selectively (e.g., around components that are more prone to errors).

Example:

Image description

In this example, ErrorBoundary wraps MyComponent. If MyComponent or any of its children throw an error, the ErrorBoundary will catch it and display the fallback UI.

Key Considerations:

Error Boundaries Catch Errors in the Following Scenarios:

  • During rendering.

  • In lifecycle methods (including those in class components).

  • In constructors of child components.

Error Boundaries Do Not Catch Errors in the Following Scenarios:

  • Event handlers (errors can be caught using try/catch blocks within the event handler itself).

  • Asynchronous code (e.g., setTimeout, requestAnimationFrame).

  • Server-side rendering.

  • Errors thrown in the error boundary itself (though you can nest error boundaries to catch such errors).

Best Practices:

  • Use error boundaries to prevent the entire app from crashing due to small, isolated errors.

  • Place error boundaries around potentially error-prone parts of your app, such as third-party components or components handling complex logic.

  • Ensure that error boundaries provide a user-friendly fallback UI, informing the user that something went wrong.

Understanding these lifecycle methods will help you better manage state, props, and side effects in React components.

Lifecycle methods in Functional Component

In functional components, React’s useEffect hook is the primary way to handle side effects and lifecycle methods. The useEffect hook can replicate behaviors similar to class component lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount. Here's a detailed breakdown of how useEffect works and how it relates to these lifecycle methods.

Basic Syntax

Image description

  • First Argument (effect): A function where you place your side effect logic. This function can return a cleanup function to clean up resources when the component unmounts or before the effect is re-run.

  • Second Argument (dependencies): An array of dependencies that determine when the effect should be re-run. If any of the values in this array change, the effect is triggered again.

useEffect as componentDidMount

To replicate the behavior of componentDidMount (which runs once after the component is mounted), you can use useEffect with an empty dependency array [].

Example:

Image description

  • Behavior: The effect runs only once after the initial render, similar to componentDidMount in class components.

useEffect as componentDidUpdate

To mimic componentDidUpdate, you use useEffect with dependencies. The effect will run whenever any of the dependencies change.

Example:

Image description

  • Behavior: The effect runs after each render where the dependencies (count or someProp) change, just like componentDidUpdate.

useEffect as componentWillUnmount

To replicate componentWillUnmount, you return a cleanup function from useEffect. This cleanup function will be executed when the component unmounts or before the effect re-runs.

Example:

Image description

  • Behavior: The cleanup function runs when the component is about to unmount, similar to componentWillUnmount.

Handling All Three Lifecycle Methods in One useEffect

In many cases, a single useEffect can handle the component's mounting, updating, and unmounting phases. Here's an example that demonstrates this:

Example:

Image description

  • Mounting: The effect runs once on the initial render.

  • Updating: The effect runs whenever someProp changes.

  • Unmounting: The cleanup function runs when the component unmounts or before the effect re-runs due to a dependency change.

Controlling the Execution Frequency of useEffect

The behavior of useEffect is controlled by the dependency array:

  • No Dependency Array: The effect runs after every render.

  • Empty Dependency Array []: The effect runs only once, after the initial render (mimicking componentDidMount).

  • Specific Dependencies: The effect runs whenever any of the specified dependencies change.

  • Example: Without Dependency Array

Image description

  • Behavior: The effect runs after every render (initial and updates).

Common Pitfalls and Best Practices

  • Avoid Missing Dependencies: Always include all state and props that are used inside useEffect in the dependency array to avoid stale closures and bugs.

  • Multiple useEffect Calls: It’s common and recommended to use multiple useEffect hooks in a single component, each responsible for a specific side effect. This keeps the code more modular and easier to manage.

  • Cleanup: Always consider cleanup for effects that involve subscriptions, timers, or any other resource that should be released when the component unmounts or the effect is re-triggered.

Understanding and using useEffect effectively allows you to manage side effects in a clean, predictable way within functional components, providing the same capabilities that class components have with lifecycle methods.

Top comments (2)

Collapse
 
brense profile image
Rense Bakker

useEffect is not a replacement for lifecycle methods. useEffect always runs after the component has fully rendered to the screen. React class components and acompanying lifecycle methods are deprecated and should no longer be used. useEffect is for handling side effects that happen outside of the React scope. Or in other words, its a bridge between React and something that happens outside of React.

Collapse
 
khushboo-tolat profile image
Khushboo Tolat Modi

Thanks for yoyr insight !!