← all articles
// article

useEffect mistakes that everyone makes

2025-08-31

Most React developers, from fresh faces to seasoned veterans, eventually trip over useEffect.

The core mistakes usually boil down to misunderstanding its dependency array, neglecting cleanup functions, or inadvertently creating infinite render loops that bog down performance and lead to elusive bugs. TL;DR: It's all about what your effect *depends* on and *when* it stops caring.

What is useEffect and Why Does It Trip Everyone Up?

At its heart, React's useEffect hook is designed for “side effects.” These are operations that interact with the world outside of React’s rendering cycle. Think data fetching from an API, manually changing the DOM, setting up subscriptions, or logging user actions to a service like Sentry or PostHog.

It’s tricky because it doesn't directly relate to *what* your component renders, but *how* your component interacts with external systems *after* it renders. This distinction is crucial. Without useEffect, your components would be purely functional, isolated islands unable to communicate with servers, browsers, or other services. With it, you gain immense power, but also the responsibility to manage that power carefully.

The Dependency Array: Your Silent Saboteur?

Perhaps the most common source of useEffect frustration is the dependency array, that second argument to useEffect (e.g., useEffect(() => {}, [dependencies])). It dictates *when* your effect re-runs. Mismanaging it leads to two major problems:

The Fix: Rely on the eslint-plugin-react-hooks with its exhaustive-deps rule. It’s an incredibly helpful tool that will flag most dependency array issues. Listen to it. Understand *why* it suggests what it does. It's not nagging; it's protecting your code from subtle, hard-to-find bugs.

Infinite Loops: When Your App Becomes a Hamster Wheel?

An infinite loop is every developer’s nightmare. In React’s useEffect, it typically manifests as your browser tab freezing, your CPU fan roaring, or your API logs showing thousands of identical requests. The root cause is usually a state update within an effect that, in turn, triggers the effect again, creating a perpetual cycle.

Common scenarios include:

Debugging: React DevTools, judicious use of console.log, and watching your browser’s network tab or performance monitor are your best friends here. Catching these early saves significant debugging time and prevents potential server overload if the loop involves external calls.

Cleanup Functions: The Unsung Heroes?

Many developers focus solely on what an effect *does* when it runs, but neglect what it *should do* when it stops or when the component unmounts. This is where cleanup functions come in.

If your useEffect returns a function, that function is the cleanup. React will execute it:

  1. Before the effect re-runs (if its dependencies change).
  2. When the component unmounts.

Forgetting cleanup leads to:

Common Cleanup Needs:

Fetching Data: Doing it Right, Not Just Doing It?

Data fetching is a prime candidate for useEffect, but it's often done incorrectly. A common anti-pattern is directly using async/await in the useEffect callback:

useEffect(async () => {
const response = await fetch('/api/data');
// This is wrong! useEffect callback can't be async directly.
}, []);

The useEffect callback function itself can’t be async because it's expected to return either nothing or a cleanup function. An async function implicitly returns a Promise, which React doesn't know how to clean up.

The Correct Approach: Define an async function *inside* your effect and call it immediately:

useEffect(() => {
const fetchData = async () => {
const response = await fetch('/api/data');
// ... handle data ...
};
fetchData();
}, []);

For more robust data fetching, especially in larger applications, libraries like TanStack Query (React Query) or SWR are game-changers. They handle caching, revalidation, error handling, loading states, and race conditions out of the box, drastically reducing the amount of useEffect boilerplate you'd otherwise write. For new projects, especially those needing robust data management, we at SISL often steer clients towards libraries like react-query. It drastically reduces the boilerplate and potential useEffect headaches.

Over-optimization and Unnecessary Effects?

Sometimes, developers reach for useEffect out of habit, even when simpler solutions exist. This leads to over-engineered code that's harder to read, debug, and maintain, and can even introduce subtle performance issues.

Ask yourself:

For example, if you have const fullName = `${firstName} ${lastName}`;, you don't need a useEffect to update fullName state when firstName or lastName changes. Just calculate fullName directly in your component body. It's simpler, clearer, and more efficient.

When Not to Use useEffect? Alternatives to Consider

Understanding when not to use useEffect is just as important as knowing when to use it. Here are some common scenarios where other React features or patterns are more appropriate:

Embrace the Discipline, Reap the Rewards

useEffect is a powerful, indispensable tool in React development. Its intricacies, particularly around the dependency array and cleanup functions, are common stumbling blocks. However, by understanding its purpose, embracing linting tools like eslint-plugin-react-hooks, and considering alternative patterns or specialized libraries when appropriate, you can navigate the useEffect minefield with confidence.

The result? More stable, performant applications, fewer elusive bugs, and a more pleasant development experience for everyone involved. Building a complex application? Navigating these React intricacies can be daunting. If you're looking for expert guidance or need a team to build resilient, performant web applications, don't hesitate to get in touch.

Got a similar problem?

Boutique web development studio from Poland — sites, WooCommerce / Magento stores, custom web apps and landings. See what we shipped.

See SISL portfolio →

Free technical audit of your site — in 24h

Core Web Vitals measured on real users, indexability, structured data, meta and internal linking. A written report with prioritised fixes, not a PDF from a generic tool. No cost, no call required.

Get the free audit →