Handling Side Effects in React With Ease

As a React developer, you might have heard about side effects. If you have built react applications, you might have faced situations like fetching data and calling Web API.

All of them are considered side effects in React.

Handling the side effects is difficult if you are new to React or even if you have some prior experience working with React.

In this blog, I will talk about how you can handle side effects in React using useEffect, when you should not use useEffect to handle the side effects in React, and many more.

side effects in react

Let’s jump right into side effects in react…

Side Effects in React

In react, side effects are anything that does not directly impact the component UI i.e. JSX Code. For example, fetching API data.

Fetching data does not directly impact the JSX code that the component is returning – so this action is regarded as a side effect.

Every component in React has a goal of returning some JSX code which will be rendered on the user screen. Anything related to that is important but anything other than that is called a side effect.

Handling Side Effects in React

Let’s take an example to understand how you can handle side effects in React.

We are trying to show some images to the user. The images are of locations that the user might want to visit. The application helps the user list down the locations that they might want to visit in the future.

When the user visits the application. The location images shown to the user will be sorted based on distance. The closest locations will be shown first.

We already have some available images and their latitude and longitude. We are going to use the navigation API to get the current location of the user.

This action will be considered a side effect because it does not directly impact the output of the component instead, we are doing it as a background task.

The code that we are going to use to get the current location and then sort them based on the distance is as follows:

navigator.geolocation.getCurrentPosition((position) => {
    const sortedPlaces = sortPlacesByDistance(
      AVAILABLE_PLACES,
      position.coords.latitude,
      position.coords.longitude
    );
    setAvailablePlaces(sortedPlaces);
  });

Before we proceed to understand how you can handle this side effect inside the component – Let’s understand what could go wrong if we directly use this inside the component.

In the code, you can see that we are using the Navigator API to get the current location and after we get it we are going to change the value of availablePlaces state using setAvailablePlaces.

If you directly put this code inside your component, It will create an infinite loop.

How?

The Navigator Route API will give us the current location. After we get the current location, we change the state availablePlaces which triggers the component to render again.

In the next render, the same thing will happen – making the component render again. And it will continue to do that.

This is why the handling of side effects is important. And we are going to do that using the useEffect hook in React.

Handling Side Effects using useEffect

This Hook should be used for any side effects you’re executing in your render cycle.

What does useEffect do?

By using this Hook, you tell React that your component needs to do something after render. React will remember the function you passed (we’ll refer to it as our “effect”), and call it later after performing the DOM updates.

How to use useEffect?

useEffect() takes a function as an input and returns nothing.

The function it takes will be executed for you:

  • after the render cycle
  • after every render cycle

Example of useEffect – after the render cycle

Here is a sample code example to show you how you can handle side effects after the render cycle.

// Code...
  const [avaialblePlaces, setAvailablePlaces] = useState([]);

  navigator.geolocation.getCurrentPosition((position) => {
    const sortedPlaces = sortPlacesByDistance(
      AVAILABLE_PLACES,
      position.coords.latitude,
      position.coords.longitude
    );
    setAvailablePlaces(sortedPlaces);
  });

  // Code...

  return (
    <>
      Code...
      <main>
        Code...
        <Places
          title="Available Places"
          places={avaialblePlaces}
          onSelectPlace={handleSelectPlace}
        />
      </main>
    </>
  );
}

Example of useEffect – after every render

If you want to render your component depending on a value change of one or more states/props, then they (states/props) are called dependencies for useEffect.

useEffect takes 2 arguments – the first argument is a callback function and the second argument is a list of dependencies.

Here is another example Modal component that opens or closes depending on the value of open prop. Here open is the dependency for the useEffect as you want to calculate your component depending on its new value.

import { useRef, useEffect } from "react";
import { createPortal } from "react-dom";

function Modal({ open, children }) {
  const dialog = useRef();

  useEffect(() => {
    if (open) {
      dialog.current.showModal();
    } else {
      dialog.current.close();
    }
  }, [open]);

  return createPortal(
    <dialog className="modal" ref={dialog}>
      {children}
    </dialog>,
    document.getElementById("modal")
  );
}

export default Modal;

Want to learn more about props and states in React? Read about the 3 essential concepts in React for beginners.

Cleanup function in useEffect

In the React component life cycle, you will see there is a time when the component is unmounted.

When a component unmounts, it means it is removed from the DOM. Sometimes you might also want to make sure that you clean up or reset before the component is removed from the DOM.

The clean-up function runs in 2 cases –

  1. When the useEffect code is run again. Before the useEffect runs again, the clean-up function runs.
  2. When the component dismounts from the DOM, which is the scenario here. So, before the component function is removed from the DOM.

Let’s take an example to understand the cleanup function.

You have a delete confirmation model. When you show the user, you also present 2 buttons with it. When the user clicks on the delete button, you delete the item.

If the user does not perform any task then you automatically delete it after three seconds. Within 3 seconds, if the user clicks cancel, then you cancel the timer.

When you cancel it, the component for the model, the component is removed from the DOM. At this time, we want to make sure that you cancel the timer. Here, the clean-up function comes in handy,

import { useEffect } from "react";

export default function DeleteConfirmation({ onConfirm, onCancel }) {
  useEffect(() => {
    console.log("TIMER SET");
    const timer = setTimeout(() => {
      onConfirm();
    }, 3000);

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

  return (
    <div id="delete-confirmation">
      <h2>Are you sure?</h2>
      <p>Do you really want to remove this place?</p>
      <div id="confirmation-actions">
        <button onClick={onCancel} className="button-text">
          No
        </button>
        <button onClick={onConfirm} className="button">
          Yes
        </button>
      </div>
    </div>
  );
}

When Not To Use useEffect

useEffect makes the React code less optimized. So it is better to avoid using useEffect it when possible.

Here is a scenario where you can simply proceed with normal React code without useEffect for handling side effects.

function handleSelectPlace(id) {
  setPickedPlaces((prevPickedPlaces) => {
    if (prevPickedPlaces.some((place) => place.id === id)) {
      return prevPickedPlaces;
    }
    const place = AVAILABLE_PLACES.find((place) => place.id === id);
    return [place, ...prevPickedPlaces];
  });

  const pickedIds = localStorage.getItem("selectedPlaces") || [];
  if (pickedIds.indexOf("id" > -1)) return;
  localStorage.setItem("selectedPlaces", JSON.jsonify([id, ...pickedIds]));
}

In the example, you can see we are using localStorage. If you follow normal convention then this is also a side effect.

But we are not using useEffect it to handle this side effect. Because useEffect is not needed in this scenario.

You only need to use useEffect when you know that you are changing any state and it can cause an infinite loop to the component. In this case, there is no change of state or even if we change any state it will not cause any infinite loop – because the function runs only once when a place is selected.

Conclusion

In summary, useEffect provides better control over the component’s lifecycle and ensures a more predictable behavior when dealing with asynchronous operations like data fetching.

It helps you handle any case where it can cause an infinite loop. It can also help you avoid memory leaks with its cleanup function.

While useEffect is a powerful tool, you should also consider other easy options before using it as it makes react code less optimized.

Thanks, and see you soon!!

Hi, I’m Arup—a full-stack engineer at Enegma and a blogger sharing my learnings. I write about coding tips, lessons from my mistakes, and how I’m improving in both work and life. If you’re into coding, personal growth, or finding ways to level up in life, my blog is for you. Read my blogs for relatable stories and actionable steps to inspire your own journey. Let’s grow and succeed together! 🚀

Leave a Reply

Your email address will not be published. Required fields are marked *