Mastering the Lifecycle in React with useEffect and useLayoutEffect

Learn how the lifecycle of a React component relates to the use of the useEffect and useLayoutEffect hooks to create more efficient and well-structured applications.

The first things I learned with React were about Hooks, especially useState and useEffect. However, in the beginning, I used them without fully understanding how I should, which led to some issues – especially with useEffect. For example, I once forgot to add dependencies in useEffect, which resulted in an infinite loop.

Back then, no one explained how to avoid these mistakes, so today I want to share some tips on how to use useEffect correctly, especially in the context of the component lifecycle.

In this article, we will explore how useEffect (and, in some cases, useLayoutEffect) can be used to handle the stages of a component’s lifecycle in React: mounting, updating, and unmounting.


First, what is useEffect?

useEffect is one of React’s hooks, and its main function is to handle side effects in functional components. But you might be wondering: What are side effects? Let me explain! Side effects are operations that are not directly related to the rendering process of the component, but are still necessary. Common examples include API calls, manual DOM updates, or even cleanup tasks like canceling requests.

How does useEffect work?
It takes two main parameters:

  • A function that contains the code to be executed when the component is mounted, updated, or unmounted.
  • An optional dependency array that defines when the effect should be executed.
1
2
3
useEffect(() => {
  console.log("The effect has been executed!");
});

So, what is the lifecycle in React?

The lifecycle describes the phases a React component goes through, from its creation to its removal from the DOM. These phases can be divided into three main stages:

  1. Mounting: When the component is added to the screen.
  2. Updating: When something in the component changes, such as state or props.
  3. Unmounting: When the component is removed from the DOM.

Mounting

The mounting stage occurs when the component is added to the page for the first time. Before the introduction of hooks, we used the componentDidMount lifecycle method to execute code right after the component was inserted into the DOM.

With the arrival of useEffect, you can simply pass an empty dependency array to simulate this behavior. The code will be executed only once, after the component is mounted.

1
2
3
4
5
6
7
8
9
import React, { useEffect } from 'react';

function MyComponent() {  
  useEffect(() => {  
    console.log('Component mounted!');  
    // Here you can make requests or set up events.
  }, []);  // No dependencies, executes only on mount.
  return <div>Hello World</div>;  
}  

Updating

A component is updated whenever there is a change in its state (state) or props (props). Before hooks, we used methods like componentDidUpdate to handle this phase. Today, useEffect allows us to monitor changes in specific variables, thanks to the dependency array.

1
2
3
4
5
import React, { useEffect } from 'react';

useEffect(() => {  
  console.log('Component updated!');  
}, [data]); // The useEffect will run every time "data" changes.
  • If we don’t pass a dependency array, useEffect will execute on every update, which can lead to undesirable behavior.

Unmounting

The unmounting stage occurs when the component is removed from the DOM. Previously, we used the componentWillUnmount method to perform cleanup tasks, such as removing event listeners or canceling requests.

Now, we use useEffect with a return statement inside the function, which is automatically executed during unmounting.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import React, { useEffect } from 'react';

useEffect(() => {  
  const timer = setInterval(() => console.log('Executando...'), 1000);  

  return () => {  
    clearInterval(timer);   // Cleanup performed when the component unmounts
    console.log('Component unmounted!');  
  };  
}, []);  

So, useLayoutEffect

As soon as you open the React documentation, you’ll see this warning:

An image showing a warning about the use of useLayoutEffect, alerting that excessive use may harm the applications performance.

In other words, use it with caution. With that in mind, let’s explore what useLayoutEffect is!!

useLayoutEffect is a React hook similar to useEffect, even in how it is declared. However, there are important differences in when the code is executed. It is used to run synchronous effects before the browser performs the screen painting. This means any changes made to the DOM within useLayoutEffect will be applied immediately, ensuring the user sees the updates before the next visual frame.

When should you use it?

  • Measuring or modifying the DOM layout: For example, calculating the size or position of an element before the browser renders the page.
  • Synchronizing states or styles directly with changes in the DOM: Especially when updates rely on precise measurements.

And within the Lifecycle? Where does it fit?
Within the React lifecycle, useLayoutEffect primarily fits into the mounting and updating phases. It makes the most sense in these stages if you need to ensure that changes to the DOM are applied before the screen is painted. Below, you’ll find a more detailed explanation of its use during these phases.

1. Mounting:

  • When the component is mounted, useLayoutEffect is executed after rendering but before the screen is painted. This is useful when you need to adjust the layout or measure DOM elements (such as sizes or positions) before the user sees any changes.

2. Updating:

  • During component updates (when state or props change), useLayoutEffect is also called after rendering but before the screen is painted. If you need to perform actions on the DOM (like recalculating an element’s size or updating a style) based on state changes, useLayoutEffect ensures the layout is adjusted before the screen is re-rendered.

Common Pitfalls and Best Practices

  1. Avoid effects without dependencies: A useEffect without dependencies will run on every update, which can lead to infinite loops or performance degradation.

  2. Remember cleanup on unmount: Always return a cleanup function from useEffect to avoid unexpected behavior, such as intervals or event listeners not being removed.

  3. Use the dependency array correctly: Ensure that all variables used within useEffect are listed as dependencies. ESLint can help with this.

  4. Excessive or unnecessary use: useLayoutEffect is heavier than useEffect, as it runs synchronously after all DOM mutations. This can impact performance, especially in components with many renders. Use it only when truly necessary.

  5. Prioritize useEffect whenever possible: I might be repeating myself, but it’s important, I promise! Most of the time, useEffect is the ideal choice for most asynchronous operations. Use useLayoutEffect only when you need to interact with the DOM layout synchronously.


Final Thoughts and Next Steps

Understanding the lifecycle of a React component is essential, both to know what you’re doing and to create robust and efficient applications. The UseEffect and useLayoutEffect hooks are great allies in this process, but it’s important to know when to use each and avoid common pitfalls. By following best practices, you ensure that your components are well-structured and easy to maintain. And if you’re ever unsure about something, your best friend will always be the documentation.

Learn more about React: https://react.dev/

How about you, do you have any tips or questions about the lifecycle? About UseEffect or useLayoutEffect? Leave a comment and let’s exchange ideas!

Built with Hugo
Theme Stack designed by Jimmy