GitHub

Copyright © 2026 Sumit Paul

Design by Sumit Paul•LinkedIn

LinkedIn

State update after unmount during navigation

Avatar

Sumeet Kumar Paul

Frontend Engineer

2 min readSource code
Feb 10, 2026

Problem

When a component starts asynchronous work and the user navigates away before it resolves, the async callback may still attempt to update state. At that point, the component no longer exists in the UI tree.

This failure usually appears only under fast navigation or slow async resolution, making it easy to miss during development and code review.

  • ●Async work initiated inside an effect.
  • ●User navigates away before async resolves.
  • ●Component unmounts while async is still running.
  • ●Async callback commits state after unmount.
page.tsx
srcapppracticestate-update-after-unmount-during-navigationpage.tsx
1useEffect(() => {
2  console.log("[BROKEN] Screen mounted → async work started");
3
4  void (async () => {
5    await delay(800);
6    console.log("[BROKEN] Async finished → updating screen");
7    setStatus("loaded");
8  })();
9
10  return () => {
11    console.log("[BROKEN] Screen unmounted");
12  };
13}, []);

The async task has no awareness of the component lifecycle. Once started, it continues executing even after the component has been removed from the UI.

Solution

Each effect execution is assigned an ownership identity. Cleanup invalidates that ownership, and async work is only allowed to commit state if its ownership is still valid.

page.tsx
srcapppracticestate-update-after-unmount-during-navigationpage.tsx
1const effectIdRef = useRef(0);
2
3useEffect(() => {
4  const effectId = ++effectIdRef.current;
5  console.log("[FIXED] Screen mounted → ownership id:", effectId);
6
7  void (async () => {
8    await delay(800);
9
10    if (effectId !== effectIdRef.current) {
11      console.log("[FIXED] Async finished → ownership no longer valid, update skipped:", effectId);
12      return;
13    }
14
15    console.log("[FIXED] Async finished → ownership valid, screen updated:", effectId);
16    setStatus("loaded");
17  })();
18
19  return () => {
20    if (effectIdRef.current === effectId) {
21      effectIdRef.current = -1;
22    }
23    console.log("[FIXED] Screen unmounted → ownership invalidated:", effectId);
24  };
25}, []);

Instead of relying on mount flags, the async work validates ownership before committing state. Cleanup explicitly invalidates intent, making teardown safe even under rapid navigation.

This issue is not about React warnings or effect cleanup syntax. It occurs whenever asynchronous work is allowed to outlive the UI intent that created it. Treating async callbacks as untrusted unless their ownership is still valid prevents state leaks, memory issues, and subtle UI corruption during navigation.

Keywords

react unmount bug, state update after unmount, frontend lifecycle, async ui failure, navigation teardown

More Cases

2 min readFeb 20, 2026

State update after route transition

A component starts asynchronous work and then a route transition occurs before the work completes. When the async task resolves, it updates state that no longer matches the active route.

Avatar

Sumeet Kumar Paul

2 min readFeb 18, 2026

Optimistic UI rollback failure

An optimistic UI update immediately reflects a user action before the server confirms success. If the request fails or multiple toggles occur quickly, the UI can drift away from actual server state.

Avatar

Sumeet Kumar Paul

2 min readFeb 16, 2026

Double submit caused by UI state lag

A submit button is disabled only after state updates to 'loading'. Under rapid user interaction, multiple clicks can occur before the UI visually reflects the disabled state.

Avatar

Sumeet Kumar Paul

2 min readFeb 13, 2026

Stale closure in click outside listener

Event listeners defined inside effects capture state from the render during which they were created. If the listener is attached once and depends on state that later changes, it may continue operating on outdated values.

Avatar

Sumeet Kumar Paul