TanStack/query · error
Devtools is not mounted
Error message
Devtools is not mounted
What it means
Thrown by `TanstackQueryDevtools.unmount()` when `unmount` is called on an instance whose `#isMounted` flag is `false`. The class refuses to invoke the internal Solid `#dispose` handle when nothing is mounted because that handle would be `undefined`, and calling it would either no-op misleadingly or, after a future change, throw an unrelated TypeError. It fails fast to surface the lifecycle bug.
Source
Thrown at packages/query-devtools/src/TanstackQueryDevtools.tsx:153
return errors()
},
get hideDisabledQueries() {
return hideDisabledQueries()
},
get theme() {
return theme()
},
}}
/>
)
}, el)
this.#isMounted = true
this.#dispose = dispose
}
unmount() {
if (!this.#isMounted) {
throw new Error('Devtools is not mounted')
}
this.#dispose?.()
this.#isMounted = false
}
}
export { TanstackQueryDevtools }
View on GitHub (pinned to 159982c80b)
Solutions
- Track mount state yourself and only unmount what you mounted: `useEffect(() => { devtools.mount(el); return () => { if (mounted) devtools.unmount() } })`.
- Wrap the call: `try { devtools.unmount() } catch { /* not mounted, ignore */ }` when the lifecycle is outside your control.
- Reproduce the lifecycle symmetrically — ensure every `mount()` path has exactly one matching `unmount()`.
- Switch to the framework-specific devtools component (`<ReactQueryDevtools />`) which handles this internally.
Example fix
// before - cleanup may run without a matching mount
useEffect(() => {
return () => devtools.unmount()
}, [])
// after - only unmount what we mounted
useEffect(() => {
let mounted = false
devtools.mount(el)
mounted = true
return () => { if (mounted) devtools.unmount() }
}, []) Defensive patterns
Strategy: validation
Validate before calling
// Track mount state so unmount only runs when mounted
let mounted = false
useEffect(() => {
devtools.mount(el); mounted = true
return () => { if (mounted) { devtools.unmount(); mounted = false } }
}, []) Type guard
const isMounted = (d: { mount(el: HTMLElement): unknown; unmount(): void }) =>
Boolean((d as unknown as { _isMounted?: boolean })._isMounted) Try / catch
try {
devtools.unmount()
} catch (e) {
if (e.message === 'Devtools is not mounted') {
// no-op: nothing to clean up
} else throw e
} Prevention
- Keep mount/unmount strictly symmetric: exactly one unmount per successful mount.
- Track your own mounted flag when the lifecycle is outside your control (StrictMode, HMR).
- Prefer <ReactQueryDevtools /> over the vanilla class to avoid manual lifecycle bookkeeping.
- Never register a cleanup unmount without registering the matching mount in the same effect.
When it happens
Trigger: Calling `devtools.unmount()` without a prior successful `mount()` on the same instance, or calling `unmount()` twice (the flag is reset to `false` after the first call). Frequent in effect cleanups that run on unmount of a component that never mounted the devtools, or in StrictMode where the dev cleanup fires for an effect whose dev mount was skipped/failed.
Common situations: React StrictMode double-invoking cleanups; conditionally-rendered devtools where the cleanup runs but the mount branch was not taken; calling `unmount` in a `disconnectCallback` / `willDestroy` hook of an element that was never connected; HMR re-running cleanup on a stale instance.
Related errors
- Devtools is already mounted
- Devtools is not mounted
- Devtools is already mounted
- usePiPWindow must be used within a PiPProvider
- No QueryClient found
AI-assisted analysis of TanStack/query@159982c80b (2026-08-12).
Data as JSON: /api/errors/6afc6b6f1647ff53.
Report an issue: GitHub.