useLayoutEffect vs useEffect with examples

useLayoutEffect vs useEffect with examples

·

14 min read

Table of Contents

In this article, we are going to learn about useEffect vs useLayoutEffect.

Introduction

II. useEffects

III. useLayoutEffect

IV. Difference between useLayoutEffect and useEffect

V. Examples: When to use useEffect and useLayoutEffect

VI. Best Practices and When to use useLayoutEffect and useEffect

VII. Conclusion

  • Thank you for reading

React Hooks: An Introduction

React hooks are simple JavaScript functions that enable developers to use state and other react features in functional components

They encapsulate re-useable code in functions, that can be added in components to introduce functionality that was previously available in class components into functional components

Some common hooks include useState , useEffect, useContext and useLayoutEffect

The hooks enable developers to handle side effects, manage local state and read and access context

This article is provided by DeadSimpleChat Chat API for your website and app

By using Hooks developers can create a more readable, concise and maintainable codebase

Side Effects in React: useEffect and useLayoutEffect

Components need to synchronize with external systems, like setting up a server connection, controlling an external library based on react state etc

Here are some of the reasons why managing side effects is important in react

  1. Ability to predict and maintain code: Properly handling effects results in fewer bugs and more reliable code, thus app behaves predictably and it is easier to maintain as well

  2. Ability to Optimize performance: Proper management of effects results in optimization of performance and resource utilization

  3. Cleanup functionality: With proper effects hook you can easily write the code to clean up as well after the effects have taken place

  4. Synchronization: With effects hooks, you can synchronize component lifecycle events with effects.

useEffect and useLayoutEffect are two react hooks that provide a way to perform side effects, sync with component lifecycle and clean up after the effect has taken place

useEffect

useEffect is a hook that lets you handle side effects in react.

Components need to connect to the network, call an API or sync with a third-party library or other asynchronous operations.

All of these are not controlled by react and hence are called external systems

useEffect is used to connect, sync and control external systems. useEffect runs after the component has rendered on the screen and it can be setup to re-run on every render or when some dependencies change

useLayoutEffect

useLayoutEffect is similar to useEffect but it runs synchronously after all the DOM but before the browser has rendered anything on the screen

You can use this hook to measure or manipulate the DOM and want to ensure the changes are made before the browser repaints

Understanding useEffect

React useEffect is a react hook that lets you perform side effects in react. With useEffect you can connect to external systems like connect to the network or server, a third party library.

Syntax and Basic usage

useEffect(() => {
  // write logic to handle side effect like callling an API /connecting to a server or connecting to a third party library here

  return () => {
    // You can create a cleanup function to clean up after the effect has taken place
  };
}, [dependencyA, dependencyB]);

useEffect has the following

  1. A function that contains the logic to handle side effect

  2. An optional dependency array, that has dependencies listed, When the dependencies change, the effect reruns

  3. An optional cleanup function that cleans up after the effect has taken place

Example: Fetching data with useEffect

In this example, we are going to fetch some data using an API and the useEffect hook

import React, { useState, useEffect } from 'react';

function GetDetails() {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function getInfo() {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
      const data = await response.json();
      setData(data);
      setLoading(false);
    }

    getInfo();
  }, []); // If you want the code to run only once when mounted leave the dependency array empty

  return (
    <div>
      {loading ? <p>searching...</p> : <ul>{data.map(item => <li key={item.id}>{item.title}</li>)}</ul>}
    </div>
  );
}

Clean Up function

As I have already stated the clean-up function is an optional function that can be returned from the useEffect

It is used to perform clean-up after the effect has taken place, this might include unsubscribing from events, or releasing resources.

Here is an example of using a clean-up function to unsubscribe from a WebSocket connection

import React, { useState, useEffect } from 'react';

function SocketConnection() {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket('wss://demo.piesocket.com/v3/channel_123?api_key=VCXCEuvhGcBDP7XhiJJUDvR1e1D3eiVjgZ9VRiaV&notify_self');

    socket.onmessage = (event) => {
      setMessages((prevMessages) => [...prevMessages, event.data]);
    };

    return () => {
      socket.close();
    };
  }, []); // To run the effect only once during mounting leave the dependency array empty

  return (
    <ul>
      {messages.map((message, index) => (
        <li key={index}>{message}</li>
      ))}
    </ul>
  );
}

Clean up function

Dependency Array

It is an optional second argument in useEffect. It is an array of dependencies, when these dependencies change it triggers a re-run of the effects

If the empty array is provided the effect will run only once when the component mounts

If the array is omitted then the effects run every time the component is rendered

let us consider an example:

import React, { useState, useEffect } from 'react';

function UserData({ userId }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch(`https://api.example.com/users/${userId}`);
      const data = await response.json();
      setData(data);
    }

    fetchData();
  }, [userId]); // Effect runs whenever the userId prop changes

useEffects

uselayoutEffect

useLayoutEffect is a react hook similar to useEffect with function that has a the side effect logic, a dependency array and an optional callback function can be returned as well

The difference between useLayoutEffect and useEffect is that useLayoutEffect runs synchronously after all the DOM mutations and before the browser repaints

This hook can be used to measure and manipulate the DOM and when we want to ensure that the changes have been made before the browser repaints. This involves use-cases which might cause flickering or layout jumps

The useLayoutEfffect should be used when necessary because it can cause performance issues, because of its synchronous nature

Syntax

useLayoutEffect has a similar syntax as useEffect

useLayoutEffect(() => {
  // Some effect logic goes here

  return () => {
    // optional clean up function
  };
}, [dependencyA, dependencyB]);

Example: using useLayoutEffect to synchronise DOM changes

In this example, we are going to update the position of the tooltip based on the position of the target element

import React, { useRef, useLayoutEffect } from 'react';

function ToolTipLocator({ targetRef, children }) {
  const tooltipRef = useRef();

  useLayoutEffect(() => {
    const targetRect = targetRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();

    tooltipRef.current.style.left = `${targetRect.left + (targetRect.width - tooltipRect.width) / 2}px`;
    tooltipRef.current.style.top = `${targetRect.top - tooltipRect.height}px`;
  }, []);

  return (
    <div ref={tooltipRef} className="tipOfTheTool">
      {children}
    </div>
  );
}

DOM changes in useLayoutEffect

In this example we are using the useLayoutEffect to update the tooltip's position with respect to the target element

Clean Up function in useLayoutEffect

Clean up function is exactly similar to the useEffect, so you can just reference it in from above. Just replace useEffect with useLayoutEffect

Dependency array in useLayoutEffect

Dependency Array is exactly similar to the useEffect, so you can just reference it from above. Just replace useEffect with useLayoutEffect

Difference between useLayoutEffect and useEffect

Asynchronous vs synchronous timing of execution

The core difference between useLayoutEffect and useEffect is the timing of the execution of code

while the useEffect is asynchronous the useLayoutEffect is synchronous

  • useEffect: The useEffect runs after the component has been rendered and is an asynchronous hook, primarily used to handle external systems like api calls or integration with third-party library

  • useLayoutEffect : The useLayoutEffect is a synchronous hook, that runs after all the DOM mutations but before the browser layout. It is mainly used for UI and it may cause performance issues as it blocks the browser from painting the UI until the effects have taken place. It is used to avoid issues like flickering or jumps in the UI

Use-Case for useLayoutEffect and useEffect

Here are some of the common use-cases for both of these hooks

useEffect

  • Connecting to a remote server

  • Fetching data from an API

  • Updating the state in response to prop changes

  • connecting to external third-party libraries

  • Performing other asynchronous operations

useLayoutEffect

  • When state changes updating the DOM

  • Preventing flickering or UI Jumps during DOM manipulations

  • Sync DOM changes with other components or UI elements

Examples: When to use useLayoutEffect and when to use useEffect

Example 1: Updating the DOM based on state changes

useEffect

In this example we have a component that displays a list of items and a toggle button to show/ hide the visibility of the list

We can use useEffect to update the visibility of the list based on the state changes

import React, { useState, useEffect } from 'react';

function TogglableList() {
  const [isVisible, setIsVisible] = useState(true);

  useEffect(() => {
    const listElement = document.getElementById('user-list');

    if (isVisible) {
      listElement.style.display = 'block';
    } else {
      listElement.style.display = 'none';
    }
  }, [isVisible]);

  return (
    <div>
      <button onClick={() => setIsVisible(!isVisible)}>Show / Hide list</button>
      <ul id="user-list">
        {/* List of items goes here */}
      </ul>
    </div>
  );
}

Here we are using the useEffect to update the show/hide property of the list using the isVisible state

Here DOM update is not critical to the calculations of layout or to asynchronous operation with useEffect hook

useLayoutEffect

Now, let us consider an example where visibility changes require a synchronous operation and we need to use useLayoutEffect

Consider a component that measures the height of a panel that can collapse and we are animating the height based on the isExpanded state

import React, { useState, useLayoutEffect } from 'react';

function PanelThatCanCollapse({ children }) {
  const [isExpanded, setIsExpanded] = useState(false);

  useLayoutEffect(() => {
    const panelElement = document.getElementById('collapsible-panel');

    if (isExpanded) {
      const heightOfThePanel = panelElement.scrollHeight;
      panelElement.style.height = `${heightOfThePanel}px`;
    } else {
      panelElement.style.height = '0';
    }
  }, [isExpanded]);

  return (
    <div>
      <button onClick={() => setIsExpanded(!isExpanded)}>Show / Hide</button>
      <div id="collapsible-panel" className="collapsible-panel">
        {children}
      </div>
    </div>
  );
}

height adjustment using useLayoutState

Here we are using the useLayoutEffect because the useLayoutEffect synchronously updates the height of the panel before the browser paints

We need the height update to be made synchronously because otherwise it would break the UI for a little time and that would be a bad experience for the user.

In short, where the UI would break we need synchronous effect resolution thus we use useLayoutEffect other wise we use useEffect

Example 2: Updating the DOM and Measuring the DOM

We are considering an example where we will measure the DOM and update the DOM based on the measurements

useEffect

We have a component which displays a notification, we need to center it horizontally in the screen.

We can use useEffect to measure the length of the notification bar and update its position to center it on the screen

import React, { useState, useEffect } from 'react';

function Toast({ toastMessage }) {
  const [leftPosition, setLeftPosition] = useState(0);

  useEffect(() => {
    const toastElement = document.getElementById('toast');
    const toastWidth = toastElement.offsetWidth;
    const screenWidth = window.innerWidth;

    setLeftPosition((screenWidth - notificationWidth) / 2);
  }, [message]);

  return (
    <div id="toast" className="toast" style={{ left: leftPosition }}>
      {toastMessage}
    </div>
  );
}

center the toast in the center of the screen

In this example we are using useEffect to measure the width of the toast notification based on the width of the screen.

Since measurement and updating the position are not critical to rendering the UI on the page we can use useEffect to manage this.

useLayoutEffect

In this example we have a tolltip next to an input field, and we want to ensure that the tooltip is propperly positioned before the browser repaints

import React, { useRef, useLayoutEffect } from 'react';

function InputTooltip({ textOfTooltip }) {
  const inputRef = useRef();
  const tooltipRef = useRef();

  useLayoutEffect(() => {
    const inputRect = inputRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();

    tooltipRef.current.style.left = `${inputRect.right + 20}px`;
    tooltipRef.current.style.top = `${inputRect.top + (inputRect.height - tooltipRect.height) / 2}px`;
  }, [TextOfTooltip]);

  return (
    <div>
      <input ref={inputRef} type="text" />
      <div ref={tooltipRef} className="tooltip">
        {textOfToolTip}
      </div>
    </div>
  );

In this example we are using useLayoutEffect to synchronously position the tooltip next to the input field

We are using useLayoutEffect because we need the tooltip exactly next to the input field when the page renders on the screen, if the tooltip comes at a later point in time it might cause issues like UI jumps.

Example 3: Fetching user data with useEffect

We are building a component that fetches the user data from an API when the component mounts

import React, { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [userData, setUserData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchData() {
      const response = await fetch(`https://jsonplaceholder.typicode.com/users`);
      const data = await response.json();
      setUserData(data);
      setLoading(false);
    }

    fetchData();
  }, [userId]);

  return (
    <div>
      {Searching ? (
        <p>Searching...</p>
      ) : (
        <div>
          <h1>{userData.name}</h1>
          <p>Email: {userData.email}</p>
          {/* list of user details here */}
        </div>
      )}
    </div>
  );
}

Here we are using the useEffect to fetch the user data from the JSON placeholder website and render it when the component mounts.

We have also added userId as a dependency, so when ever the userId gets upadated the effects run again thus calling the API again to update the user details

we are also displaying the user data or a seaching indicator based on the state of the component at that point in time

Example 4: Sync the height of 2 elements with the help of useLayoutEffect

We have 2 elements on a page and we want to make their heights in sync with each other such that they are always equal

import React, { useRef, useLayoutEffect } from 'react';

function InSyncHeight({ leftColumn, rightColumn }) {
  const leftRef = useRef();
  const rightRef = useRef();

  useLayoutEffect(() => {
    const leftHeight = leftRef.current.offsetHeight;
    const rightHeight = rightRef.current.offsetHeight;

    if (leftHeight > rightHeight) {
      rightRef.current.style.height = `${leftHeight}px`;
    } else {
      leftRef.current.style.height = `${rightHeight}px`;
    }
  }, [leftColumn, rightColumn]);

  return (
    <div className="InSyncHeight">
      <div ref={leftRef} className="column-left">
        {leftColumn}
      </div>
      <div ref={rightRef} className="column-right">
        {rightColumn}
      </div>
    </div>
  );
}

Here we are using useLayoutEffect to measure the height of both the colums and then set the height of the smaller column to match the height of the taller column

we have also added both the column variables in the dependency array such that whenver the height of any of these colums changes then the useLayoutEffect will run again

Best Practices and When to use useLayoutEffect and useEffect

When to choose useLayoutEffect and useEffect

useEffect runs asynchronous and does not cause any performance issues and thus should be the default choice whenever you want to deal with external systems such as

  • calling an API

  • updating DOM based on state changes

  • connecting with a remote server

  • connecting with third party library

Only for things that might cause an UI issue such as flickerring or UI jumps if the operation or connection with the external system is performed asynchronously then useLayoutEffect should be used

use the useLayoutEffect in the following scenarios

  • When measuring or manipulating the DOM you need to make sure that Changes are made before the browser repaints

  • Effects must be in sync with the component rendering to avoid momentary breaks in UI rendering.

Tips for optimizing performance

  • use the dependency array to control when the effects runs

  • Avoid performance resource intensive tasks in useLayoutEffect as this would slow down the UI rendering, whenever the effect re runs

  • Properly run the clean up function when the effects have run. As not performing proper clean-up might result in bugs or memory leaks

  • Try to encapsulate complex logic in custom hooks

Common mistakes and how to avoid them

  1. Not managing the dependency Array: Remember these points with regard to the dependency array

Always state the reactive values in the dependency array, when reactive values change the effect will re-run

Leaving the dependency array empty will result in the effect running only once when the component mounts

Not providing the dependency array results in the effects running every time the component reruns

2. Overusing useLayoutEffect

Only use the useLayoutEffect where it is necessary, defaulting to useEffect whenever possible. As useLayoutEffect is synchronous it may cause performance issues and it is render blocking as well. So, to avoid performance issues always default to useEffect

3. Always list reactive values in the dependency array

Whenever you are using component values that are reactive, always list them in the dependency array. This ensures that whenever these values change the effect will run

  1. Not running the clean-up function properly or not running the clean-up function at all

Clean up function is an important part of the effects, after the effects have taken place always free up the resources and clean up. Not doing the cleanup properly may cause bugs or memory leaks

Conclusion

I hope you liked the article. Thank you for reading.

This article is brought to you by DeadSimpleChat, Chat API for your website and app