Learnitweb

React useEffect Hook

1. Introduction

useEffect is a React Hook which allows you to synchronize your component with an external system. Here, external system means any piece of code that’s not controlled by React, such as:

  • A timer managed with setInterval() and clearInterval().
  • An event subscription using window.addEventListener() and window.removeEventListener().

2. Signature

useEffect(setup, dependencies?)

useEffect has two parameters, setup and dependencies and returns undefined.

  • setup: The setup is the function which contains logic to be executed for the Effect. Your setup code runs when your component is added to the page. The setup function can optionally return a cleanup function. The cleanup function should stop or undo whatever the setup function was doing. Whenever a re-render happens due to change in dependencies, React runs the cleanup function (if it is available) with old props and state. Then React runs the setup function using new props and state. The cleanup function is executed when the component is removed from the DOM.
  • dependencies: This parameter is optional. If this parameter is not provided then the Effect runs after every re-render of the component. The dependencies is a list of all the reactive values used inside the setup code. So if there is any value in your setup function which is supposed to change and should result in execution of your logic, should be added to the dependencies.
    The dependencies is a constant number of items like [dep1, dep2, dep3]. If linter is configured, it checked if all the dependencies are added. dependencies can be props, state and all the variables and functions declared directly inside your component body. React compares all the dependencies with previous values using Object.is.
    If your Effect’s code doesn’t use any reactive values, its dependency list should be empty ([]). An Effect with empty dependencies doesn’t re-run when any of your component’s props or state change.
    Even with empty dependencies, setup and cleanup will run one extra time in development to help you find bugs. If you pass no dependency array at all, your Effect runs after every single render (and re-render) of your component.

React calls setup and cleanup code multiple times based on the requirement.

3. Points to note about useEffect

  • useEffect React Hook allows you to synchronize a component with an external system. So if you are not trying to synchronize with an external system, you may not need useEffect.
  • useEffect is a React Hook, so you can only call it at the top level of your component or your own Hooks. You can not call useEffect inside loops or conditions.
  • React will run an extra setup and cleanup cycle only in development when Strict mode is on. This is a stress test which React does to ensure that the cleanup code actually undo what the setup does. If there is a visible issue, you need to provide and validate the cleanup code.
  • It is possible that some objects and functions declared as dependencies can cause Effect to re-run multiple times. Only necessary objects and functions should be declared as dependencies and state updates and non-reactive code can be moved outside Effect.
  • If your Effect is a result of some interactive action like button click, React will generally allow the browser to paint the updated screen before running your Effect. If you don’t want this behavior then you must use useLayoutEffect instead of useEffect.
  • useEffect is meant only for the client side. It is not meant for server side rendering.

4. Example 1

import { useState, useEffect } from "react";
import { createConnection } from "./server.js";

function ServerRoom({ serverId }) {
  const [serverUrl, setServerUrl] = useState("https://localhost:1234");

  useEffect(() => {
    const connection = createConnection(serverUrl, serverId);
    connection.connect();
    return () => {
      connection.disconnect();
    };
  }, [serverId, serverUrl]);

  return (
    <>
      <label>
        Server URL: <input value={serverUrl} onChange={(e) => setServerUrl(e.target.value)} />
      </label>
      <h1>Hello message from the Server: {serverId}</h1>
    </>
  );
}

export default function App() {
  const [serverId, setServerId] = useState("primary");
  const [show, setShow] = useState(false);
  return (
    <>
      <label>
        Choose the Server to connect:{" "}
        <select value={serverId} onChange={(e) => setServerId(e.target.value)}>
          <option value="primary">primary</option>
          <option value="secondary">secondary</option>
          <option value="recovery">recovery</option>
        </select>
      </label>
      <button onClick={() => setShow(!show)}>{show ? "Close connection" : "Open connection"}</button>
      {show && <hr />}
      {show && <ServerRoom serverId={serverId} />}
    </>
  );
}

server.js

export function createConnection(serverUrl, serverId) {
  // A real implementation would actually connect to the server
  return {
    connect() {
      console.log('Connecting to "' + serverId + '" server at ' + serverUrl + "...");
    },
    disconnect() {
      console.log('Disconnected from "' + serverId + '" server at ' + serverUrl);
    },
  };
}

In this example, whenever the server type is selected or the server url is changed, Effect is executed.

5. Example 2

import { useState, useEffect } from "react";

export default function App() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    function handleMove(e) {
      setPosition({ x: e.clientX, y: e.clientY });
    }

    window.addEventListener("pointermove", handleMove);

    return () => {
      window.removeEventListener("  ", handleMove);
    };
  }, []);

  return (
    <label>
      x position: {position.x} <br />y position: {position.y}
    </label>
  );
}

In this example, the external system is the browser DOM itself. We are using Effect to listen to the global window object. We are using the Effect to listen to the pointermove event and printing the coordinates for the pointer.

6. Fetching data with Effects

You can use an Effect to fetch data for your component. Let us see an example to manually fetch data.

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

function App() {
  // Set the default value of dogImage to null. This is done so that
  // when fetch is no complete, image is not rendered
  let [dogImage, setDogImage] = useState(null);

  useEffect(() => {
    fetch("https://dog.ceo/api/breeds/image/random")
      .then((response) => response.json())
      .then((data) => setDogImage(data.message));
  }, []);

  return <div className="App">{dogImage && <img src={dogImage}></img>}</div>;
}

export default App;

In this example, there is no dependency and image is fetched at the render of the component.

7. Downsides of fetching data with useEffect

Fetching data with useEffect is very popular. However, there are downsides of this approach.

  • Effects don’t run on the server – Since Effects do no run on the server, initially there is no data. Usually there is only initial value to render.
  • Creates network waterfalls – When there are nested components, fetching data creates network waterfall. Parent fetches data, then the child fetches data and so on.
  • You can’t cache and preload data when a component is mounted and unmounted again.
  • Lot of boilerplate code.

8. Conclusion

In conclusion, the React useEffect hook is an essential tool for managing side effects in functional components. By understanding its basic structure and nuances, developers can efficiently control when and how side effects are executed, such as data fetching, subscriptions, or manually changing the DOM. Key aspects include the dependency array, which determines when the effect runs, and the cleanup function, which ensures that effects are correctly disposed of to prevent memory leaks and other issues.

Mastering useEffect allows for more readable and maintainable code by clearly separating the logic for rendering from the side effects associated with it. As you continue to develop with React, incorporating the useEffect hook effectively will be crucial for building robust and efficient applications. Remember to pay attention to dependencies to avoid unnecessary re-renders and infinite loops, and always ensure cleanup functions are properly defined for optimal performance and resource management.