Can't perform React state update on unmounted component: how to fix and speed up code

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

A familiar bug that annoys every React developer

\n

Have you ever seen the warning Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. in your browser console? If yes — you are not alone. This question on Stack Overflow has gathered thousands of views and has become a real headache for beginners and experienced React developers alike.

\n

In this article, we will figure out why this error occurs, how to fix it using TypeScript, and most importantly — how to optimize the performance of your application so that such warnings no longer appear. Let's go!

\n\n

Why does React complain about an unmounted component?

\n

Imagine: a user opened a page, started loading data (for example, via fetch or axios), but didn't wait for the response and navigated to another screen. The component that initiated the request is already unmounted (removed from the DOM), but the asynchronous operation is still executing. When the response arrives, the code tries to update the state of a component that no longer exists. React rightly complains: "Hey, I can't update something that doesn't exist!".

\n

In TypeScript, this problem is especially noticeable if you use strict typing and forget about cleanup functions in useEffect.

\n\n

Classic example of the problem (TypeScript)

\n
import React, { useState, useEffect } from 'react';\n\ninterface User {\n  id: number;\n  name: string;\n}\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n  const [user, setUser] = useState<User | null>(null);\n\n  useEffect(() => {\n    fetch(`/api/users/${userId}`)\n      .then(res => res.json())\n      .then(data => setUser(data)); // Error! The component may be unmounted\n  }, [userId]);\n\n  return <div>{user?.name}</div>;\n};
\n

If the user quickly switches routes, the UserProfile component will be removed, and setUser will be called for nothing. We get a memory leak and an error in the console.

\n\n

3 ways to fix the error (with TypeScript examples)

\n

Let's look at three reliable approaches that solve the problem and improve the performance of a React application.

\n\n

Method 1: Flag using useRef (most popular)

\n

Create a ref flag that indicates whether the component is mounted. Update the state only if the flag is true.

\n
import React, { useState, useEffect, useRef } from 'react';\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n  const [user, setUser] = useState<User | null>(null);\n  const isMounted = useRef(true);\n\n  useEffect(() => {\n    // Reset the flag on each mount\n    isMounted.current = true;\n\n    fetch(`/api/users/${userId}`)\n      .then(res => res.json())\n      .then(data => {\n        if (isMounted.current) {\n          setUser(data);\n        }\n      });\n\n    // Cleanup: set the flag to false on unmount\n    return () => {\n      isMounted.current = false;\n    };\n  }, [userId]);\n\n  return <div>{user?.name}</div>;\n};
\n

Pros: simplicity, no additional libraries required. Cons: you must not forget about cleanup.

\n\n

Method 2: AbortController to cancel the fetch request (recommended)

\n

A modern and correct approach is to cancel the request itself, rather than just ignoring the response. This saves server and client resources.

\n
import React, { useState, useEffect } from 'react';\n\nconst UserProfile: React.FC<{ userId: number }> = ({ userId }) => {\n  const [user, setUser] = useState<User | null>(null);\n  const [loading, setLoading] = useState(true);\n\n  useEffect(() => {\n    const controller = new AbortController();\n    const signal = con                

Blogs

Book Recommendations