How to use useEffect() in React?

Managing Side Effects in React Functional Components with useEffect()

Introduction

React developers rejoice! The useEffect() hook is here to save the day! It's the Batman to our Robin, the peanut butter to our jelly, the...well, you get the point. useEffect() is a hook that allows you to perform side effects in your functional components, and it's so easy even your grandma could use it (no offense to your grandma).

What's a "side effect" in React?

Think of it like this: if your component is the party, then side effects are the aftermath. They're the things that happen after the component has rendered and they can be things like fetching data from an API, updating the DOM, setting timers, or making a sandwich. Okay, maybe not that last one, but you get the idea.

Check out my previous post to know more about side effects and when to use useEffect(). Make sure you read them before proceeding further.

How to...

There are 3 easy steps that you need to follow to use useEffect() :

  1. Import useEffect() and declare an Effect. By default, your Effect will run after every render. Effect is nothing but a function that you need to perform after React renders your component. (To dive deeper into Effects, check out this article)

  2. Specify the dependencies for the Effect. Sometimes, you don't need your Effects to re-run after every render right? That's why you specify the dependencies for the Effect. It tells React to re-run the Effect only when the state of the dependencies changes and not on every render.

  3. Add cleanup if needed. Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, “connect” needs “disconnect”, “subscribe” needs “unsubscribe”, and “fetch” needs either “cancel” or “ignore”. You will learn how to do this by returning a cleanup function.

Let’s look at each of these steps in detail.

Step 1: Declaration

  • Import useEffect() hook from react

      import {useEffect} from 'react';
    
  • Call it at the top level of the component where you need to use it

      const MyComponent = () => {
          useEffect(() => {
              // Write your Effect
              // Code here will run at every render by default!
          });
          return <h1> Hola </h1>;
      };
    

    Since there is no dependency array specified, the code inside useEffect() will run every time after the component renders. In other words, useEffect() will delay the code inside it until the component is rendered completely.

Sometimes, a piece of code must run only after the component is rendered completely.

An uncommon scenario

  • Problem: Suppose you want to give a default value to an input field whenever a user visits that page. Of course, you can do it within the <input/> tag itself. But let's say you wanna do it without using it for some reason.

  • Let's go ahead and try it:

    %[codesandbox.io/embed/reverent-star-1iohyb?f..

  • We are using ref to update the value of the input tag. But as you can see, it is showing an error! Can you figure out why?

  • That's because we are trying to access the current property of the ref even before the component is rendered. This means that the <input/> tag hasn't even appeared on the screen yet!

  • Here's how you do it: Just wrap the ref.current.value="Hey"; within useEffect() !

  •   import "./styles.css";
      import { useEffect, useRef } from "react";
    
      export default function App() {
        const ref = useRef();
        useEffect(() => {
          ref.current.value = "hey";
        });
    
        return (
          <div className="App">
            <h1>Hello CodeSandbox</h1>
            <input type="text" name="msg" ref={ref} />
          </div>
        );
      }
    

    (Go ahead and copy this code to the above sandbox and verify it!)

Step 2: The Dependency Array for useEffect()

So, imagine you're a chef who loves to bake cookies. You have a special recipe for chocolate chip cookies that requires a few key ingredients - flour, sugar, eggs, and of course, chocolate chips.

In React terms, these ingredients are like the dependencies of your useEffect hook. Just like you need all these ingredients to bake your cookies, useEffect needs all its dependencies to work properly.

Now, let's say you want to use useEffect to track the number of cookies you've baked. You might write something like this:

useEffect(() => {
  console.log(`You've baked ${numCookies} cookies!`);
}, [numCookies]);

In this example, numCookies is the dependency that useEffect needs to keep track of. Whenever numCookies changes, useEffect will run the code inside its function (in this case, logging the number of cookies you've baked).

But let's say you forget to include numCookies in the dependency array:

useEffect(() => {
  console.log(`You've baked ${numCookies} cookies!`);
});

Now, useEffect won't be able to track changes to numCookies. It's like trying to bake cookies without flour - it just won't work!

So, remember to always include your dependencies in the dependency array for useEffect, or else your code might end up like a batch of cookies without chocolate chips - still good, but not quite as delicious as they could be!

A Common Scenario

Sure! The dependency array in the useEffect hook is used to specify which variables or values should trigger a re-run of the effect function when they change. Here's a common scenario where the dependency array is used:

Let's say you have a component that fetches data from an API and renders it on the screen. You want to update the data whenever the user clicks a button, so you use the useEffect hook to fetch the data:

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

  useEffect(() => {
    fetchData().then((newData) => {
      setData(newData);
    });
  }, []); // <-- dependency array is empty

  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
      <button onClick={() => {
        // update data here
      }}>Update Data</button>
    </div>
  );
}

In this example, the useEffect hook is run only once, when the component mounts, because the dependency array is empty. But you want to fetch the data again whenever the user clicks the "Update Data" button.

To achieve this, you can add data as a dependency in the dependency array:

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

  useEffect(() => {
    fetchData().then((newData) => {
      setData(newData);
    });
  }, [data]); // <-- add data to dependency array

  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
      <button onClick={() => {
        fetchData().then((newData) => {
          setData(newData);
        });
      }}>Update Data</button>
    </div>
  );
}

Now, the useEffect hook will be re-run whenever data changes, which happens when the user updates the data by clicking the "Update Data" button.

In summary, a common scenario for the use of the dependency array is when you want to re-run an effect function when certain variables or values change. By adding these variables to the dependency array, you can ensure that the effect runs at the appropriate times, and keeps your component in sync with its environment.

In the scenario I provided, the function specified in the onClick handler is almost identical to the one specified in the useEffect hook. In this case, it may seem redundant to use both.

However, in practice, the useEffect hook is often used for more complex logic, such as updating state based on multiple dependencies, subscribing to event listeners, or cleaning up resources when the component unmounts. In those cases, it may not be appropriate to duplicate that logic in an onClick handler.

Additionally, using useEffect can help keep your code organized and maintainable. By separating the effect logic from the event handling logic, you can more easily reason about how your component works, and make changes to one part without affecting the other.

So while it may not always be necessary to use both useEffect and an onClick handler, it's good practice to consider the benefits of each and use them as appropriate for your specific use case.

Step 3: Cleanup

Imagine you're a very forgetful person who often forgets to turn off the lights when leaving a room. Your partner complains about the high electricity bills, so you decide to use a smart light switch that can be controlled with your phone. You install the switch and write a React component to control it:

function LightSwitch() {
  const [isOn, setIsOn] = useState(false);

  useEffect(() => {
    if (isOn) {
      console.log("Turning the lights on!");
    } else {
      console.log("Turning the lights off!");
    }

    return () => {
      console.log("Cleaning up after myself!");
      setIsOn(false);
    };
  }, [isOn]);

  return (
    <div>
      <button onClick={() => setIsOn(!isOn)}>
        {isOn ? "Turn off the lights" : "Turn on the lights"}
      </button>
    </div>
  );
}

In this example, the useEffect hook is used to log a message whenever the lights are turned on or off. Additionally, a cleanup function is defined to set isOn to false when the component unmounts. This ensures that the lights are always turned off when the component is no longer being used.

Now, imagine you're testing this component and you forget to turn off the lights before closing the browser window. Without the cleanup function, the lights would stay on even though the component is no longer being used! But thanks to the cleanup function, the lights will be turned off automatically when the component unmounts.

Of course, in real life, you might not need a cleanup function for a light switch - but the concept still applies to other scenarios, such as unsubscribing from event listeners or closing database connections. By using a cleanup function in useEffect, you can ensure that your component behaves correctly and doesn't leave any "messes" behind.

Conclusion

Hope this article has given you an understanding of using the useEffect() hook in React. Be sure to like the article and subscribe to the blog before you go. If you have any thoughts or queries, just make use of the comment section! 😊

Did you find this article valuable?

Support Sudarshan MG by becoming a sponsor. Any amount is appreciated!