- The symptom
- A screen keeps working after the user has left it.
- The cause
- Async work started in an effect is not tied to that component's lifetime.
- The fix
- Invalidate ownership in cleanup and check it before every state update.
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.
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.
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.
Check your own code
- 01Does every async effect have a cleanup that invalidates its work?
- 02Could the work be cancelled outright with an AbortController?
- 03Have you navigated away in the middle of a slow request?
- react unmount bug
- state update after unmount
- frontend lifecycle
- async ui failure
- navigation teardown

Written by Sumeet Kumar Paul
Senior Frontend Engineer from India. Frontend Lab is where I reproduce real interface bugs and work through the fix.
Need a hand?
I build frontends like this for a living.
More from the lab
Sumeet Paul· 2 min readState 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.
Sumeet Paul· 2 min readOptimistic 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.
Sumeet Paul· 2 min readDouble 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.