DEV Community

Cover image for React Hooks Explained: useEffect( ) (By Building An API Driven App)
Sahil Jain
Sahil Jain

Posted on • Originally published at webbrainsmedia.com

React Hooks Explained: useEffect( ) (By Building An API Driven App)

Original Interactive Post Link => https://webbrainsmedia.com/blogs/react-hooks-explained-useEffect-hook

In the previous article, I talked about the useState react hook. In this article, We will talk about the useEffect hook. which gives us the combined ability of these three famous React class lifecycle methods => componentDidMount, componentDidUpdate and componentWillUnmount. So, Lets start exploring this powerful hook by building a Coronavirus Tracker Application.

The Coronavirus Tracker App

Let's start by first defining the basic React functional component.

import React from 'react';

export const CoronaApp = () => {
  const renderButtons = () => {
    return (
      <div>
        <button style={{ margin: '5px' }}>Worldwide</button>
        <button style={{ margin: '5px' }}>USA</button>
        <button style={{ margin: '5px' }}>India</button>
        <button style={{ margin: '5px' }}>China</button>
      </div>
    );
  };

  return (
    <div>
      <h2>Corona Tracker</h2>
      {renderButtons()}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now, let's define two states =>

  • data: To store the tracking data that is fetched from the API
  • region: To store the current region
import React, { useState } from 'react';

export const CoronaApp = () => {
  const [data, setData] = useState({});
  const [region, setRegion] = useState('all');

  const renderButtons = () => {
    return (
      <div>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('all');
          }}
        >
          Worldwide
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('usa');
          }}
        >
          USA
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('india');
          }}
        >
          India
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('china');
          }}
        >
          China
        </button>
      </div>
    );
  };

  return (
    <div>
      <h2>Corona Tracker</h2>
      {renderButtons()}
      <h4 style={{ marginTop: '10px' }}>{region.toUpperCase()}</h4>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now, We will use axios to fetch the data from the API inside our useEffect hook. But before that, Let's first look at the basic usage of useEffect.

The most basic way to use the useEffect hook is by passing a single function as its argument like this =>

useEffect(() => {
  console.log('I will run on every render');
});
Enter fullscreen mode Exit fullscreen mode

By defining useEffect like this, Makes this hook behave like componentDidUpdate lifecycle method meaning it will run on every single render of our functional component.

To make the useEffect to behave like componentDidMount i.e. Make it to run only on the first render of our functional component. We need to pass an empty array as the second argument in the useEffect hook like this =>

useEffect(() => {
  console.log('I will run only on first render');
}, []);
Enter fullscreen mode Exit fullscreen mode

We can also pass a value in the array. By doing this, We are depending the running of useEffect hook on the state of the value passed. Like if we take an example of our corona tracker app, We want our useEffect to only run when the value of the region changes. So, We will define our useEffect hook like this =>

useEffect(() => {
  // Data fetching from the API
}, [region]);
Enter fullscreen mode Exit fullscreen mode

Okay! So now let's get back to our tracker app and use the useEffect hook to fetch the data from the API.

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

export const CoronaApp = () => {
  const [data, setData] = useState({});
  const [region, setRegion] = useState('all');

  useEffect(() => {
    axios
      .get(
        region === 'all'
          ? `https://corona.lmao.ninja/v2/${region}`
          : `https://corona.lmao.ninja/v2/countries/${region}`
      )
      .then((res) => {
        setData(res.data);
      });
  }, [region]);

  const renderButtons = () => {
    return (
      <div>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('all');
          }}
        >
          Worldwide
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('usa');
          }}
        >
          USA
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('india');
          }}
        >
          India
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('china');
          }}
        >
          China
        </button>
      </div>
    );
  };

  return (
    <div>
      <h2>Corona Tracker</h2>
      {renderButtons()}
      <h4 style={{ marginTop: '10px' }}>{region.toUpperCase()}</h4>
      <ul>
        {Object.keys(data).map((key, i) => {
          return (
            <li key={i}>
              {key} : {typeof data[key] !== 'object' ? data[key] : ''}
            </li>
          );
        })}
      </ul>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Lets Quickly also add a collapse info button.

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

export const CoronaApp = () => {
  const [data, setData] = useState({});
  const [region, setRegion] = useState('all');
  const [inDetail, setInDetail] = useState(false);

  const dataLen = Object.keys(data).length;

  useEffect(() => {
    axios
      .get(
        region === 'all'
          ? `https://corona.lmao.ninja/v2/${region}`
          : `https://corona.lmao.ninja/v2/countries/${region}`
      )
      .then((res) => {
        setData(res.data);
      });
  }, [region]);

  const renderButtons = () => {
    return (
      <div>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('all');
          }}
        >
          Worldwide
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('usa');
          }}
        >
          USA
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('india');
          }}
        >
          India
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setRegion('china');
          }}
        >
          China
        </button>
        <button
          style={{ margin: '5px' }}
          onClick={() => {
            setInDetail(!inDetail);
          }}
        >
          {inDetail ? 'Show Less' : 'Show More'}
        </button>
      </div>
    );
  };

  return (
    <div>
      <h2>Corona Tracker</h2>
      {renderButtons()}
      <h4 style={{ marginTop: '10px' }}>{region.toUpperCase()}</h4>
      <ul>
        {Object.keys(data).map((key, i) => {
          return (
            <span key={i}>
              {i < (!inDetail ? 10 : dataLen) ? (
                <li key={i}>
                  {key} : {typeof data[key] !== 'object' ? data[key] : ''}
                </li>
              ) : (
                ''
              )}
            </span>
          );
        })}
      </ul>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Now, If you open the developer console and go to the network tab, you will notice that when you click on the 'Show Less/Show More' button, the useEffect will not run. It will only run when you change the value of the region by clicking on any country button. That is happening because we passed the value of region in the array as the second argument of our useEffect hook. If we remove the region from the array it will run only the first time and if we remove the array then, it will run everytime on every state change event.

useEffect Clean Up

If you have used React then, you must be familiar with this warning that comes up in the console

Can't perform a React state update on an unmounted component. This is a no-op,
but it indicates a memory leak in your application. To fix, cancel all
subscriptions and asynchronous tasks in a useEffect cleanup function.

This message is simply saying to us that please don't try to change the state of a component which has already been unmounted and its unavailable.

This error is very common when we subscribe to a service but forgot to unsubscribe or a component gets unmounted before finishing our async operation. To prevent this we can run a cleanup inside our useEffect hook.

To do a cleanup, Simply return a function within the method in the useEffect hook like this =>

useEffect(() => {
  console.log('Doing some task like subscribing to a service');

  return () => {
    console.log('Cleaning up like unsubscribing to a service');
  };
});
Enter fullscreen mode Exit fullscreen mode

If you observe the console you will see the running pattern like this =>

Output:

Clean Up

You can see that the cleanup will run before the task in useEffect skipping the first run of the useEffect hook. The cleanup will also run when the component will get unmounted.

Thats it, that is all you need to know about the useEffect hook. If you like my articles please consider liking, commenting and sharing them.

Cheers 🍻!!

Original Interactive Post Link => https://webbrainsmedia.com/blogs/react-hooks-explained-useEffect-hook

Top comments (1)

Collapse
 
scriptkavi profile image
ScriptKavi

Many early birds have already started using this custom hooks library
in their ReactJs/NextJs project.

Have you started using it?

scriptkavi/hooks

PS: Don't be a late bloomer :P