README.md
April 9, 2026 ยท View on GitHub
๐ฆ use-is-mounted-ref
React Hooks for mount state tracking and auto-cleanup with AbortController/AbortSignal
Table of Contents
Motivation
Prevent memory leaks and auto-cancel async work when components unmount. React Strict Mode compatible.
Common warning this library helps avoid
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Installation
yarn add use-is-mounted-ref
# or
npm install use-is-mounted-ref
Hooks
useIsMountedRef
Track component mount state with a ref.
Example: Avoid setState when unmounted
import { useState, useEffect } from 'react';
import { useIsMountedRef } from 'use-is-mounted-ref';
function App() {
const isMountedRef = useIsMountedRef();
const [state, setState] = useState({
loading: true,
error: false,
data: [],
});
useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then(({ data }) => {
if (isMountedRef.current) {
setState((prev) => ({ ...prev, loading: false, data }));
}
})
.catch((err) => {
if (isMountedRef.current) {
setState((prev) => ({ ...prev, loading: false, error: true }));
}
});
}, [isMountedRef]);
return state.loading ? 'Loading...' : 'Found Data!';
}
useAbortController
Automatically abort fetch requests and async operations on unmount.
Example: Auto-cancel fetch on unmount
import { useState, useEffect } from 'react';
import { useAbortController } from 'use-is-mounted-ref';
function App() {
const abortController = useAbortController();
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data', {
signal: abortController.signal,
})
.then((response) => response.json())
.then(setData)
.catch((err) => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
}, [abortController]);
return <div>{data ? 'Loaded!' : 'Loading...'}</div>;
}
useAbortSignal
Returns an AbortSignal that automatically aborts on unmount. Simpler API for the most common use case โ you usually only need the signal, not the full controller.
Example: Auto-cancel fetch on unmount
import { useState, useEffect } from 'react';
import { useAbortSignal } from 'use-is-mounted-ref';
function UserProfile({ id }) {
const signal = useAbortSignal();
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${id}`, { signal })
.then((res) => res.json())
.then(setUser)
.catch((err) => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
}, [id, signal]);
return <div>{user?.name}</div>;
}
Example: Auto-cleanup event listeners
The signal option in addEventListener removes the listener automatically when aborted โ no need for manual removeEventListener.
import { useEffect } from 'react';
import { useAbortSignal } from 'use-is-mounted-ref';
function useWindowResize(callback) {
const signal = useAbortSignal();
useEffect(() => {
window.addEventListener('resize', callback, { signal });
}, [callback, signal]);
}
Example: Cancel timers on unmount
import { useEffect } from 'react';
import { useAbortSignal } from 'use-is-mounted-ref';
function useDelayedAction(action, delay) {
const signal = useAbortSignal();
useEffect(() => {
const id = setTimeout(action, delay);
signal.addEventListener('abort', () => clearTimeout(id));
}, [action, delay, signal]);
}
Example: Combine hooks
import { useState, useEffect } from 'react';
import { useIsMountedRef, useAbortController } from 'use-is-mounted-ref';
function App() {
const isMountedRef = useIsMountedRef();
const abortController = useAbortController();
const [state, setState] = useState({ loading: true, data: null });
useEffect(() => {
fetch('https://api.example.com/data', {
signal: abortController.signal,
})
.then((res) => res.json())
.then((data) => {
if (isMountedRef.current) {
setState({ loading: false, data });
}
})
.catch((err) => {
if (err.name !== 'AbortError' && isMountedRef.current) {
setState({ loading: false, data: null });
}
});
}, [abortController, isMountedRef]);
return state.loading ? 'Loading...' : 'Loaded!';
}
Migration from v1 to v2
Breaking change: Default export replaced with named exports.
- import useIsMountedRef from 'use-is-mounted-ref';
+ import { useIsMountedRef } from 'use-is-mounted-ref';
New hook available:
import { useAbortController } from 'use-is-mounted-ref';
Contributing
Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests to us.
Bugs and Sugestions
Report bugs or do suggestions using the issues.