useMounted
The useMounted hook tracks whether a component is currently mounted to the DOM. It returns false initially and on unmount, and true when the component is mounted. This is useful for preventing state updates on unmounted components.
Installation
npm install @craft-ui/hooks
Import
import { useMounted } from "@craft-ui/hooks";
Usage
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function DataFetcher() {
const mounted = useMounted();
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/data")
.then(res => res.json())
.then(result => {
// Only update state if component is still mounted
if (mounted) {
setData(result);
}
});
}, [mounted]);
return <div>{data ? JSON.stringify(data) : "Loading..."}</div>;
}
Returns
A boolean value that is false initially and on unmount, and true when the component is mounted.
Type Definition
function useMounted(): boolean;
Examples
Preventing Memory Leaks
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function AsyncComponent() {
const mounted = useMounted();
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch("/api/data");
const result = await response.json();
if (mounted) {
setData(result);
setLoading(false);
}
} catch (error) {
if (mounted) {
console.error(error);
setLoading(false);
}
}
};
fetchData();
}, [mounted]);
if (loading) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
}
Delayed Actions
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function NotificationBanner({ message, duration = 3000 }) {
const mounted = useMounted();
const [visible, setVisible] = useState(true);
useEffect(() => {
const timer = setTimeout(() => {
if (mounted) {
setVisible(false);
}
}, duration);
return () => clearTimeout(timer);
}, [mounted, duration]);
if (!visible) return null;
return (
<div className="notification">
{message}
</div>
);
}
Animation Delays
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function FadeInComponent({ children }) {
const mounted = useMounted();
const [show, setShow] = useState(false);
useEffect(() => {
if (mounted) {
// Delay to trigger CSS transition
const timer = setTimeout(() => setShow(true), 10);
return () => clearTimeout(timer);
}
}, [mounted]);
return (
<div
style={{
opacity: show ? 1 : 0,
transition: "opacity 0.3s ease-in",
}}
>
{children}
</div>
);
}
Conditional Event Listeners
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function KeyboardListener() {
const mounted = useMounted();
const [key, setKey] = useState("");
useEffect(() => {
const handleKeyPress = (e: KeyboardEvent) => {
if (mounted) {
setKey(e.key);
}
};
if (mounted) {
window.addEventListener("keypress", handleKeyPress);
}
return () => {
window.removeEventListener("keypress", handleKeyPress);
};
}, [mounted]);
return <div>Last key pressed: {key}</div>;
}
WebSocket Connection
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function WebSocketComponent() {
const mounted = useMounted();
const [messages, setMessages] = useState<string[]>([]);
useEffect(() => {
const ws = new WebSocket("ws://localhost:8080");
ws.onmessage = (event) => {
if (mounted) {
setMessages(prev => [...prev, event.data]);
}
};
return () => {
ws.close();
};
}, [mounted]);
return (
<ul>
{messages.map((msg, i) => (
<li key={i}>{msg}</li>
))}
</ul>
);
}
Polling Data
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function PollingComponent() {
const mounted = useMounted();
const [data, setData] = useState(null);
useEffect(() => {
const pollData = async () => {
const response = await fetch("/api/status");
const result = await response.json();
if (mounted) {
setData(result);
}
};
if (mounted) {
pollData();
const interval = setInterval(pollData, 5000);
return () => clearInterval(interval);
}
}, [mounted]);
return <div>{data ? JSON.stringify(data) : "Loading..."}</div>;
}
Form Submission
import { useMounted } from "@craft-ui/hooks";
import { useState } from "react";
function FormComponent() {
const mounted = useMounted();
const [submitting, setSubmitting] = useState(false);
const [success, setSuccess] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setSubmitting(true);
try {
await fetch("/api/submit", { method: "POST" });
if (mounted) {
setSuccess(true);
setSubmitting(false);
}
} catch (error) {
if (mounted) {
setSubmitting(false);
}
}
};
return (
<form onSubmit={handleSubmit}>
<button type="submit" disabled={submitting}>
{submitting ? "Submitting..." : "Submit"}
</button>
{success && <p>Success!</p>}
</form>
);
}
Common Patterns
Safe State Updates
Wrap state updates in mounted checks:
const mounted = useMounted();
useEffect(() => {
fetchData().then(result => {
if (mounted) {
setState(result);
}
});
}, [mounted]);
Cleanup Pattern
Use with cleanup functions:
const mounted = useMounted();
useEffect(() => {
const subscription = subscribe(data => {
if (mounted) {
handleData(data);
}
});
return () => subscription.unsubscribe();
}, [mounted]);
Custom Async Hook
import { useMounted } from "@craft-ui/hooks";
import { useState, useEffect } from "react";
function useAsync<T>(asyncFn: () => Promise<T>) {
const mounted = useMounted();
const [state, setState] = useState<{
loading: boolean;
data: T | null;
error: Error | null;
}>({ loading: true, data: null, error: null });
useEffect(() => {
asyncFn()
.then(data => {
if (mounted) {
setState({ loading: false, data, error: null });
}
})
.catch(error => {
if (mounted) {
setState({ loading: false, data: null, error });
}
});
}, [mounted]);
return state;
}
Use Cases
- Preventing state updates after component unmounts
- Avoiding “Can’t perform a React state update on an unmounted component” warnings
- Safely handling async operations (fetch, setTimeout, setInterval)
- Managing WebSocket connections
- Controlling animations and transitions
- Conditional event listener attachment
- Polling and real-time data updates
Notes
- The hook returns
false on initial render and after unmount
- The hook returns
true after the component mounts (after useEffect runs)
- This pattern helps prevent memory leaks and React warnings
- Always check the
mounted value before calling state setters in async callbacks
- The cleanup function automatically sets
mounted to false
- This is particularly useful for components that fetch data or use timers
- Consider using AbortController for fetch requests as an alternative cancellation method