useEffect a Conceptual Deep Dive

harisbinejaz

Haris Ejaz

Posted on February 4, 2023

useEffect a Conceptual Deep Dive

In simplest of words, useEffect is a react hook that allows you to perform side effects in functional components. You can perform a lot of side effects using this hook such as Data fetching, setting up a subscription, manually changing the DOM in React components and many more.

Now we will look closely into useEffect functionality and it's usage.
Consider the following example:

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

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

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `Your counter: ${count}`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the above example we are simply editing title of our webpage when the NumberCount component is rendered. This showcases how to perform a simple side effect using useEffect hook in a react component.

Now let's see deep dive into this side effect thing.

Side Effects

There are two kinds of side effects in React components: those that require cleanup, and those that don't require cleanup. Let's talk about these distinctions in detail.

Effects With Cleanup

In some cases we might want to run some code when the React has updated the DOM. For instance

  • Network Requests
  • Manual Dom Mutations
  • Logging

When we say these side effects don't require cleanup this means that, when we run them we forget about them immediately.

Let's consider another example:

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

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

  useEffect(() => {
    // alert the value of count to user
    alert('Count Value: ' + count);
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

So what's actually happening?

  • We are actually telling React that we want to do something when our component is rendered by using this Hook.
  • React will remember the effect (the function we have passed) and call it when it updates the DOM.
  • In this effect we are setting alerting count value whenever we increment that value but we can also perform some kind of data fetching or some imperative API.

Explanation

We are setting a count variable and then we tell react we need to use an effect. Then we pass a function to the useEffect hook. The function we passed is our effect. In our function or effect we display the count value using the alert dialog of web API.

We can read the latest count inside the effect because it’s in the scope of our function. React will remember the effect we used when it renders our component, then run our effect after updating the DOM. This happens for every render, including the first one.

Effects Without Cleanup

Previously we looked on how to express side effects that don't require a cleanup. Now we will look at side effects that require a clean up for instance we might want to set up a subscription to an external data source. In that case we need to clean up that subscription in order to prevent memory leaks.

Let's consider the following example:

Some of you might be thinking that we need a separate effect for canceling the subscription but the code for adding & removing a subscription is so tightly related that useEffect is designed to keep it together. If you return something from your effect, React will run it when it's time to clean up a subscription.

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

function ServerStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    backendAPI.subscribeToServerStatus(props.server.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      backendAPI.unsubscribeFromServerStatus(
        props.server.id,
        handleStatusChange
      );
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
Enter fullscreen mode Exit fullscreen mode

UseEffect Arguments

useEffect hook accept two arguments:

useEffect(callback[, dependencies]);
Enter fullscreen mode Exit fullscreen mode
  • callback is a function where you'll write your side effects logic.
  • dependencies is an array of state variables or props. And useffect will call the callback function every time these values are updated between renderings.

flowchart describing useEffect flow

Using the dependencies array you can control when to run your side effects and these side effects are written inside the callback function.

We can use this dependency array in three ways:

  • Not Provided, side Effects run after every re-render
import { useEffect } from 'react';
function FunctionalComponent() {
  useEffect(() => {
    // Runs after EVERY render
  });
}
Enter fullscreen mode Exit fullscreen mode
  • Empty Array, side Effects run only on the first re-render
import { useEffect } from 'react';
function FunctionalComponent() {
  useEffect(() => {
    // Runs ONCE after initial render
  }, []);
}
Enter fullscreen mode Exit fullscreen mode
  • Not Provided, side Effects run after every re-render
import { useEffect, useState } from 'react';
function FunctionalComponent({ prop }) {
  const [state, setState] = useState('');
  useEffect(() => {
    // Runs ONCE after initial rendering
    // and after every rendering ONLY IF `prop` or `state` changes
  }, [prop, state]);
}
Enter fullscreen mode Exit fullscreen mode

To compile the above concept let's consider this example:

useEffect(() => {
  document.title = `Your counter: ${count}`;
}, [count]);
Enter fullscreen mode Exit fullscreen mode

So you see we have added count state variable in the dependency array. Whenever the count variable is updated callback function inside the useEffect hook is called. Which means useEffect is not called on every render rather it is called when count state variable is updated.

useEffect & Component Lifecycle

The dependency array of useEffect can be used in a way to replicate some important component lifecycle methods.

ComponentDidMount

As we know ComponentDidMount method is called after a component is mounted. We can use useEffect to replicate this functionality. How? Let's see:

import { useEffect } from 'react';
function Home({ name }) {
  const message = `Its, ${name}!, How you doin?`;
  useEffect(() => {
    // Runs once, after mounting
    document.title = 'Home page';
  }, []);
  return <div>{message}</div>;
}
Enter fullscreen mode Exit fullscreen mode

In the above example we are setting the dependency array empty ,so the callback function will be called once i.e when the component is mounted. In this manner, we can imitate the ComponentDidMount() functionality using the useEffect hook.

ComponentDidUpdate

By adding the state and props in the dependency array we can replicate the ComponentDidUpdate functionality using the useEffect hook. Let's see how we can do this:

import { useEffect } from 'react';
function FunctionalComponent({ prop }) {
  const [state, setState] = useState();
  useEffect(() => {
    // side effect logic where `props` and `state` is used
  }, [prop, state]);
  return <div>Functional Component</div>;
}
Enter fullscreen mode Exit fullscreen mode

So useEffect hook calls the callback once after the component has been mounted and subsequently after any changes to the values in the dependency array [prop, state] have been committed to the DOM. The callback will only be executed if one of the values in the dependencies array has changed.

Important Interview Related Stuff

So what happens, if I set the initial value of a state variable inside the callback function of useEffect?
Will it render the component or not?
Let us consider the following example:

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

function ServerStatus(props) {
  const [value, setValue] = useState(1);

  useEffect(() => {
    setValue(1);
  });

  return <div>{value}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Most of you will be thinking that when we reset the state variable value to 1 inside the callback function, ServerStatus component will render again, but the thing here is that when we mutate the state variable to 1 React compares the prevState value to the passed value in the setState method. As both values are equal it doesn't re-render the component to avoid any wasteful re-renders.

But what happens if we are dealing with non-primitive values?

Consider the following example:

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

function ServerStatus(props) {
  const [value, setValue] = useState({ x: 1 });

  useEffect(() => {
    setValue({ x: 1 });
  });

  return <div>{value}</div>;
}
Enter fullscreen mode Exit fullscreen mode

While considering our pervious observation we can say that the component will not be rendered as we are setting the same value as the prevState value. But if you dive deeper into this you will see ServerStatus component will be rendered infinitely. Now you will be wondering why? Let me explain this to you:

As you know when we compare two objects in javascript even if all the content of these objects are same when we compare them javascript will return false why? Because it store both objects in the memory with different reference and when it compare these objects it will compare reference of these objects and the result will be false. Let's understand this using some coding example:

// this will display false on the screen
console.log({} === {});
Enter fullscreen mode Exit fullscreen mode

Summary

The useEffect(callback, dependencies) hook is used to manage side-effects in functional components. The callback argument is a function that contains the logic for the side-effect, while the dependencies argument is a list of the props or state values that the side-effect depends on.

useEffect(callback, dependencies) invokes the callback function after the initial rendering (mounting) and on subsequent renderings, if any of the values in the dependencies array have changed.

If you still have queries regarding useEffect? Write a comment below!

💖 💪 🙅 🚩
harisbinejaz
Haris Ejaz

Posted on February 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related