DEV Community

Cover image for useEffect Finally Made Simple ( The way I wished someone explained me) ✨
Ruby Dahal
Ruby Dahal

Posted on

useEffect Finally Made Simple ( The way I wished someone explained me) ✨

Do you find understanding the useEffect hook in React hard? Honestly, I did too.

When I first started learning React, useEffect looked simple at first, but things like dependencies and cleanup made it confusing pretty quickly.

So let's break it down.

What is useEffect?

useEffect is a built-in React Hook that lets functional components perform side effects.

A side effect is basically something your component does outside of simply rendering the UI.

For example:

  • Fetching data from an API
  • Starting a timer
  • Adding an event listener
  • Updating the page title
  • Connecting to an external service

Basic Format

useEffect(() => {
  // Effect code

  return () => {
    // Cleanup code
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

There are three main parts.

1. The Effect

This is the function containing the code you want to run.

useEffect(() => {
  console.log("Effect ran!");
}, []);
Enter fullscreen mode Exit fullscreen mode

By default, useEffect runs after React renders the component.


2. The Dependency Array

The second part is the dependency array:

useEffect(() => {
  console.log(`Count is ${count}`);
}, [count]);
Enter fullscreen mode Exit fullscreen mode

It tells React when the effect needs to run again.

Empty array

useEffect(() => {
  console.log("Effect ran");
}, []);
Enter fullscreen mode Exit fullscreen mode

The effect doesn't re-run because of changing dependencies.

Note: In development, React Strict Mode can run an effect's setup and cleanup more than once, so don't panic if you see your console log twice.

With a dependency

useEffect(() => {
  console.log(`Count is ${count}`);
}, [count]);
Enter fullscreen mode Exit fullscreen mode

The effect runs again when count changes.

No dependency array

useEffect(() => {
  console.log("Effect ran");
});
Enter fullscreen mode Exit fullscreen mode

The effect runs after every render.

A quick way to remember:

[]          → no dependency-based re-runs
[count]     → runs when count changes
(no array)  → runs after every render
Enter fullscreen mode Exit fullscreen mode

3. The Cleanup Function

The cleanup function is used to stop or remove something that your effect started.

For example, if you start a timer:

useEffect(() => {
  const timer = setInterval(() => {
    console.log("Running...");
  }, 1000);

  return () => {
    clearInterval(timer);
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

When the component disappears, the cleanup function stops the timer.

Cleanup is commonly used for:

  • Timers
  • Event listeners
  • Subscriptions
  • Connections

Think of it like this:

Effect = Set something up

Cleanup = Clean it up


A Simple Example

Here's a basic counter:

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

  useEffect(() => {
    console.log(`Count is ${count}`);
  }, [count]);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Whenever count changes, React renders again, notices that the dependency changed, and runs the effect.

Pretty simple, right?

But this is where people often run into problems. Because there are some common mistakes you should know about.


Common Mistakes

Mistake 1: Forgetting Cleanup

Imagine you create a timer but never stop it:

useEffect(() => {
  const timer = setInterval(() => {
    console.log("Hello");
  }, 1000);
}, []);
Enter fullscreen mode Exit fullscreen mode

A better version is:

useEffect(() => {
  const timer = setInterval(() => {
    console.log("Hello");
  }, 1000);

  return () => clearInterval(timer);
}, []);
Enter fullscreen mode Exit fullscreen mode

Whenever your effect creates something that needs to be stopped or removed, remember to clean it up.


Mistake 2: Unstable Dependencies

Objects, arrays, and functions are compared by reference, not their contents.

For example:

const user = {
  id: 193,
  name: "Rubi"
};

useEffect(() => {
  fetchUser(user);
}, [user]);
Enter fullscreen mode Exit fullscreen mode

If user is recreated on every render, React sees a new object reference and may run the effect again.

If you only need the ID, depend on the primitive value instead:

useEffect(() => {
  fetchUser(user.id);
}, [user.id]);
Enter fullscreen mode Exit fullscreen mode

Now the effect only needs to re-run when user.id changes.


Mistake 3: Using useEffect for Everything

Sometimes you don't need useEffect at all.

For example, don't use an effect just to respond to a button click:

const [submitted, setSubmitted] = useState(false);

useEffect(() => {
  if (submitted) {
    postForm(formData);
  }
}, [submitted]);

<button onClick={() => setSubmitted(true)}>
  Submit
</button>
Enter fullscreen mode Exit fullscreen mode

You can simply do:

<button onClick={() => postForm(formData)}>
  Submit
</button>
Enter fullscreen mode Exit fullscreen mode

If something happens directly because of a user action, an event handler is usually the better choice.

Similarly, if you need to measure or adjust the DOM before the browser paints, useLayoutEffect may be more appropriate.


Wrapping Up

useEffect is actually pretty simple once you understand its three main parts:

Effect → the code you want to run.

Dependencies → when the effect should run again.

Cleanup → what needs to be stopped or removed.

And before writing an effect, ask yourself:

“Do I actually need useEffect here?”

Once you understand when to use it, what it depends on, and how to clean it up, useEffect becomes a lot less scary.

Top comments (2)

Collapse
 
samcodes17 profile image
Sam

Interesting!!

Collapse
 
prasamshaadhikari profile image
Prasamsha Adhikari

Beautiful project