Hello Sunil
react-useeffect-hook-feature-image

The React useEffect Hook for Absolute Beginners

Whenever we need to make use of side effects in our application, useEffect hook is the way to go in React functional components.

In this article, we will learn about the useEffect hook in React, its uses, and implementation with the help of an example.

Let’s get started.

What exactly is an “effect”?

The word effect refers to a functional programming term called a “side effect”. But to really understand what a side effect is, we first have to grasp the concept of a pure function.

Most React components are pure functions, meaning they receive an input and produce a predictable output of JSX.

The input to a JavaScript function is arguments. What is the input to a React component, however? Props!

App.jsx

function App() {
  return (
    <>
      <div>
        <User name="John Doe" />
      </div>
    </>
  );
}

export default App;

User.jsx

import React from 'react';

const User = (props) => {
  return (
    <div>
      <h1>{props.name}</h1>
    </div>
  );
};

export default User;

Here we have a User component that has the prop name declared on it. Within User, the prop value is displayed in a header element.

This is pure because, given the same input, it will always return the same output. If we pass User a name prop with value “John Doe”, our output will always be John Doe.

You might be saying, “Who cares? Why do we even have a name for this?”

Pure functions have the great benefit of being predictable, reliable, and easy to test. This is as compared to when we need to perform a side effect in our component.

What are side effects in React?

Side effects are not predictable because they are actions which are performed with the “outside world.”

We perform a side effect when we need to reach outside of our React components to do something. Performing a side effect, however, will not give us a predictable result.

Think about if we were to request data (like blog posts) from a server that has failed and instead of our post data, gives us a 500 status code response.

๐Ÿ“ฃ Also Read: The SEOโ€™s Guide to HTTP Status Codes

Virtually all applications rely on side effects to work in one way or another, aside from the simplest applications.

Common side effects include:

  • Making a request to an API for data from a backend server
  • To interact with browser APIs (that is, to use document or window directly)
  • Using unpredictable timing functions like setTimeout or setInterval

This is why useEffect exists: to provide a way to handle performing these side effects in what are otherwise pure React components.

For example, if we wanted to change the title meta tag to display the user’s name in their browser tab, we could do it within the component itself, but we shouldn’t.

User.jsx

import React from 'react';

const User = ({ name }) => {
  document.title = name;
  // This is a side effect. Don't do this in the component body!

  return (
    <div>
      <h1>{name}</h1>
    </div>
  );
};

export default User;

App.jsx

import User from './User';

function App() {
  return (
    <>
      <div>
        <User name="John Doe" />
      </div>
    </>
  );
}

export default App;

Output:

perform-side-effect-directly

If we perform a side effect directly in our component body, it gets in the way of our React component’s rendering.

Component rendering and side-effect logic are independent. So it would be a mistake to perform side-effects directly in the body of the component.

Side effects should be separated from the rendering process. If we need to perform a side effect, it should strictly be done after our component renders.

This is what useEffect gives us.

In short, useEffect is a tool that lets us interact with the outside world but not affect the rendering or performance of the component that it’s in.

useEffect() Syntax

The basic syntax of useEffect is as follows:

// 1. import useEffect
import { useEffect } from 'react';

function MyComponent() {
  // 2. call it above the returned JSX  
  // 3. pass two arguments to it: a function and an array
  useEffect(() => {}, []);
  
  // return ...
}

The correct way to perform the side effect in our User component is as follows:

  • We import useEffect from “react”
  • We call it above the returned JSX in our component
  • We pass it two arguments: a function and an array

User.jsx

import { useEffect } from 'react';

const User = ({ name }) => {
  useEffect(() => {
    document.title = name;
  }, [name]);

  return (
    <div>
      <h1>{name}</h1>
    </div>
  );
};

App.jsx

import User from './User';

function App() {
  return (
    <>
      <div>
        <User name="John Doe" />
      </div>
    </>
  );
}

export default App;

The function passed to useEffect is a callback function. This will be called after the component renders.

๐Ÿ“ฃ Also Read: Understand Callbacks In JavaScript

In this function, we can perform our side effects or multiple side effects if we want.

The second argument is an array, called the dependencies array. This array should include all of the values that our side effect relies upon.

Kindly remember, dependencies is an optional array of dependencies. useEffect hook executes callback only once the page loads.

In our example above, since we are changing the title based off of a value in the outer scope, name, we need to include that within the dependencies array.

What this array will do is it will check and see if a value (in this case name) has changed between renders. If so, it will execute our use effect function again.

This makes sense because if the name changes, we want to display that changed name and therefore run our side effect again.

Dependencies Argument (Ways of Controlling Side Effects in useEffect Hook)

Dependencies argument of useEffect hook lets you control when the side effect runs. If dependencies are:

A) Not Provided: The side effect runs after every rendering

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Runs after EVERY rendering
  });  
}

B) An Empty Array []: The side effect runs once after the initial rendering

import { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // Runs ONCE after initial rendering
  }, []);
}

C) Has props or state values [prop1, prop2, โ€ฆ, state1, state2]: The side effect runs once after initial rendering and then only when any dependency value changes

import { useEffect, useState } from 'react';

function MyComponent({ prop }) {
  const [state, setState] = useState('');
  useEffect(() => {
    // Runs ONCE after initial rendering
    // and after every rendering ONLY IF `prop` or `state` changes
  }, [prop, state]);
}

Let’s detail cases B) and C) since they are often used.

useEffect Hook & Component Lifecycle

The dependencies argument of the useEffect hook lets you catch certain component lifecycle events: when the component has been mounted or a specific prop or state value has changed.

Component Lifecycle – componentDidMount

We can use an empty dependencies array to invoke a side effect once after component mounting.

Greet.jsx

import { useEffect } from 'react';

const Greet = ({ name }) => {
  const message = `Hello, ${name}!`;

  useEffect(() => {
    // Runs once, after mounting
    document.title = 'Greetings page';
  }, []);

  return <div>{message}</div>;
};

export default Greet;

useEffect(โ€ฆ, []) was supplied with an empty array as the dependencies argument. When configured in such a way, the useEffect hook executes the callback just once, after initial rendering (mounting).

App.jsx

import Greet from './Greet';

function App() {
  return (
    <>
      {/* First render */}
      <Greet name="Sunil" /> //Side-effect RUNS

      {/* Second render, name prop changes */}
      <Greet name="Anil" /> // Side-effect DOES NOT RUN
			
      {/* Third render, name prop changes */}
      <Greet name="Gopal" /> // Side-effect DOES NOT RUN
    </>
  );
}

export default App;

Even if the component re-renders with different name property, the side effect runs just once after initial rendering.

Output:

componentDidMount-useeffect-react-hook

Component Lifecycle – componentDidUpdate

Each time the side effect uses props or state values, you must indicate these values as dependencies:

import { useEffect } from 'react';

function MyComponent({ prop }) {
  const [state, setState] = useState();

  useEffect(() => {
    // Side-effect uses `prop` and `state`
  }, [prop, state]);

  return <div>....</div>;
}

useEffect(callback, [prop, state]) invokes the callback once after mounting, and again after committing the changes to the DOM, if and only if any value in the dependencies array [prop, state] has changed.

By using the dependencies argument of useEffect hook we control when the side effect is called, independently from the rendering cycles of the component. Again, that’s the essence of useEffect hook.

Let’s improve the Greet component by using name prop in the document title:

Greet.jsx

import { useEffect } from 'react';

const Greet = ({ name }) => {
  const message = `Hello, ${name}!`;

  useEffect(() => {
    document.title = `Greetings to ${name}`;
  }, [name]);

  return <div>{message}</div>;
};

export default Greet;

App.jsx

import Greet from './Greet';

function App() {
  return (
    <>
      {/* First render */}
      <Greet name="Sunil" /> //Side-effect RUNS

      {/* Second render, name prop changes */}
      <Greet name="Anil" /> // Side-effect RUNS

	  {/* Third render, name prop doesn't change */}
      <Greet name="Anil" /> // Side-effect DOES NOT RUN

      {/* // Fourth render, name prop changes */}
      <Greet name="Gopal" /> // Side-effect RUNS
    </>
  );
}

export default App;

Output:

componentDidUpdate-useeffect-react-hook

What is the cleanup function in useEffect?

The final part of performing side effects properly in React is the effect cleanup function.

Sometimes our side effects need to be shut off. For example, close a socket, clear timers.

If the callback of useEffect(callback, deps) returns a function, then useEffect hook considers that function as an effect cleanup.

useEffect(function callback() => {
  // Side-effect...

  return function cleanup() {
    // Side-effect cleanup...
  };
}, dependencies);

Cleanup works the following way:

A) After initial rendering, useEffect hook invokes the callback with the side effect. cleanup function is not invoked.

B) On later renderings, before invoking the next side effect callback, useEffect hook invokes the cleanup function from the previous side effect execution (to clean up everything after the previous side effect), then invokes the current side effect.

C) Finally, after unmounting the component, useEffect hook invokes the cleanup function from the latest side effect.

Let’s see an example of when the side effect cleanup is useful.

RepeatMessage.jsx

import { useEffect } from 'react';

const RepeatMessage = ({ message }) => {
  useEffect(() => {
    setInterval(() => {
      console.log(message);
    }, 2000);
  }, [message]);

  return <div>I'm logging to console "{message}"</div>;
};

export default RepeatMessage;

App.jsx

import React, { useState } from 'react';
import RepeatMessage from './RepeatMessage';

function App() {
  const [message, setMessage] = useState('Hello, World!');
  return (
    <>
      <h3>Type the message to log to console</h3>
      <input
        type="text"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <RepeatMessage message={message}  />
    </>
  );
}

export default App;

Output:

useeffect-without-cleanup-function-example

When you type some messages. The console logs every 2 seconds the messages typed into the input. However, you need to log only the latest message.

That’s the case to clean up the side effect: cancel the previous timer when starting a new one. Let’s return a cleanup function that stops the previous timer before starting a new one:

RepeatMessage.jsx

import { useEffect } from 'react';

const RepeatMessage = ({ message }) => {
  useEffect(() => {
    const id = setInterval(() => {
      console.log(message);
    }, 2000);
    return () => {
      clearInterval(id);
    };
  }, [message]);

  return <div>I'm logging to console "{message}"</div>;
};

export default RepeatMessage;

App.jsx

import React, { useState } from 'react';
import RepeatMessage from './RepeatMessage';

function App() {
  const [message, setMessage] = useState('Hello, World!');
  return (
    <>
      <h3>Type the message to log to console</h3>
      <input
        type="text"
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <RepeatMessage message={message}  />
    </>
  );
}

export default App;

Now when you type some messages. You will see that every 2 seconds only the latest message logs to the console. Which means that all of the previous timers were cleaned up.

Result:

useeffect-with-cleanup-function-example

useEffect Hook in Practice

Example 1 – Random Meme Generator

We can use useEffect hook to fetch data from a server and display it without problems.

In the code snippet below, we will fetch meme images from the meme API and display a new image whenever a button clicks.

Meme.jsx

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

const Meme = () => {
  const [memeData, setMemeData] = useState({
    randomImage: 'https://i.imgflip.com/26jxvz.jpg',
  });
  const [allMemeImages, setAllMemeImages] = useState([]);

  useEffect(() => {
    fetch('https://api.imgflip.com/get_memes')
      .then((res) => res.json())
      .then((data) => setAllMemeImages(data.data.memes));
  }, []);

  const handleClick = () => {
    const randomNum = Math.floor(Math.random() * allMemeImages.length);
    const url = allMemeImages[randomNum].url;
    setMemeData((prevState) => {
      return {
        ...prevState,
        randomImage: url,
      };
    });
  };

  return (
    <div style={{ textAlign: 'center', marginTop: '30px' }}>
      <button onClick={handleClick}>Get a new meme image</button>
      <div style={{ textAlign: 'center', marginTop: '50px' }}>
        <img
          src={memeData.randomImage}
          alt="meme-images"
        />
      </div>
    </div>
  );
};

export default Meme;

App.jsx

import './App.css';
import Meme from './Meme';

function App() {
  return (
    <>
      <Meme/>
    </>
  );
}

export default App;

Result:

random-meme-generator-useeffect

The code above does the following:

  • Import useEffect and useState from React
  • Declares the initial state for memeData to display an image on the first render of the component with setMemeData as the setter function
  • Declares the initial state for allMemeImages to store all the images from the meme API endpoint
  • useEffect hook is called, and the fetch function is used to make a call to the meme API and store all the data using the setAllMemeImages. An empty dependency array indicates that the meme API is called once with the useEffect hook
  • Declares handleClick function to get a new image when the button is clicked. This function randomly generates a URL from the list of all the image URL gotten from the meme API and stored in the allMemeImages state. In this function, setMemeData is used to re-render the component on every click, which makes the image change to another image on the screen

Example 2 – Counter

Initially, the document title reads โ€œYou clicked 0 timesโ€. when you click on the button, the count value increments, and the document title is updated.

HookCounterOne.jsx

import { useState, useEffect } from 'react';

const HookCounterOne = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <button onClick={() => setCount((prevCount) => prevCount + 1)}>
        Click {count} times
      </button>
    </div>
  );
};

export default HookCounterOne;

App.jsx

import HookCounterOne from './HookCounterOne';

function App() {
  return (
    <>
      <HookCounterOne />
    </>
  );
}

export default App;

Result:

useeffect-counter-example

Conclusion

And that’s the gist of useEffect hook! i.e.

useEffect hook can be used to handle lifecycle events, such as componentDidMount, componentDidUpdate, and componentWillUnmount, in a functional component.

Thanks for reading this article. We hope it was helpful for React beginners.

Please feel free to ask questions in the comments below. Ultimately, practicing and building projects with this hook will help anyone to pick it up faster.

Resource

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

We are sorry that this post was not useful for you!

Let us improve this post!

Tell us how we can improve this post?

Similar articles you may like

Sunil Pradhan

Hi there ๐Ÿ‘‹ I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.

Add comment

Stay Updated

Want to be notified when our article is published? Enter your email address below to be the first to know.

Sunil Pradhan

Hi there ๐Ÿ‘‹ I am a front-end developer passionate about cutting-edge, semantic, pixel-perfect design. Writing helps me to understand things better.