DEV Community

Cover image for Why useEffect? no pun intended lol!
Faith Kimani
Faith Kimani

Posted on

Why useEffect? no pun intended lol!

Understanding useEffect in React: Why You Can't Just Call a Function

A Saturday Morning Realization

So it’s a Saturday morning, and I’m basking in the sun, flipping through the last pages of The Psychology of Money by Morgan Housel. I just finished the final chapter. Not gonna lie, I was just floating the whole time because I'm not a US citizen, so understanding their economy with that well-detailed history workflow was very unrelatable. I am in Kenya, so we have completely different economies, but of course, there are some similarities here and there.

Anyway, enough about my side quests. I am here to talk about useEffect in React. Reading the last chapter felt like reading useEffect for the first time. When I first encountered it, I didn’t understand why I needed it. Why couldn’t I just call a function inside my component like in regular JavaScript? Say, for example, it's an API call—why not write the function directly in my component? If you’re wondering the same thing, give yourself a little pat on the back because that's a great question. What you need to understand is, React's rendering process is different from the normal execution of JavaScript functions. Let’s break it down.


The Problem: Why Not Just Call a Function?

In JavaScript, you can normally define and call functions inside your code. So why not just do this inside a React component?

function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  fetch("https://api.example.com/data")
    .then((response) => response.json())
    .then((data) => console.log(data));

  return (
    <div>
      <button onClick={() => setCount((count) => count + 1)}>
        I've been clicked {count} times
      </button>
      <input
        type="text"
        placeholder="Type away..."
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

🚨 Issue: Every time the component re-renders (due to state changes like count or text updates), the API call runs again. This leads to unnecessary network requests, which slow down your app.


The Waiter Analogy: Explaining Multiple API Calls

Think of this problem like a restaurant scenario:

Without useEffect: The Overeager Waiter

Imagine you walk into a restaurant and place your order. Now imagine that every time you pick up your spoon or sip water, the waiter rushes back to the kitchen and places another order for you—even though you haven’t asked for it. By the end of the meal, you have ten unnecessary plates on your table.

That’s what happens when you place an API call inside a React component without useEffect. React re-renders every time state changes, triggering the API call multiple times when it’s not needed.

With useEffect: The Well-Trained Waiter

Now, imagine a properly trained waiter who only takes your order once when you arrive. They don’t keep reordering unless you explicitly ask for something new.

That’s what useEffect does—it ensures that side effects like API calls only run when needed, preventing unnecessary re-fetching.


Enter useEffect: The Solution

To control when our API call runs, we use useEffect. It consists of two parts:

  1. A callback function that contains the logic for your side effect.
  2. A dependency array that determines when the effect should re-run.

Basic Structure:

useEffect(() => {
  // Side effect logic
}, [dependencies]);
Enter fullscreen mode Exit fullscreen mode

Example: Fixing Our API Call

import { useEffect, useState } from "react";

function MyComponent() {
  const [data, setData] = useState(null);
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  useEffect(() => {
    fetch("https://api.example.com/data")
      .then((response) => response.json())
      .then((data) => setData(data));
  }, []); // Runs only once when the component mounts

  return (
    <div>
      <div>{data ? JSON.stringify(data) : "Loading..."}</div>
      <button onClick={() => setCount((count) => count + 1)}>
        I've been clicked {count} times
      </button>
      <input
        type="text"
        placeholder="Type away..."
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now, the API call only runs once, just like a well-trained waiter taking your order.


Controlling When useEffect Runs

You can control useEffect's behavior using the dependency array:

  • useEffect(() => {}) (No dependency array) → Runs on every render.
  • useEffect(() => {}, []) (Empty dependency array) → Runs only once when the component mounts.
  • useEffect(() => {}, [variable]) (Dependency array with values) → Runs whenever the specified variable changes.

Example: Fetch Data When a State Changes

useEffect(() => {
  fetch(`https://api.example.com/data?page=${page}`)
    .then((response) => response.json())
    .then((data) => setData(data));
}, [page]); // Runs only when `page` state changes
Enter fullscreen mode Exit fullscreen mode

Cleaning Up: Avoiding Memory Leaks

Some side effects, like event listeners or timers, need to be cleaned up to prevent memory leaks. useEffect allows you to return a cleanup function:

useEffect(() => {
  const handleResize = () => console.log("Window resized");
  window.addEventListener("resize", handleResize);

  return () => {
    window.removeEventListener("resize", handleResize);
  };
}, []);
Enter fullscreen mode Exit fullscreen mode

Now, the event listener is removed when the component unmounts.


What Are Side Effects?

If you're wondering what I mean by side effects, let's break it down:

In a pure function (ideal in React), given the same inputs, you always get the same output. Side effects are actions that occur outside of a component's rendering logic. These include:

  • Fetching data from an API
  • Modifying the DOM directly
  • Setting up event listeners
  • Using timers
  • Managing subscriptions

Wrapping Up: Full Circle Moment

Just like I pushed through The Psychology of Money despite feeling disconnected from some parts, learning useEffect felt unnecessary at first—until I understood the why behind it. Now, I see it as an essential tool for managing side effects in React.

Key Takeaways:

Without useEffect, API calls run on every render, leading to performance issues.
useEffect lets you control when a function runs, avoiding unnecessary re-renders.
Using the dependency array helps optimize performance.
Cleanup functions prevent memory leaks.

So next time you see an API call inside a React component, think of our overeager waiter. And remember—React re-renders more than you think! 😉

Top comments (3)

Collapse
 
thealgorist profile image
Warui Wanjiru

Nice read💯💯

Collapse
 
gitahi_evans profile image
gitahievans

Great 👍 I love the analogies there.

Collapse
 
vandamugo profile image
John Mugo

Really good explanation