• Home
  • Skills
  • Projects
  • Blog
  • Contact
Resume
Naser Rasouli

Author

Naser Rasouli

Front-End developer - sharing lessons learned, notes, and write-ups from real projects.

GitHubLinkedIn

Last posts

BEM Methodology in CSS: predictable naming for clean styles
2026-02-18•1 min read

BEM Methodology in CSS: predictable naming for clean styles

A practical guide to BEM to avoid style conflicts, structure class names, and keep CSS maintainable.

Why console.log After setState Shows the Old Value
2026-02-04•1 min read

Why console.log After setState Shows the Old Value

React batches state updates, so logging right after setState prints the previous value. Here’s why and the right ways to read the fresh state.

Frontend Interview Flow: Stages, Signals, and Prep
2026-01-17•1 min read

Frontend Interview Flow: Stages, Signals, and Prep

A frontend interview isn’t just a few HTML/JS questions—it’s a path to gauge web understanding, problem solving, and collaboration. This guide outlines common stages, what interviewers look for, and how to prepare.

Cleanup Functions in useEffect: Stop Leaks Before They Start

Cleanup Functions in useEffect: Stop Leaks Before They Start

2026-02-04
reactuseEffectcleanuphooks

Introduction

Almost everyone uses useEffect in React for fetching data, timers, or adding event listeners. The part that causes the most silent bugs is the cleanup function. Skip it or write it incorrectly and you get memory leaks, duplicate handlers firing, or the infamous setState on unmounted component. This post shows, with concrete examples, how to write a correct cleanup every time.


What useEffect does and what cleanup means

useEffect is for side effects — work that isn’t directly part of rendering. Its shape:

useEffect(() => {
  // effect: do the side effect

  return () => {
    // cleanup: tear down what you set up
  };
}, deps);

cleanup is the returned function. React runs it in two moments:

  • Before the effect re-runs when any deps change
  • When the component unmounts

In Strict Mode (dev), effects are mounted, cleaned up, then mounted again to expose bugs, so correct cleanup matters even more.


When does cleanup run?

  1. Before the next effect
    If any dependency changes, React calls the previous cleanup first, then runs the new effect.

  2. On unmount
    When the component leaves the DOM, React calls the last cleanup for that effect.

Order example:

useEffect(() => {
  console.log("effect");
  return () => console.log("cleanup");
}, [count]);

When count goes from 0 → 1, you’ll see: cleanup → effect.


Essential cleanup scenarios

DOM events

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

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

Without removal, each re-run adds another listener and the handler fires multiple times.

Timers (setInterval / setTimeout)

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

Forget clearInterval and the timer keeps running after unmount, burning CPU.

Subscriptions (socket/observable)

useEffect(() => {
  const unsubscribe = socket.subscribe(data => {
    console.log(data);
  });

  return () => unsubscribe();
}, []);

Every subscription needs an exit path; otherwise old handlers keep receiving messages.

Network requests with AbortController

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

  fetch("/api/data", { signal: controller.signal })
    .then(res => res.json())
    .then(data => console.log(data))
    .catch(err => {
      if (err.name !== "AbortError") console.error(err);
    });

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

Aborting prevents stray responses from calling setState on an unmounted component.


Dependencies and hidden bugs

  • Each time a dep changes, cleanup from the previous run fires first; rely on stable refs/state for teardown.
  • If you create handlers inside the effect and omit them from deps, you may remove the wrong reference later (stale closure). Use useCallback or include the dependency.
  • In Strict Mode dev, the sequence is effect → cleanup → effect for a single mount; make your effect idempotent and reversible with its cleanup.

Common mistakes to avoid

  • Declaring async directly on the useEffect callback and forgetting to abort requests
  • Adding event listeners without removing them or with a different reference on cleanup
  • Relying on setInterval without clearing it
  • Ignoring the dependency array and working with stale data
  • Assuming cleanup only runs on unmount

Quick checklist before merging

  • Does every resource you open (listener, timer, subscription, request) have a teardown path?
  • Does the dependency array intentionally include everything you use — or is it empty on purpose?
  • In Strict Mode dev, will effect → cleanup → effect still behave correctly?
  • Are handlers/callbacks stable so removeEventListener can actually remove them?

Wrap-up

The cleanup function is small, but it prevents big problems. Every time you write useEffect, ask: “What did I start that needs to be shut down?” Answering that keeps your React app free of leaks, duplicate listeners, and setState errors.