NIXX/DEVv1.14.0
ArticlesFavorites
Sign In
Sign In
Articles

Welcome to our blog

A curated collection of insightful articles, practical guides, and expert tips designed to simplify your workflow

Cover image for: Mastering useEffect: Patterns Every React Developer Should Know
August 12, 20256 MIN READ min readBy ℵi✗✗

Mastering useEffect: Patterns Every React Developer Should Know

Tired of fighting with useEffect in React? This guide breaks down the most common useEffect patterns, explains when (and when not) to use it, and shows how to avoid the bugs that come with it.

reactjavascript
ℵi✗✗

ℵi✗✗

Full-Stack Developer

Passionate about building tools and sharing knowledge with the developer community.

Was this helpful?

Popular Posts

  • NixOS vs. Arch Linux: Which One Belongs in Your Dev Setup?

    NixOS vs. Arch Linux: Which One Belongs in Your Dev Setup?

    5 MIN READ min read

  • How to Enable HTTPS on Localhost in Under 2 Minutes

    How to Enable HTTPS on Localhost in Under 2 Minutes

    3 MIN READ min read

  • Migrating from Create React App (CRA) to Vite: A Step-by-Step Guide

    Migrating from Create React App (CRA) to Vite: A Step-by-Step Guide

    4 MIN READ min read

  • Array Destructuring in PHP: A Practical Guide for Modern Developers

    Array Destructuring in PHP: A Practical Guide for Modern Developers

    5 MIN READ min read

Recommended Products

  • Apple iPad (7th Gen)

    Apple iPad (7th Gen)

    4.3
  • Fitbit Versa 4

    Fitbit Versa 4

    4.3
  • JBL Flip 6

    JBL Flip 6

    4.8
  • Dell 24 Monitor — SE2425HM Full HD

    Dell 24 Monitor — SE2425HM Full HD

    4.7

May contain affiliate links

Topics

webdev33productivity16cybersecurity12javascript11automation9guide8react7typescript7php6tutorial6freelancing5github actions5privacy5how to4Node.js4
+111 more topics →
🇺🇸USD ACCOUNTOpen a free US-based USD accountReceive & save in USD — powered by ClevaSponsoredInterserver Hosting#1 VALUEAffordable, reliable hosting from $2.50/mo99.9% uptimeSponsored

useEffect is one of the most used and most misunderstood hooks in React. The hook itself is not complicated, but the rules around when it runs, what it captures, and what belongs in the dependency array are subtle enough that they produce consistent categories of bugs: infinite loops, stale values, missing cleanup, and effects running when they should not.

This guide covers the practical patterns that handle the most common use cases correctly, explains the Strict Mode double-invocation behavior, identifies when useEffect is the wrong tool, and shows how custom hooks reduce complexity when effect logic is reused.

What this covers:

  • Running code once on mount

  • Re-fetching data when dependencies change

  • Cleanup functions for subscriptions, timers, and event listeners

  • Avoiding stale closures

  • When to skip useEffect entirely

  • Debouncing with setTimeout

  • The ESLint exhaustive-deps rule

  • Extracting effects into custom hooks


1. Run Once on Mount

The empty dependency array tells React to run the effect only after the first render:

useEffect(() => {
    console.log("Component mounted");
}, []);

Suitable for: initializing a third-party library, firing an analytics event, subscribing to an external store on mount.

Strict Mode double-invocation: In development, React Strict Mode intentionally mounts, unmounts, and remounts every component to surface effects that do not clean up correctly. This causes the effect to run twice in development. In production it runs once. If an effect runs twice and produces unexpected behavior, it is usually because the effect is not idempotent and is missing a cleanup function. The double invocation is surfacing a real problem, not causing one.


2. Re-fetch Data When a Dependency Changes

useEffect(() => {
    fetchUserData(userId);
}, [userId]);

The effect runs after the first render and again whenever userId changes. This is the standard pattern for loading data in response to a prop or state value.

One problem with data fetching in useEffect is that it does not handle race conditions: if userId changes quickly, two requests may be in flight simultaneously, and the earlier response may arrive after the later one. The fix is an AbortController in the cleanup function:

useEffect(() => {
    const controller = new AbortController();

    fetchUserData(userId, { signal: controller.signal })
        .then(setUser)
        .catch((err) => {
            if (err.name !== "AbortError") {
                setError(err);
            }
        });

    return () => controller.abort();
}, [userId]);

The cleanup cancels the in-flight request when userId changes again or when the component unmounts.

For new projects, data fetching libraries like TanStack Query or SWR handle caching, deduplication, and race conditions automatically. Using useEffect for data fetching directly is workable but requires this kind of careful handling.


3. Cleanup Functions

The function returned from a useEffect callback runs before the component unmounts and before the effect runs again on the next render cycle. It is used to tear down anything the effect set up.

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

    return () => clearInterval(id);
}, []);

Without the cleanup, the interval continues running after the component unmounts, which causes a memory leak and potentially a state update on an unmounted component.

The same pattern applies to:

  • addEventListener / removeEventListener

  • WebSocket connections

  • Observable subscriptions

  • Any external resource that must be explicitly released

A useful mental model: every resource the effect acquires should be released in the cleanup function.


4. Avoiding Stale Closures

A stale closure occurs when an effect captures a value from an earlier render and that value has since changed, but the effect still holds the old version.

// Stale: count may be outdated when handleScroll runs
useEffect(() => {
    function handleScroll() {
        console.log(count); // captures count from the render when this ran
    }

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
}, []); // missing count in dependencies

The correct fix is to include count in the dependency array:

useEffect(() => {
    function handleScroll() {
        console.log(count);
    }

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
}, [count]); // re-registers the listener when count changes

For cases where re-registering the listener on every count change is expensive, a useRef can hold the latest value without triggering re-registration:

const countRef = useRef(count);

useEffect(() => {
    countRef.current = count;
}, [count]);

useEffect(() => {
    function handleScroll() {
        console.log(countRef.current); // always current
    }

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
}, []); // listener registered once

The rule of thumb is: include everything the effect uses in the dependency array, unless there is a specific reason not to and the alternative is explicitly handled.


5. When to Skip useEffect

React's documentation now explicitly identifies several patterns where useEffect is the wrong tool:

Derived state. If a value can be computed directly from existing state or props, compute it inline rather than syncing it with an effect:

// Unnecessary — an effect to derive a value
useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

// Correct — derive inline
const fullName = `${firstName} ${lastName}`;

Syncing props to state. Copying a prop into state with useEffect creates synchronization problems. If a value comes from a prop and needs to be controlled, either lift the state up or use the prop directly.

Responding to a user event. If code should run because a user clicked a button or submitted a form, put it in the event handler, not in an effect that watches for the resulting state change.

Transforming data for rendering. Any data transformation that can be done during render should be done there. An effect that transforms and stores in state adds an extra render cycle for no benefit.

The guiding question is: is this code running because of a user interaction or a render, or is it synchronizing with something outside of React? If the former, it probably belongs in an event handler. useEffect is for the latter.


6. Debounce with setTimeout

useEffect(() => {
    const handler = setTimeout(() => {
        performSearch(searchTerm);
    }, 500);

    return () => clearTimeout(handler);
}, [searchTerm]);

Every time searchTerm changes, the previous timeout is canceled and a new one is started. The search only fires if searchTerm has not changed for 500 milliseconds. This prevents a request on every keystroke without needing an external debounce library.


7. The ESLint Exhaustive-Deps Rule

The eslint-plugin-react-hooks package includes an exhaustive-deps rule that warns when a dependency is missing from a useEffect dependency array:

npm install --save-dev eslint-plugin-react-hooks

In the ESLint configuration:

{
    "plugins": ["react-hooks"],
    "rules": {
        "react-hooks/rules-of-hooks": "error",
        "react-hooks/exhaustive-deps": "warn"
    }
}

Missing dependencies are one of the primary causes of stale closure bugs. The linter catches the cases that are easy to miss during code review. Disabling the rule with an inline comment should be a deliberate decision with a documented reason, not a way to silence a warning without understanding it.


8. Extract Repeated Effect Logic into Custom Hooks

When the same useEffect pattern appears in multiple components, extracting it into a custom hook reduces repetition and makes the component code easier to read.

function useWindowSize() {
    const [size, setSize] = useState({ width: 0, height: 0 });

    useEffect(() => {
        function updateSize() {
            setSize({
                width: window.innerWidth,
                height: window.innerHeight,
            });
        }

        window.addEventListener("resize", updateSize);
        updateSize();

        return () => window.removeEventListener("resize", updateSize);
    }, []);

    return size;
}

Usage in a component:

const { width, height } = useWindowSize();

The component no longer contains any effect logic. The hook handles setup, state, and cleanup internally. This pattern also makes the effect easier to test in isolation.


Key Takeaways

  • useEffect runs after render. Use it for synchronizing with external systems, not for logic that belongs in event handlers or derivations that can be computed inline.

  • The Strict Mode double-invocation is intentional. If an effect behaves incorrectly when run twice, the cleanup function is missing or incomplete.

  • Every resource acquired in an effect (timers, listeners, connections) should be released in the cleanup function returned from the effect.

  • Stale closures result from missing dependencies. Include everything the effect uses in the dependency array, or use useRef deliberately when re-running the effect on every change is too expensive.

  • Derived state, event responses, and data transformations for rendering do not belong in useEffect.

  • The exhaustive-deps ESLint rule catches missing dependencies automatically. Treat its warnings seriously.

  • Extract repeated effect logic into custom hooks to reduce component complexity and make effects easier to test.


Conclusion

Most useEffect bugs fall into a small number of categories: missing cleanup, missing dependencies, or using the hook for something that belongs elsewhere. Understanding these categories makes the bugs easier to spot and prevents them from being introduced in the first place.

The patterns in this guide cover the majority of real-world use cases. When the patterns feel like they are working against the code rather than helping it, it is usually a signal that useEffect is not the right tool for that particular problem.


Hit a specific useEffect bug that took a while to diagnose? Share what it was and what fixed it in the comments.

Topics
reactjavascript

Discussion

Join the discussion

Sign in to share your thoughts and engage with the community.

Sign In
Loading comments…

Continue Reading

More Articles

View all
Cover image for: AI for DevOps: Tools That Are Already Changing the Game
Jun 17, 20256 MIN READ min read

AI for DevOps: Tools That Are Already Changing the Game

How artificial intelligence is transforming CI/CD pipelines, monitoring, and incident response—today.

Cover image for: Build a Fun Alphabet Reader with TypeScript, Vite & Speech Synthesis API
Jun 27, 20254 MIN READ min read

Build a Fun Alphabet Reader with TypeScript, Vite & Speech Synthesis API

An interactive, educational project for beginners to learn modern frontend development.

Cover image for: Why You Should Use TypeScript in Every JavaScript Project
Jul 23, 20255 MIN READ min read

Why You Should Use TypeScript in Every JavaScript Project

JavaScript gets the job done—but TypeScript helps you write cleaner, safer, and easier-to-maintain code. Here’s why it’s worth using everywhere.

Cover image for: Embedding Cybersecurity in Development: Best Practices for 2025
Jul 1, 20257 MIN READ min read

Embedding Cybersecurity in Development: Best Practices for 2025

A developer-focused guide to integrating security into your workflow—covering tools, practices, and mindset shifts for 2025.

|Made with · © 2026|TermsPrivacy
AboutBlogContact

Free, open-source tools for developers and creators · Community driven