Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/isolate-devtools-instances.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tanstack/query-devtools": patch
---

Fix multiple mounted Devtools instances (e.g. two panels pointing at two different `QueryClient`s) sharing selection, panel width, offline-mocking, and cache-subscription state through module-level signals and maps. Each Devtools instance now owns its own isolated UI/subscription state, so selecting a query, resizing, or mocking offline behavior in one panel no longer affects any other mounted panel.
51 changes: 28 additions & 23 deletions packages/query-devtools/src/Devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ import {
XCircle,
} from './icons'
import Explorer from './Explorer'
import { usePiPWindow, useQueryDevtoolsContext, useTheme } from './contexts'
import {
useDevtoolsUiContext,
usePiPWindow,
useQueryDevtoolsContext,
useTheme,
} from './contexts'
import {
BUTTON_POSITION,
DEFAULT_HEIGHT,
Expand Down Expand Up @@ -98,21 +103,13 @@ interface QueryStatusProps {
count: number
}

const [selectedQueryHash, setSelectedQueryHash] = createSignal<string | null>(
null,
)
const [selectedMutationId, setSelectedMutationId] = createSignal<number | null>(
null,
)
const [panelWidth, setPanelWidth] = createSignal(0)
const [offline, setOffline] = createSignal(false)

export type DevtoolsComponentType = Component<QueryDevtoolsProps> & {
shadowDOMTarget?: ShadowRoot
}

export const Devtools: Component<DevtoolsPanelProps> = (props) => {
const theme = useTheme()
const { setOffline } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -283,6 +280,7 @@ const PiPPanel: Component<{
}> = (props) => {
const pip = usePiPWindow()
const theme = useTheme()
const { panelWidth, setPanelWidth } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -352,6 +350,7 @@ export const ParentPanel: Component<{
children: JSX.Element
}> = (props) => {
const theme = useTheme()
const { panelWidth, setPanelWidth } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -409,6 +408,8 @@ export const ParentPanel: Component<{

const DraggablePanel: Component<DevtoolsPanelProps> = (props) => {
const theme = useTheme()
const { panelWidth, setPanelWidth, setSelectedQueryHash } =
useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -676,6 +677,14 @@ export const ContentView: Component<ContentViewProps> = (props) => {
setupMutationCacheSubscription()
let containerRef!: HTMLDivElement
const theme = useTheme()
const {
panelWidth,
selectedQueryHash,
setSelectedQueryHash,
selectedMutationId,
setSelectedMutationId,
offline,
} = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -1373,6 +1382,7 @@ export const ContentView: Component<ContentViewProps> = (props) => {

const QueryRow: Component<{ query: Query }> = (props) => {
const theme = useTheme()
const { selectedQueryHash, setSelectedQueryHash } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -1482,6 +1492,7 @@ const QueryRow: Component<{ query: Query }> = (props) => {

const MutationRow: Component<{ mutation: Mutation }> = (props) => {
const theme = useTheme()
const { selectedMutationId, setSelectedMutationId } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -1723,6 +1734,7 @@ const MutationStatusCount: Component = () => {

const QueryStatus: Component<QueryStatusProps> = (props) => {
const theme = useTheme()
const { selectedQueryHash, panelWidth } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -1838,6 +1850,7 @@ const QueryStatus: Component<QueryStatusProps> = (props) => {

const QueryDetails = () => {
const theme = useTheme()
const { selectedQueryHash, setSelectedQueryHash } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -2383,6 +2396,7 @@ const QueryDetails = () => {

const MutationDetails = () => {
const theme = useTheme()
const { selectedMutationId } = useDevtoolsUiContext()
const css = useQueryDevtoolsContext().shadowDOMTarget
? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget })
: goober.css
Expand Down Expand Up @@ -2569,15 +2583,8 @@ const MutationDetails = () => {
)
}

const queryCacheMap = new Map<
(q: Accessor<QueryCache>) => any,
{
setter: Setter<any>
shouldUpdate: (event: QueryCacheNotifyEvent) => boolean
}
>()

const setupQueryCacheSubscription = () => {
const { queryCacheMap } = useDevtoolsUiContext()
const queryCache = createMemo(() => {
const client = useQueryDevtoolsContext().client
return client.getQueryCache()
Expand Down Expand Up @@ -2605,6 +2612,7 @@ const createSubscribeToQueryCacheBatcher = <T,>(
equalityCheck: boolean = true,
shouldUpdate: (event: QueryCacheNotifyEvent) => boolean = () => true,
) => {
const { queryCacheMap } = useDevtoolsUiContext()
const queryCache = createMemo(() => {
const client = useQueryDevtoolsContext().client
return client.getQueryCache()
Expand All @@ -2631,12 +2639,8 @@ const createSubscribeToQueryCacheBatcher = <T,>(
return value
}

const mutationCacheMap = new Map<
(q: Accessor<MutationCache>) => any,
Setter<any>
>()

const setupMutationCacheSubscription = () => {
const { mutationCacheMap } = useDevtoolsUiContext()
const mutationCache = createMemo(() => {
const client = useQueryDevtoolsContext().client
return client.getMutationCache()
Expand All @@ -2662,6 +2666,7 @@ const createSubscribeToMutationCacheBatcher = <T,>(
callback: (queryCache: Accessor<MutationCache>) => Exclude<T, Function>,
equalityCheck: boolean = true,
) => {
const { mutationCacheMap } = useDevtoolsUiContext()
const mutationCache = createMemo(() => {
const client = useQueryDevtoolsContext().client
return client.getMutationCache()
Expand Down
19 changes: 13 additions & 6 deletions packages/query-devtools/src/DevtoolsComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { createMemo } from 'solid-js'
import { Devtools } from './Devtools'
import { getPreferredColorScheme } from './utils'
import { THEME_PREFERENCE } from './constants'
import { PiPProvider, QueryDevtoolsContext, ThemeContext } from './contexts'
import {
DevtoolsUiProvider,
PiPProvider,
QueryDevtoolsContext,
ThemeContext,
} from './contexts'
import type { Theme } from './contexts'
import type { DevtoolsComponentType } from './Devtools'

Expand All @@ -24,11 +29,13 @@ const DevtoolsComponent: DevtoolsComponentType = (props) => {

return (
<QueryDevtoolsContext.Provider value={props}>
<PiPProvider localStore={localStore} setLocalStore={setLocalStore}>
<ThemeContext.Provider value={theme}>
<Devtools localStore={localStore} setLocalStore={setLocalStore} />
</ThemeContext.Provider>
</PiPProvider>
<DevtoolsUiProvider>
<PiPProvider localStore={localStore} setLocalStore={setLocalStore}>
<ThemeContext.Provider value={theme}>
<Devtools localStore={localStore} setLocalStore={setLocalStore} />
</ThemeContext.Provider>
</PiPProvider>
</DevtoolsUiProvider>
</QueryDevtoolsContext.Provider>
)
}
Expand Down
41 changes: 24 additions & 17 deletions packages/query-devtools/src/DevtoolsPanelComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { createMemo } from 'solid-js'
import { ContentView, ParentPanel } from './Devtools'
import { getPreferredColorScheme } from './utils'
import { THEME_PREFERENCE } from './constants'
import { PiPProvider, QueryDevtoolsContext, ThemeContext } from './contexts'
import {
DevtoolsUiProvider,
PiPProvider,
QueryDevtoolsContext,
ThemeContext,
} from './contexts'
import type { Theme } from './contexts'
import type { DevtoolsComponentType } from './Devtools'

Expand All @@ -24,22 +29,24 @@ const DevtoolsPanelComponent: DevtoolsComponentType = (props) => {

return (
<QueryDevtoolsContext.Provider value={props}>
<PiPProvider
disabled
localStore={localStore}
setLocalStore={setLocalStore}
>
<ThemeContext.Provider value={theme}>
<ParentPanel>
<ContentView
localStore={localStore}
setLocalStore={setLocalStore}
onClose={props.onClose}
showPanelViewOnly
/>
</ParentPanel>
</ThemeContext.Provider>
</PiPProvider>
<DevtoolsUiProvider>
<PiPProvider
disabled
localStore={localStore}
setLocalStore={setLocalStore}
>
<ThemeContext.Provider value={theme}>
<ParentPanel>
<ContentView
localStore={localStore}
setLocalStore={setLocalStore}
onClose={props.onClose}
showPanelViewOnly
/>
</ParentPanel>
</ThemeContext.Provider>
</PiPProvider>
</DevtoolsUiProvider>
</QueryDevtoolsContext.Provider>
)
}
Expand Down
22 changes: 16 additions & 6 deletions packages/query-devtools/src/__tests__/Devtools.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import { QueryClient, QueryObserver, onlineManager } from '@tanstack/query-core'
import { fireEvent, render } from '@solidjs/testing-library'
import { createLocalStorage } from '@solid-primitives/storage'
import { Devtools } from '../Devtools'
import { PiPProvider, QueryDevtoolsContext, ThemeContext } from '../contexts'
import {
DevtoolsUiProvider,
PiPProvider,
QueryDevtoolsContext,
ThemeContext,
} from '../contexts'
import type { QueryDevtoolsProps } from '../contexts'

// `solid-transition-group` internally imports from
Expand Down Expand Up @@ -118,11 +123,16 @@ describe('Devtools', () => {
...overrides,
}}
>
<PiPProvider localStore={localStore} setLocalStore={setLocalStore}>
<ThemeContext.Provider value={() => 'dark'}>
<Devtools localStore={localStore} setLocalStore={setLocalStore} />
</ThemeContext.Provider>
</PiPProvider>
<DevtoolsUiProvider>
<PiPProvider localStore={localStore} setLocalStore={setLocalStore}>
<ThemeContext.Provider value={() => 'dark'}>
<Devtools
localStore={localStore}
setLocalStore={setLocalStore}
/>
</ThemeContext.Provider>
</PiPProvider>
</DevtoolsUiProvider>
</QueryDevtoolsContext.Provider>
)
})
Expand Down
Loading