This warning appears when asynchronous operations (like API calls or timers) attempt to update state after a component has been removed from the DOM. While often harmless, it indicates that cleanup logic may be missing from your useEffect hooks.
This warning occurs when React detects that your code is trying to call setState (or a state updater from useState/useReducer) on a component that has already been unmounted from the DOM. React has removed the component from the Virtual DOM and the actual DOM, but asynchronous code is still trying to modify its state. The warning message mentions a "memory leak," but this is somewhat misleading. In most cases, there is no actual memory leakβthe warning simply indicates that an asynchronous operation (like a fetch request, setTimeout, or event listener) initiated while the component was mounted has completed after the component was removed. The state update itself is ignored by React (it's a "no-op"), so it won't cause crashes, but the warning suggests you should clean up these operations. True memory leaks occur when you subscribe to external services (WebSocket connections, global event listeners, store subscriptions) and forget to unsubscribe when the component unmounts. These subscriptions can persist and hold references to the unmounted component, preventing garbage collection. However, most instances of this warning are caused by promises or timers that complete after unmount, which are temporary and don't create persistent leaks.
Use an isMounted flag or AbortController to prevent state updates after unmount:
function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
let isMounted = true;
fetch('/api/data')
.then(res => res.json())
.then(data => {
if (isMounted) {
setData(data);
}
});
return () => {
isMounted = false;
};
}, []);
}Or with AbortController for modern browsers:
useEffect(() => {
const abortController = new AbortController();
fetch('/api/data', { signal: abortController.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
return () => abortController.abort();
}, []);Always cancel setTimeout and setInterval when component unmounts:
function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
setCount(c => c + 1);
}, 1000);
return () => clearTimeout(timer);
}, [count]);
// For intervals:
useEffect(() => {
const interval = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(interval);
}, []);
}Remove event listeners added to window, document, or global objects:
function WindowResize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
}Close persistent connections when component unmounts:
function LiveUpdates() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const ws = new WebSocket('wss://api.example.com/updates');
ws.onmessage = (event) => {
setMessages(prev => [...prev, event.data]);
};
return () => {
ws.close();
};
}, []);
}Cancel subscriptions to Redux, MobX, RxJS, or other state management libraries:
import { store } from './store';
function StoreSubscriber() {
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
});
return () => unsubscribe();
}, []);
}For async functions, check if component is still mounted before updating state:
function AsyncComponent() {
const [data, setData] = useState(null);
useEffect(() => {
let isMounted = true;
async function fetchData() {
try {
const response = await fetch('/api/data');
const result = await response.json();
if (isMounted) {
setData(result);
}
} catch (error) {
if (isMounted) {
console.error(error);
}
}
}
fetchData();
return () => {
isMounted = false;
};
}, []);
}React 18 removed this warning because it was widely misunderstood. Most cases flagged by the warning were not actual memory leaks. If you're on React 17 or earlier and see this warning frequently for legitimate async operations, consider upgrading:
npm install react@^18 react-dom@^18However, you should still implement proper cleanup for actual subscriptions (event listeners, WebSockets, store subscriptions) to prevent real memory leaks, even though React 18 won't warn you about them.
The React team removed this warning in React 18 after community feedback showed it was causing confusion. The warning was originally intended to catch memory leaks from uncanceled subscriptions, but it triggered too often for harmless async operations like fetch requests that complete after unmount. These operations don't create persistent memory leaks because promises are garbage collected once they resolve.
True memory leaks require persistent references that prevent garbage collection. Examples include global event listeners (window.addEventListener), WebSocket connections, setInterval timers, and store subscriptions (Redux, MobX). These should always be cleaned up in useEffect return functions.
For libraries like React Query, SWR, or Apollo Client, the warning might appear even though these libraries handle cleanup internally. Upgrading to their latest versions or to React 18 typically resolves these false positives.
If you're using class components, implement componentWillUnmount() to perform cleanup instead of useEffect cleanup functions. For components that need to perform cleanup before state updates in multiple effects, consider using a custom hook that encapsulates the isMounted pattern to avoid repetition.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite