diff --git a/.changeset/true-loops-report.md b/.changeset/true-loops-report.md new file mode 100644 index 0000000000..1925476b80 --- /dev/null +++ b/.changeset/true-loops-report.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-devtools': patch +--- + +Multiple Devtool instances sharing same state causing isolation issues diff --git a/packages/query-devtools/src/Devtools.tsx b/packages/query-devtools/src/Devtools.tsx index cceec7a3de..ac7982b32c 100644 --- a/packages/query-devtools/src/Devtools.tsx +++ b/packages/query-devtools/src/Devtools.tsx @@ -51,7 +51,13 @@ import { XCircle, } from './icons' import Explorer from './Explorer' -import { usePiPWindow, useQueryDevtoolsContext, useTheme } from './contexts' +import { + DevtoolsStateContext, + useDevtoolsState, + usePiPWindow, + useQueryDevtoolsContext, + useTheme, +} from './contexts' import { BUTTON_POSITION, DEFAULT_HEIGHT, @@ -68,6 +74,8 @@ import { import type { DevtoolsErrorType, DevtoolsPosition, + MutationCacheMap, + QueryCacheMap, QueryDevtoolsProps, } from './contexts' import type { @@ -79,7 +87,7 @@ import type { QueryState, } from '@tanstack/query-core' import type { StorageObject, StorageSetter } from '@solid-primitives/storage' -import type { Accessor, Component, JSX, Setter } from 'solid-js' +import type { Accessor, Component, JSX } from 'solid-js' interface DevtoolsPanelProps { localStore: StorageObject @@ -99,20 +107,22 @@ interface QueryStatusProps { count: number } -const [selectedQueryHash, setSelectedQueryHash] = createSignal( - null, -) -const [selectedMutationId, setSelectedMutationId] = createSignal( - null, -) -const [panelWidth, setPanelWidth] = createSignal(0) -const [offline, setOffline] = createSignal(false) - export type DevtoolsComponentType = Component & { shadowDOMTarget?: ShadowRoot } export const Devtools: Component = (props) => { + const [selectedQueryHash, setSelectedQueryHash] = createSignal( + null, + ) + const [selectedMutationId, setSelectedMutationId] = createSignal< + number | null + >(null) + const [panelWidth, setPanelWidth] = createSignal(0) + const [offline, setOffline] = createSignal(false) + const queryCacheMap: QueryCacheMap = new Map() + const mutationCacheMap: MutationCacheMap = new Map() + const theme = useTheme() const css = useQueryDevtoolsContext().shadowDOMTarget ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget }) @@ -193,7 +203,20 @@ export const Devtools: Component = (props) => { ) return ( - <> + @@ -275,7 +298,7 @@ export const Devtools: Component = (props) => { - + ) } @@ -284,6 +307,7 @@ const PiPPanel: Component<{ }> = (props) => { const pip = usePiPWindow() const theme = useTheme() + const { panelWidth, setPanelWidth } = useDevtoolsState() const css = useQueryDevtoolsContext().shadowDOMTarget ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget }) : goober.css @@ -353,6 +377,7 @@ export const ParentPanel: Component<{ children: JSX.Element }> = (props) => { const theme = useTheme() + const { panelWidth, setPanelWidth } = useDevtoolsState() const css = useQueryDevtoolsContext().shadowDOMTarget ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget }) : goober.css @@ -409,6 +434,7 @@ export const ParentPanel: Component<{ } const DraggablePanel: Component = (props) => { + const { setSelectedQueryHash, setPanelWidth, panelWidth } = useDevtoolsState() const theme = useTheme() const css = useQueryDevtoolsContext().shadowDOMTarget ? goober.css.bind({ target: useQueryDevtoolsContext().shadowDOMTarget }) @@ -690,6 +716,15 @@ export const ContentView: Component = (props) => { 'queries', ) + const { + selectedQueryHash, + offline, + setSelectedQueryHash, + selectedMutationId, + setSelectedMutationId, + panelWidth, + } = useDevtoolsState() + const sort = createMemo(() => props.localStore.sort || DEFAULT_SORT_FN_NAME) const sortOrder = createMemo( () => Number(props.localStore.sortOrder) || DEFAULT_SORT_ORDER, @@ -1383,6 +1418,7 @@ const QueryRow: Component<{ query: Query }> = (props) => { const { colors, alpha } = tokens const t = (light: string, dark: string) => (theme() === 'dark' ? dark : light) + const { selectedQueryHash, setSelectedQueryHash } = useDevtoolsState() const queryState = createSubscribeToQueryCacheBatcher( (queryCache) => @@ -1513,6 +1549,8 @@ const MutationRow: Component<{ mutation: Mutation }> = (props) => { const { colors, alpha } = tokens const t = (light: string, dark: string) => (theme() === 'dark' ? dark : light) + const { selectedMutationId, setSelectedMutationId } = useDevtoolsState() + const mutationState = createSubscribeToMutationCacheBatcher( (mutationCache) => { const mutations = mutationCache().getAll() @@ -1759,6 +1797,8 @@ const QueryStatus: Component = (props) => { const [mouseOver, setMouseOver] = createSignal(false) const [focused, setFocused] = createSignal(false) + const { selectedQueryHash, panelWidth } = useDevtoolsState() + const showLabel = createMemo(() => { if (selectedQueryHash()) { if (panelWidth() < firstBreakpoint && panelWidth() > secondBreakpoint) { @@ -1875,6 +1915,8 @@ const QueryDetails = () => { const [dataMode, setDataMode] = createSignal<'view' | 'edit'>('view') const [dataEditError, setDataEditError] = createSignal(false) + const { selectedQueryHash, setSelectedQueryHash } = useDevtoolsState() + const errorTypes = createMemo(() => { return useQueryDevtoolsContext().errorTypes || [] }) @@ -2402,6 +2444,8 @@ const MutationDetails = () => { const { colors } = tokens const t = (light: string, dark: string) => (theme() === 'dark' ? dark : light) + const { selectedMutationId } = useDevtoolsState() + const isPaused = createSubscribeToMutationCacheBatcher((mutationCache) => { const mutations = mutationCache().getAll() const mutation = mutations.find( @@ -2578,15 +2622,8 @@ const MutationDetails = () => { ) } -const queryCacheMap = new Map< - (q: Accessor) => any, - { - setter: Setter - shouldUpdate: (event: QueryCacheNotifyEvent) => boolean - } ->() - const setupQueryCacheSubscription = () => { + const { queryCacheMap } = useDevtoolsState() const queryCache = createMemo(() => { const client = useQueryDevtoolsContext().client return client.getQueryCache() @@ -2614,6 +2651,7 @@ const createSubscribeToQueryCacheBatcher = ( equalityCheck: boolean = true, shouldUpdate: (event: QueryCacheNotifyEvent) => boolean = () => true, ) => { + const { queryCacheMap } = useDevtoolsState() const queryCache = createMemo(() => { const client = useQueryDevtoolsContext().client return client.getQueryCache() @@ -2640,21 +2678,17 @@ const createSubscribeToQueryCacheBatcher = ( return value } -const mutationCacheMap = new Map< - (q: Accessor) => any, - Setter ->() - const setupMutationCacheSubscription = () => { + const { mutationCacheMap } = useDevtoolsState() const mutationCache = createMemo(() => { const client = useQueryDevtoolsContext().client return client.getMutationCache() }) const unsubscribe = mutationCache().subscribe(() => { - for (const [callback, setter] of mutationCacheMap.entries()) { + for (const [callback, value] of mutationCacheMap.entries()) { queueMicrotask(() => { - setter(callback(mutationCache)) + value.setter(callback(mutationCache)) }) } }) @@ -2671,6 +2705,7 @@ const createSubscribeToMutationCacheBatcher = ( callback: (queryCache: Accessor) => Exclude, equalityCheck: boolean = true, ) => { + const { mutationCacheMap } = useDevtoolsState() const mutationCache = createMemo(() => { const client = useQueryDevtoolsContext().client return client.getMutationCache() @@ -2685,7 +2720,9 @@ const createSubscribeToMutationCacheBatcher = ( setValue(callback(mutationCache)) }) - mutationCacheMap.set(callback, setValue) + mutationCacheMap.set(callback, { + setter: setValue, + }) onCleanup(() => { mutationCacheMap.delete(callback) diff --git a/packages/query-devtools/src/DevtoolsPanelComponent.tsx b/packages/query-devtools/src/DevtoolsPanelComponent.tsx index cae641b44a..122a69c92a 100644 --- a/packages/query-devtools/src/DevtoolsPanelComponent.tsx +++ b/packages/query-devtools/src/DevtoolsPanelComponent.tsx @@ -1,10 +1,15 @@ import { createLocalStorage } from '@solid-primitives/storage' -import { createMemo } from 'solid-js' +import { createMemo, createSignal } from 'solid-js' import { ContentView, ParentPanel } from './Devtools' import { getPreferredColorScheme } from './utils' import { THEME_PREFERENCE } from './constants' -import { PiPProvider, QueryDevtoolsContext, ThemeContext } from './contexts' -import type { Theme } from './contexts' +import { + DevtoolsStateContext, + PiPProvider, + QueryDevtoolsContext, + ThemeContext, +} from './contexts' +import type { MutationCacheMap, QueryCacheMap, Theme } from './contexts' import type { DevtoolsComponentType } from './Devtools' const DevtoolsPanelComponent: DevtoolsComponentType = (props) => { @@ -12,6 +17,17 @@ const DevtoolsPanelComponent: DevtoolsComponentType = (props) => { prefix: 'TanstackQueryDevtools', }) + const [selectedQueryHash, setSelectedQueryHash] = createSignal( + null, + ) + const [selectedMutationId, setSelectedMutationId] = createSignal< + number | null + >(null) + const [panelWidth, setPanelWidth] = createSignal(0) + const [offline, setOffline] = createSignal(false) + const queryCacheMap: QueryCacheMap = new Map() + const mutationCacheMap: MutationCacheMap = new Map() + const colorScheme = getPreferredColorScheme() const theme = createMemo(() => { @@ -24,22 +40,37 @@ const DevtoolsPanelComponent: DevtoolsComponentType = (props) => { return ( - - - - - - - + + + + + + + + ) } diff --git a/packages/query-devtools/src/contexts/DevtoolsStateContext.ts b/packages/query-devtools/src/contexts/DevtoolsStateContext.ts new file mode 100644 index 0000000000..7f9f64024d --- /dev/null +++ b/packages/query-devtools/src/contexts/DevtoolsStateContext.ts @@ -0,0 +1,45 @@ +import { createContext, useContext } from 'solid-js' +import type { Accessor, Setter } from 'solid-js' +import type { + MutationCache, + QueryCache, + QueryCacheNotifyEvent, +} from '@tanstack/query-core' + +type QueryCacheMapValue = { + setter: Setter + shouldUpdate: (event: QueryCacheNotifyEvent) => boolean +} + +type MutationCacheMapValue = { + setter: Setter +} + +export type QueryCacheMap = Map< + (q: Accessor) => any, + QueryCacheMapValue +> + +export type MutationCacheMap = Map< + (q: Accessor) => any, + MutationCacheMapValue +> + +interface DevtoolsState { + selectedQueryHash: Accessor + setSelectedQueryHash: Setter + selectedMutationId: Accessor + setSelectedMutationId: Setter + panelWidth: Accessor + setPanelWidth: Setter + offline: Accessor + setOffline: Setter + queryCacheMap: QueryCacheMap + mutationCacheMap: MutationCacheMap +} + +export const DevtoolsStateContext = createContext() + +export function useDevtoolsState() { + return useContext(DevtoolsStateContext)! +} diff --git a/packages/query-devtools/src/contexts/index.ts b/packages/query-devtools/src/contexts/index.ts index b9329a3cd9..a1b7cb5d38 100644 --- a/packages/query-devtools/src/contexts/index.ts +++ b/packages/query-devtools/src/contexts/index.ts @@ -1,3 +1,4 @@ export * from './PiPContext' export * from './QueryDevtoolsContext' export * from './ThemeContext' +export * from './DevtoolsStateContext'