Exploring React Hooks: useEffect

Exploring React Hooks: useEffect

Exploring the Power of useEffect Hook in React

Welcome back to our Exploring React Hooks series! In our previous article, we explored how to use useState effectively. Today, we will explore the useEffect hook, a versatile tool that allows us to manage side effects within our React components. Let's get started!

What is the useEffect Hook?

The useEffect hook is one of the most widely used hooks in React. It enables us to perform side effects, such as data fetching, subscriptions, or manually changing the DOM, within our functional components.

Side effects are anything that affects something outside of the scope of the current function that’s being executed.

It serves as a replacement for the lifecycle methods in class components, providing a more concise and declarative way to handle side effects.

🪝Syntax and Usage:

The useEffect hook takes two arguments:

  • A function and

  • An optional dependency array.

The function passed to useEffect is the effect itself, containing the code that will be executed when the component mounts, updates, or unmounts. At the top of the component, we should first import the useEffect Hook. Here's a basic example:

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Code to be executed
    // ...

    return () => {
      // Cleanup code (optional)
      // ...
    };
  }, []); // Dependency array
}

The useEffect hook is called after the component renders for the first time and after every subsequent re-render. The optional dependency array specifies the values that the effect depends on.

🪝Dependency Management:

The dependency array is a crucial part of the useEffect hook, as it controls when the effect runs. By specifying dependencies, we can ensure the effect only runs when those dependencies change. This optimization helps prevent unnecessary computations and improves performance.

➡️ If the dependency array is not provided, the effect runs after every render:

import { useState, useEffect } from "react";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  });        //Runs on every render

  return <h1>This count has been rendered {count} times!</h1>;
}

export default Timer;

Within the useEffect hook, we invoked the setTimeout, which increments the count state by 1 after a delay of 1000 milliseconds (1 second). However, since the dependency array was not provided, it would result in the effect running on every render of the component.

Output:

➡️ If the dependency array is provided but empty, the effect runs only once when the component mounts:

import { useState, useEffect } from "react";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
  }, []);    //Runs only on the first render

  return <h1>This count has been rendered {count} times!</h1>;
}

export default Timer;

Within the useEffect hook, we again invoked the setTimeout, which increments the count state by 1 after a delay of 1000 milliseconds (1 second). However, this time, the useEffect hook is provided with an empty dependency array. This will ensure that the effect will run only once, specifically on the initial render of the component.

Output:

➡️ If the dependency array contains values, the effect runs whenever any of those values change:

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

function Timer() {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState("");

  useEffect(() => {
    if (count % 2 === 0) {
      setMessage("Count is even");
    } else {
      setMessage("Count is odd");
    }
  }, [count]);     //Runs anytime the dependency value changes

  return (
    <div>
      <h3>Count: {count}</h3>
      <p>{message}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Timer;

Within the useEffect hook, we used an if-else statement to determine whether the count value is even or odd. Based on the evaluation, the message state is updated accordingly. We also created a button to increment the count when clicked. The useEffect hook is provided with the count variable as its dependency, which ensures that the effect runs whenever the count value changes.

Output:

🪝Effect Cleanup

The useEffect hook provides an effect cleanup function that is valuable when dealing with side effects that require cleanup or to prevent memory leaks.

Memory leaks in JavaScript occur when a piece of memory is occupied unnecessarily.

The cleanup function is defined within the effect and is called when the component unmounts or before the next effect runs (if any). Effects such as subscriptions, timeouts, event listeners, and other effects that are no longer necessary should be disposed of.

This can be achieved by including a return function at the end of the useEffect Hook.

import { useState, useEffect } from "react";

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    let timer = setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);

    return () => clearTimeout(timer);          // Effect Cleanup
  }, []);

  return <h1>This count has been rendered {count} time!</h1>;
}

export default Timer;

Output:

Basic Guidelines:

  1. Infinite loops:

    If you forget to provide a correct dependency array, you may end up with an infinite loop of effects. Ensure that your dependencies accurately represent the state or prop changes that should trigger the effect.

  2. Separate effects:

    If your component requires multiple effects, it's generally recommended to separate them into separate useEffect calls. This allows you to manage each effect independently, keeping your code organized and easier to read and maintain.

  3. Cleanup functions:

    If your effect requires cleanup, such as canceling subscriptions or removing event listeners, it's essential to return a cleanup function. This ensures that the necessary cleanup actions are performed correctly, reducing memory leaks and unnecessary resource usage.

To Summarize

In summary, the useEffect hook is a powerful tool in React that allows developers to add lifecycle methods to functional components

By understanding its syntax, usage, dependency management, and optimization techniques and by following the basic guidelines we can effectively handle data fetching, subscriptions, and DOM manipulation. You should remember to optimize your effects using the dependency array and ensure proper cleanup to avoid potential issues.

References


Thank you for taking the time to read this blog. I hope you found it informative and enjoyable!

Share your thoughts and feel free to connect with me on Twitter.

For our next article, we'll explore another essential hook: useContext. Stay tuned!

Catch you guys on the next one. Cheers .. ✌️

Did you find this article valuable?

Support Raj Sarkar by becoming a sponsor. Any amount is appreciated!