TanStack/query · error
Devtools is already mounted
Error message
Devtools is already mounted
What it means
Thrown by `TanstackQueryDevtools.mount()` (the non-framework, framework-agnostic devtools class) when `mount` is called on an instance that is already mounted. The class tracks a single `#isMounted` flag and renders into exactly one DOM element via Solid's `render`; calling `mount` twice would create a leaked Solid disposal tree and a duplicate panel, so it throws to fail fast.
Source
Thrown at packages/query-devtools/src/TanstackQueryDevtools.tsx:95
setInitialIsOpen(isOpen: boolean) {
this.#initialIsOpen[1](isOpen)
}
setErrorTypes(errorTypes: Array<DevtoolsErrorType>) {
this.#errorTypes[1](errorTypes)
}
setClient(client: QueryClient) {
this.#client[1](client)
}
setTheme(theme?: Theme) {
this.#theme[1](theme)
}
mount<T extends HTMLElement>(el: T) {
if (this.#isMounted) {
throw new Error('Devtools is already mounted')
}
const dispose = render(() => {
const [btnPosition] = this.#buttonPosition
const [pos] = this.#position
const [isOpen] = this.#initialIsOpen
const [errors] = this.#errorTypes
const [hideDisabledQueries] = this.#hideDisabledQueries
const [queryClient] = this.#client
const [theme] = this.#theme
let Devtools: DevtoolsComponentType
if (this.#Component) {
Devtools = this.#Component
} else {
Devtools = lazy(() => import('./DevtoolsComponent'))
this.#Component = Devtools
}
View on GitHub (pinned to 159982c80b)
Solutions
- Call `unmount()` in the same lifecycle's cleanup before `mount()` can re-run, e.g. `useEffect(() => { devtools.mount(el); return () => devtools.unmount() }, [])`.
- Guard the call yourself: `if (!devtoolsMounted) { devtools.mount(el); devtoolsMounted = true }`.
- Create a fresh `new TanstackQueryDevtools(config)` instance for each mount target instead of reusing one.
- For React, prefer the `<ReactQueryDevtools />` component over the vanilla class — it manages mount/unmount for you.
Example fix
// before - double mount in StrictMode
useEffect(() => {
devtools.mount(el)
}, [])
// after - pair mount with unmount
useEffect(() => {
devtools.mount(el)
return () => devtools.unmount()
}, []) Defensive patterns
Strategy: validation
Validate before calling
// Track mount state alongside the devtools instance
let devtoolsMounted = false
const ensureMounted = (devtools, el) => {
if (devtoolsMounted) return
devtools.mount(el)
devtoolsMounted = true
} Type guard
// If you mirror the class, expose its flag instead of guessing
type MountedDevtools = { mount(el: HTMLElement): void; unmount(): void }
const isMountable = (d: MountedDevtools & { isMounted?: boolean }) =>
!(d as { __mounted?: boolean }).__mounted Try / catch
try {
devtools.mount(el)
} catch (e) {
if (e.message === 'Devtools is already mounted') {
// already mounted, nothing to do
} else throw e
} Prevention
- Always pair mount() with unmount() in the same effect lifecycle.
- Prefer the framework-specific <ReactQueryDevtools /> component which handles mount/unmount internally.
- Create a fresh TanstackQueryDevtools instance per mount target rather than reusing one.
- Guard against React StrictMode double-mount by tracking your own mounted flag.
When it happens
Trigger: Calling `devtools.mount(el)` twice on the same `TanstackQueryDevtools` instance without an intervening `unmount()`. Common in React StrictMode (which double-invokes effects in dev) when the devtools is instantiated outside the effect and `mount` is called in the effect body, or when a micro-frontend / widget host remounts the devtools on route change without disposing.
Common situations: React 18 StrictMode double-mounting effects; HMR reloading a module that holds a long-lived devtools instance; manually integrating the vanilla devtools class into a lifecycle that re-runs (e.g. inside `useEffect` without an unmount cleanup, or inside a `connectedCallback` that fires twice).
Related errors
- Devtools is not mounted
- Devtools is already mounted
- Devtools is not 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/f12920b88a410ee1.
Report an issue: GitHub.