REACT HOOKS: Effect Hooks

PART III

REACT HOOKS: Effect Hooks

Effects let a component connect to and synchronize with external systems. This includes dealing with network, browser DOM, animations, widgets written using a different UI library, and other non-React code, making api calls.

useEffect

  • connects a component to an external system.

  • The useEffect hook in React is used to handle side effects in functional components. Side effects refer to any code that needs to be executed after or during the rendering of a component, but is not directly related to rendering itself. Common side effects include data fetching, DOM manipulation, subscribing to external data sources, cleanup(example in CleanUp.md) and more.

  • rerender side Effect part of code after change in specified dependency

  • parameters:

    • A function: This is the effect function that contains the code you want to run as a side effect.

    • An optional array of dependencies: This array specifies which values or variables the effect function depends on. React will re-run the effect whenever any of these dependencies change. If you omit this array, the effect runs after every render.

  • TYPES OF DEPENDENCY

    • call every time change happens: define no dependency

    • call once when page renders: add [] as dependency

    • define change for specific change in state: add [stateName] in dependency

Effects are an “escape hatch” from the React paradigm. Don’t use Effects to orchestrate the data flow of your application. If you’re not interacting with an external system, you might not need an Effect.

useEffect Example

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

function UseEffectEg() {
  const [data, setData] = useState("");

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/comments")
      .then((response) => {
        setData(response.data[0].email);
        console.log("API WAS CALLED");
      })
      .catch((error) => {
        console.error(error);
      });
  });

  return <div>UseEffectEg : {data}</div>;
}

export default UseEffectEg;

There are two rarely used variations of useEffect with differences in timing:

useLayoutEffect:

  • fires before the browser repaints the screen. You can measure layout here.

  • called at early stage of page rendering than useEffect

  • CAN BE USED BEFORE SOME CHANGES IN UI HAPPEN MOSTLY USING useEffect and useState

useLayoutEffect Example

import React, { useEffect, useLayoutEffect, useRef } from "react";

function UseLayoutEffectEg() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.value = "HELLO";
    // console.log("useEffect");
  }, []);
  useLayoutEffect(() => {
    console.log(inputRef.current.value);
    // console.log("useLayoutEffect");
  }, []);

  return (
    <div>
      UseLayoutEffectEg
      <input ref={inputRef} value="PEDRO" style={{ width: 400, height: 60 }} />
    </div>
  );
}

export default UseLayoutEffectEg;

useEffect VS useLayoutEffect

  • useEffect called after all computation and rendering is done

  • useLayoutEffect is called before rendering is started In React, both useEffect and useLayoutEffect are hooks used for handling side effects in functional components, but they have different timing and use cases:

  1. Timing of Execution:
  • useEffect: useEffect runs after the component has rendered and the browser has painted the changes to the screen. It is asynchronous and non-blocking, meaning it doesn't delay the rendering process.

  • useLayoutEffect: useLayoutEffect runs synchronously immediately after the component has rendered but before the browser has had a chance to paint the changes. It's blocking, which means it can potentially cause a longer delay before the UI is updated.

  1. Use Cases:
  • useEffect: Use useEffect for most side effects that don't need to be synchronous with the rendering process. It's suitable for data fetching, setting up event listeners, and performing other asynchronous tasks.

  • useLayoutEffect: Use useLayoutEffect when you need to perform a side effect that relies on the actual DOM layout being updated immediately after a render. It's often used for measuring the layout of DOM elements or synchronously modifying the DOM based on component state changes.

  1. Performance Considerations:
  • Since useLayoutEffect runs synchronously and can potentially block the rendering process, it can lead to performance issues if not used carefully. If a side effect doesn't need to run synchronously, it's usually better to use useEffect to avoid impacting the perceived performance of your application.

  • Use useLayoutEffect when you specifically need to measure or interact with the DOM layout before the browser paints the changes. For example, if you need to measure the size of an element to make layout decisions, useLayoutEffect might be more appropriate.

Here's an example of when you might choose one over the other:

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

function ExampleComponent() {
  useEffect(() => {
    // This effect runs asynchronously, after the browser paints the changes.
    // It's suitable for non-blocking tasks.
    fetchData();
  }, []);

  useLayoutEffect(() => {
    // This effect runs synchronously, before the browser paints the changes.
    // It's suitable for tasks that rely on the DOM layout being up-to-date.
    measureElement();
  }, []);

  return <div>Content</div>;
}

In most cases, you'll use useEffect because it's more forgiving in terms of performance and timing. However, when you have specific requirements that involve immediate access to the layout or synchronous DOM updates, useLayoutEffect can be the right choice. Be cautious when using useLayoutEffect to avoid unnecessary performance bottlenecks.

  • useInsertionEffect

    fires before React makes changes to the DOM. Libraries can insert dynamic CSS here.

CLEANUP FUNCTION

Cleanup functions provided in the useEffect hook are valuable for various use cases beyond simply resetting state or cleaning up resources. Here are some additional use cases where cleanup functions can be beneficial:

  1. Clearing Timers and Intervals: When you set timers or intervals using setTimeout or setInterval in an effect, you should clear them in the cleanup function to prevent memory leaks and unexpected behaviour.
useEffect(() => {
  const timer = setTimeout(() => {
    // Some code here
  }, 1000);

  return () => {
    clearTimeout(timer); // Cleanup by clearing the timer
  };
}, []);
  1. Removing Event Listeners: If you attach event listeners in an effect, you should remove them in the cleanup function to avoid memory leaks and ensure proper behaviour when the component is unmounted.
useEffect(() => {
  const handleClick = () => {
    // Event handling code
  };

  window.addEventListener("click", handleClick);

  return () => {
    window.removeEventListener("click", handleClick); // Cleanup by removing the event listener
  };
}, []);
  1. Cancelling Promises: If you have async operations or promises in your effect, you can cancel them by using an "abort" controller and cleaning up the controller in the cleanup function.
useEffect(() => {
  const controller = new AbortController();

  fetchDataWithCancellation(controller.signal)
    .then((data) => {
      // Handle the data
    })
    .catch((error) => {
      // Handle errors or cancellation
    });

  return () => {
    controller.abort(); // Cleanup by aborting the fetch operation
  };
}, []);
  1. Managing Subscriptions: If your component subscribes to data streams (e.g., from web sockets or observables), you can unsubscribe or close these subscriptions in the cleanup function to avoid data leaks.
useEffect(() => {
  const subscription = dataStream.subscribe((data) => {
    // Handle the data
  });

  return () => {
    subscription.unsubscribe(); // Cleanup by unsubscribing
  };
}, []);
  1. Resetting Temporary State: In some cases, you might create temporary state or modify state for UI transitions or animations. You can use the cleanup function to reset this state when it's no longer needed.
useEffect(() => {
  // Set up temporary state for an animation or transition

  return () => {
    // Reset the temporary state
  };
}, [someDependency]);
  1. Clean-Up After Component Unmount: Cleanup functions are particularly important when handling asynchronous operations, timers, and subscriptions, as they ensure that these operations are properly cancelled or cleaned up when the component is unmounted.

By using cleanup functions effectively in your useEffect hooks, you can maintain a clean and predictable component lifecycle, avoid memory leaks, and ensure that your components behave correctly throughout their lifespan.