React useIsMounted Hook
Setting state on an unmounted component throws a warning in development and can mask real bugs in production. The useIsMounted hook gives async callbacks a way to bail out before they touch React state. This snippet covers the basic ref-based hook, a useSafeState wrapper that no-ops after unmount, and a useSafeCallback variant that protects the function itself.
656 views
14
function useIsMounted() {
const ref = useRef(true);
useEffect(() => {
ref.current = true;
return () => { ref.current = false; };
}, []);
return () => ref.current;
}
function useRef(v) { return { current: v }; }
function useEffect(fn) { fn(); }
const isMounted = useIsMounted();
console.log('mounted right after render?', isMounted());The hook returns a stable function that returns whether the component is currently mounted. Storing the flag in a ref keeps reads cheap and avoids triggering re-renders. The cleanup callback in useEffect is what flips the flag false on unmount, and returning a function (not the ref directly) lets call sites read the latest value at the moment they need it. Use it as if (!isMounted()) return; inside async callbacks before calling any setter.
function useSafeState(initial) {
const [state, setState] = useState(initial);
const isMounted = useIsMounted();
const safeSet = (next) => {
if (isMounted()) setState(next);
};
return [state, safeSet];
}
function useState(v) { return [v, () => {}]; }
const [data, setData] = useSafeState(null);
setData({ id: 1 });
console.log('safe state initial:', data);Sprinkling if (!isMounted()) return inside every async callback is repetitive. useSafeState wraps useState so the setter quietly no-ops after unmount, which keeps the call site identical to plain React state. This is the right tradeoff for fire-and-forget side effects (analytics, optimistic logging) where missing the final write is acceptable. For network requests where you actually want to cancel the work, prefer AbortController over silently dropping the result.
function useSafeCallback(fn) {
const isMounted = useIsMounted();
const fnRef = useRef(fn);
fnRef.current = fn;
return async (...args) => {
const result = await fnRef.current(...args);
return isMounted() ? result : undefined;
};
}
const safeFetch = useSafeCallback(async (id) => ({ id, name: 'demo' }));
safeFetch(1).then((r) => console.log('safe fetch result:', r));Sometimes you want to propagate the result back to a caller, but only if the component is still mounted. useSafeCallback resolves to undefined after unmount so downstream code can early-return. Storing fn in a ref keeps the wrapper referentially stable across renders, which avoids re-creating effects in dependent hooks. Pair this with React Query or SWR for production-grade fetching, but reach for it directly when the call lives outside any data-fetching library.
