@tanstack/react-query-devtools 5.0.0-alpha.5 → 5.0.0-alpha.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/build/lib/__tests__/devtools.test.d.ts +1 -1
  2. package/build/lib/__tests__/devtools.test.d.ts.map +1 -0
  3. package/build/lib/__tests__/utils.d.ts +2 -1
  4. package/build/lib/__tests__/utils.d.ts.map +1 -0
  5. package/build/lib/devtools.d.ts +11 -75
  6. package/build/lib/devtools.d.ts.map +1 -0
  7. package/build/lib/devtools.js +49 -783
  8. package/build/lib/devtools.js.map +1 -1
  9. package/build/lib/devtools.mjs +52 -766
  10. package/build/lib/devtools.mjs.map +1 -1
  11. package/build/lib/index.d.ts +1 -1
  12. package/build/lib/index.d.ts.map +1 -0
  13. package/build/lib/index.js +8 -4
  14. package/build/lib/index.js.map +1 -1
  15. package/build/lib/index.mjs +10 -5
  16. package/build/lib/index.mjs.map +1 -1
  17. package/build/lib/{index.prod.d.ts → index.prod.d.ts/index.d.ts} +1 -1
  18. package/build/lib/index.prod.js +7923 -1389
  19. package/build/lib/index.prod.js.map +1 -1
  20. package/build/lib/index.prod.mjs +7857 -1303
  21. package/build/lib/index.prod.mjs.map +1 -1
  22. package/package.json +17 -16
  23. package/src/__tests__/devtools.test.tsx +1 -861
  24. package/src/devtools.tsx +61 -1164
  25. package/src/index.ts +6 -6
  26. package/build/lib/Explorer.d.ts +0 -50
  27. package/build/lib/Explorer.esm.js +0 -253
  28. package/build/lib/Explorer.esm.js.map +0 -1
  29. package/build/lib/Explorer.js +0 -287
  30. package/build/lib/Explorer.js.map +0 -1
  31. package/build/lib/Explorer.mjs +0 -253
  32. package/build/lib/Explorer.mjs.map +0 -1
  33. package/build/lib/Logo.d.ts +0 -1
  34. package/build/lib/Logo.esm.js +0 -32
  35. package/build/lib/Logo.esm.js.map +0 -1
  36. package/build/lib/Logo.js +0 -55
  37. package/build/lib/Logo.js.map +0 -1
  38. package/build/lib/Logo.mjs +0 -32
  39. package/build/lib/Logo.mjs.map +0 -1
  40. package/build/lib/__tests__/Explorer.test.d.ts +0 -1
  41. package/build/lib/_virtual/_rollupPluginBabelHelpers.esm.js +0 -17
  42. package/build/lib/_virtual/_rollupPluginBabelHelpers.esm.js.map +0 -1
  43. package/build/lib/_virtual/_rollupPluginBabelHelpers.js +0 -19
  44. package/build/lib/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
  45. package/build/lib/_virtual/_rollupPluginBabelHelpers.mjs +0 -17
  46. package/build/lib/_virtual/_rollupPluginBabelHelpers.mjs.map +0 -1
  47. package/build/lib/devtools.esm.js +0 -773
  48. package/build/lib/devtools.esm.js.map +0 -1
  49. package/build/lib/index.esm.js +0 -11
  50. package/build/lib/index.esm.js.map +0 -1
  51. package/build/lib/index.prod.esm.js +0 -1431
  52. package/build/lib/index.prod.esm.js.map +0 -1
  53. package/build/lib/noop.d.ts +0 -2
  54. package/build/lib/screenreader.d.ts +0 -3
  55. package/build/lib/screenreader.esm.js +0 -17
  56. package/build/lib/screenreader.esm.js.map +0 -1
  57. package/build/lib/screenreader.js +0 -40
  58. package/build/lib/screenreader.js.map +0 -1
  59. package/build/lib/screenreader.mjs +0 -17
  60. package/build/lib/screenreader.mjs.map +0 -1
  61. package/build/lib/styledComponents.d.ts +0 -9
  62. package/build/lib/styledComponents.esm.js +0 -93
  63. package/build/lib/styledComponents.esm.js.map +0 -1
  64. package/build/lib/styledComponents.js +0 -102
  65. package/build/lib/styledComponents.js.map +0 -1
  66. package/build/lib/styledComponents.mjs +0 -93
  67. package/build/lib/styledComponents.mjs.map +0 -1
  68. package/build/lib/theme.d.ts +0 -36
  69. package/build/lib/theme.esm.js +0 -32
  70. package/build/lib/theme.esm.js.map +0 -1
  71. package/build/lib/theme.js +0 -55
  72. package/build/lib/theme.js.map +0 -1
  73. package/build/lib/theme.mjs +0 -32
  74. package/build/lib/theme.mjs.map +0 -1
  75. package/build/lib/useLocalStorage.d.ts +0 -1
  76. package/build/lib/useLocalStorage.esm.js +0 -40
  77. package/build/lib/useLocalStorage.esm.js.map +0 -1
  78. package/build/lib/useLocalStorage.js +0 -63
  79. package/build/lib/useLocalStorage.js.map +0 -1
  80. package/build/lib/useLocalStorage.mjs +0 -40
  81. package/build/lib/useLocalStorage.mjs.map +0 -1
  82. package/build/lib/useMediaQuery.d.ts +0 -1
  83. package/build/lib/useMediaQuery.esm.js +0 -36
  84. package/build/lib/useMediaQuery.esm.js.map +0 -1
  85. package/build/lib/useMediaQuery.js +0 -59
  86. package/build/lib/useMediaQuery.js.map +0 -1
  87. package/build/lib/useMediaQuery.mjs +0 -36
  88. package/build/lib/useMediaQuery.mjs.map +0 -1
  89. package/build/lib/utils.d.ts +0 -79
  90. package/build/lib/utils.esm.js +0 -184
  91. package/build/lib/utils.esm.js.map +0 -1
  92. package/build/lib/utils.js +0 -218
  93. package/build/lib/utils.js.map +0 -1
  94. package/build/lib/utils.mjs +0 -184
  95. package/build/lib/utils.mjs.map +0 -1
  96. package/build/umd/index.development.js +0 -3099
  97. package/build/umd/index.development.js.map +0 -1
  98. package/build/umd/index.production.js +0 -2
  99. package/build/umd/index.production.js.map +0 -1
  100. package/src/Explorer.tsx +0 -385
  101. package/src/Logo.tsx +0 -32
  102. package/src/__tests__/Explorer.test.tsx +0 -150
  103. package/src/noop.ts +0 -7
  104. package/src/screenreader.tsx +0 -16
  105. package/src/styledComponents.ts +0 -108
  106. package/src/theme.tsx +0 -33
  107. package/src/useLocalStorage.ts +0 -52
  108. package/src/useMediaQuery.ts +0 -34
  109. package/src/utils.ts +0 -319
package/src/devtools.tsx CHANGED
@@ -1,1198 +1,95 @@
1
1
  'use client'
2
- import * as React from 'react'
2
+ import { useRef, useState, useEffect } from 'react'
3
+ import type { QueryClient } from '@tanstack/react-query'
4
+ import { useQueryClient, onlineManager } from '@tanstack/react-query'
3
5
  import type {
4
- QueryCache,
5
- QueryClient,
6
- QueryKey as QueryKeyType,
7
- } from '@tanstack/react-query'
8
- import {
9
- useQueryClient,
10
- onlineManager,
11
- notifyManager,
12
- } from '@tanstack/react-query'
13
- import { rankItem } from '@tanstack/match-sorter-utils'
14
- import useLocalStorage from './useLocalStorage'
15
- import {
16
- isVerticalSide,
17
- sortFns,
18
- useIsMounted,
19
- getSidePanelStyle,
20
- minPanelSize,
21
- getResizeHandleStyle,
22
- getSidedProp,
23
- defaultPanelSize,
24
- displayValue,
25
- } from './utils'
26
- import type { Corner, Side } from './utils'
27
- import {
28
- Panel,
29
- QueryKeys,
30
- QueryKey,
31
- Button,
32
- Code,
33
- Input,
34
- Select,
35
- ActiveQueryPanel,
36
- } from './styledComponents'
37
- import ScreenReader from './screenreader'
38
- import { ThemeProvider, defaultTheme as theme } from './theme'
39
- import { getQueryStatusLabel, getQueryStatusColor } from './utils'
40
- import Explorer from './Explorer'
41
- import Logo from './Logo'
6
+ DevtoolsButtonPosition,
7
+ DevtoolsPosition,
8
+ DevToolsErrorType,
9
+ } from '@tanstack/query-devtools'
10
+ import { TanstackQueryDevtools } from '@tanstack/query-devtools'
11
+ import React from 'react'
42
12
 
43
13
  export interface DevtoolsOptions {
44
14
  /**
45
15
  * Set this true if you want the dev tools to default to being open
46
16
  */
47
17
  initialIsOpen?: boolean
48
- /**
49
- * Use this to add props to the panel. For example, you can add className, style (merge and override default style), etc.
50
- */
51
- panelProps?: React.ComponentPropsWithoutRef<'div'>
52
- /**
53
- * Use this to add props to the close button. For example, you can add className, style (merge and override default style), onClick (extend default handler), etc.
54
- */
55
- closeButtonProps?: React.ComponentPropsWithoutRef<'button'>
56
- /**
57
- * Use this to add props to the toggle button. For example, you can add className, style (merge and override default style), onClick (extend default handler), etc.
58
- */
59
- toggleButtonProps?: React.ComponentPropsWithoutRef<'button'>
60
18
  /**
61
19
  * The position of the React Query logo to open and close the devtools panel.
20
+ * 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
62
21
  * Defaults to 'bottom-left'.
63
22
  */
64
- position?: Corner
23
+ buttonPosition?: DevtoolsButtonPosition
65
24
  /**
66
25
  * The position of the React Query devtools panel.
26
+ * 'top' | 'bottom' | 'left' | 'right'
67
27
  * Defaults to 'bottom'.
68
28
  */
69
- panelPosition?: Side
70
- /**
71
- * Use this to render the devtools inside a different type of container element for a11y purposes.
72
- * Any string which corresponds to a valid intrinsic JSX element is allowed.
73
- * Defaults to 'aside'.
74
- */
75
- containerElement?: string | any
76
- /**
77
- * nonce for style element for CSP
78
- */
79
- styleNonce?: string
29
+ position?: DevtoolsPosition
80
30
  /**
81
31
  * Custom instance of QueryClient
82
32
  */
83
- queryClient?: QueryClient
84
- }
85
-
86
- interface DevtoolsPanelOptions {
87
- /**
88
- * The standard React style object used to style a component with inline styles
89
- */
90
- style?: React.CSSProperties
91
- /**
92
- * The standard React className property used to style a component with classes
93
- */
94
- className?: string
33
+ client?: QueryClient
95
34
  /**
96
- * A boolean variable indicating whether the panel is open or closed
35
+ * Use this so you can define custom errors that can be shown in the devtools.
97
36
  */
98
- isOpen?: boolean
99
- /**
100
- * nonce for style element for CSP
101
- */
102
- styleNonce?: string
103
- /**
104
- * A function that toggles the open and close state of the panel
105
- */
106
- setIsOpen: (isOpen: boolean) => void
107
- /**
108
- * Handles the opening and closing the devtools panel
109
- */
110
- onDragStart: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
111
- /**
112
- * The position of the React Query devtools panel.
113
- * Defaults to 'bottom'.
114
- */
115
- position?: Side
116
- /**
117
- * Handles the panel position select change
118
- */
119
- onPositionChange?: (side: Side) => void
120
- /**
121
- * Show a close button inside the panel
122
- */
123
- showCloseButton?: boolean
124
- /**
125
- * Use this to add props to the close button. For example, you can add className, style (merge and override default style), onClick (extend default handler), etc.
126
- */
127
- closeButtonProps?: React.ComponentPropsWithoutRef<'button'>
128
- /**
129
- * Custom instance of QueryClient
130
- */
131
- queryClient?: QueryClient
37
+ errorTypes?: DevToolsErrorType[]
132
38
  }
133
39
 
134
- export function ReactQueryDevtools({
135
- initialIsOpen,
136
- panelProps = {},
137
- closeButtonProps = {},
138
- toggleButtonProps = {},
139
- position = 'bottom-left',
140
- containerElement: Container = 'aside',
141
- queryClient,
142
- styleNonce,
143
- panelPosition: initialPanelPosition = 'bottom',
144
- }: DevtoolsOptions): React.ReactElement | null {
145
- const rootRef = React.useRef<HTMLDivElement>(null)
146
- const panelRef = React.useRef<HTMLDivElement>(null)
147
- const [isOpen, setIsOpen] = useLocalStorage(
148
- 'reactQueryDevtoolsOpen',
149
- initialIsOpen,
150
- )
151
- const [devtoolsHeight, setDevtoolsHeight] = useLocalStorage<number>(
152
- 'reactQueryDevtoolsHeight',
153
- defaultPanelSize,
154
- )
155
- const [devtoolsWidth, setDevtoolsWidth] = useLocalStorage<number>(
156
- 'reactQueryDevtoolsWidth',
157
- defaultPanelSize,
158
- )
159
-
160
- const [panelPosition = 'bottom', setPanelPosition] = useLocalStorage<Side>(
161
- 'reactQueryDevtoolsPanelPosition',
162
- initialPanelPosition,
163
- )
164
-
165
- const [isResolvedOpen, setIsResolvedOpen] = React.useState(false)
166
- const [isResizing, setIsResizing] = React.useState(false)
167
- const isMounted = useIsMounted()
168
-
169
- const handleDragStart = (
170
- panelElement: HTMLDivElement | null,
171
- startEvent: React.MouseEvent<HTMLDivElement, MouseEvent>,
172
- ) => {
173
- if (!panelElement) return
174
- if (startEvent.button !== 0) return // Only allow left click for drag
175
- const isVertical = isVerticalSide(panelPosition)
176
- setIsResizing(true)
177
-
178
- const { height, width } = panelElement.getBoundingClientRect()
179
- const startX = startEvent.clientX
180
- const startY = startEvent.clientY
181
- let newSize = 0
182
-
183
- const run = (moveEvent: MouseEvent) => {
184
- // prevent mouse selecting stuff with mouse drag
185
- moveEvent.preventDefault()
186
-
187
- // calculate the correct size based on mouse position and current panel position
188
- // hint: it is different formula for the opposite sides
189
- if (isVertical) {
190
- newSize =
191
- width +
192
- (panelPosition === 'right'
193
- ? startX - moveEvent.clientX
194
- : moveEvent.clientX - startX)
195
- setDevtoolsWidth(newSize)
196
- } else {
197
- newSize =
198
- height +
199
- (panelPosition === 'bottom'
200
- ? startY - moveEvent.clientY
201
- : moveEvent.clientY - startY)
202
- setDevtoolsHeight(newSize)
203
- }
204
-
205
- if (newSize < minPanelSize) {
206
- setIsOpen(false)
207
- } else {
208
- setIsOpen(true)
209
- }
40
+ export function ReactQueryDevtools(
41
+ props: DevtoolsOptions,
42
+ ): React.ReactElement | null {
43
+ const queryClient = useQueryClient()
44
+ const client = props.client || queryClient
45
+ const ref = useRef<HTMLDivElement>(null)
46
+ const { buttonPosition, position, initialIsOpen, errorTypes } = props
47
+ const [devtools] = useState(
48
+ new TanstackQueryDevtools({
49
+ client: client,
50
+ queryFlavor: 'React Query',
51
+ version: '5',
52
+ onlineManager,
53
+ buttonPosition,
54
+ position,
55
+ initialIsOpen,
56
+ errorTypes,
57
+ }),
58
+ )
59
+
60
+ useEffect(() => {
61
+ devtools.setClient(client)
62
+ }, [client, devtools])
63
+
64
+ useEffect(() => {
65
+ if (buttonPosition) {
66
+ devtools.setButtonPosition(buttonPosition)
210
67
  }
68
+ }, [buttonPosition, devtools])
211
69
 
212
- const unsub = () => {
213
- if (isResizing) {
214
- setIsResizing(false)
215
- }
216
-
217
- document.removeEventListener('mousemove', run, false)
218
- document.removeEventListener('mouseUp', unsub, false)
70
+ useEffect(() => {
71
+ if (position) {
72
+ devtools.setPosition(position)
219
73
  }
74
+ }, [position, devtools])
220
75
 
221
- document.addEventListener('mousemove', run, false)
222
- document.addEventListener('mouseup', unsub, false)
223
- }
224
-
225
- React.useEffect(() => {
226
- setIsResolvedOpen(isOpen ?? false)
227
- }, [isOpen, isResolvedOpen, setIsResolvedOpen])
228
-
229
- // Toggle panel visibility before/after transition (depending on direction).
230
- // Prevents focusing in a closed panel.
231
- React.useEffect(() => {
232
- const ref = panelRef.current
233
- if (ref) {
234
- const handlePanelTransitionStart = () => {
235
- if (isResolvedOpen) {
236
- ref.style.visibility = 'visible'
237
- }
238
- }
239
-
240
- const handlePanelTransitionEnd = () => {
241
- if (!isResolvedOpen) {
242
- ref.style.visibility = 'hidden'
243
- }
244
- }
76
+ useEffect(() => {
77
+ devtools.setInitialIsOpen(initialIsOpen || false)
78
+ }, [initialIsOpen, devtools])
245
79
 
246
- ref.addEventListener('transitionstart', handlePanelTransitionStart)
247
- ref.addEventListener('transitionend', handlePanelTransitionEnd)
80
+ useEffect(() => {
81
+ devtools.setErrorTypes(errorTypes || [])
82
+ }, [errorTypes, devtools])
248
83
 
249
- return () => {
250
- ref.removeEventListener('transitionstart', handlePanelTransitionStart)
251
- ref.removeEventListener('transitionend', handlePanelTransitionEnd)
252
- }
84
+ useEffect(() => {
85
+ if (ref.current) {
86
+ devtools.mount(ref.current)
253
87
  }
254
- return
255
- }, [isResolvedOpen])
256
-
257
- React.useEffect(() => {
258
- if (isResolvedOpen && rootRef.current?.parentElement) {
259
- const { parentElement } = rootRef.current
260
- const styleProp = getSidedProp('padding', panelPosition)
261
- const isVertical = isVerticalSide(panelPosition)
262
-
263
- const previousPaddings = (({
264
- padding,
265
- paddingTop,
266
- paddingBottom,
267
- paddingLeft,
268
- paddingRight,
269
- }) => ({
270
- padding,
271
- paddingTop,
272
- paddingBottom,
273
- paddingLeft,
274
- paddingRight,
275
- }))(parentElement.style)
276
-
277
- const run = () => {
278
- // reset the padding
279
- parentElement.style.padding = '0px'
280
- parentElement.style.paddingTop = '0px'
281
- parentElement.style.paddingBottom = '0px'
282
- parentElement.style.paddingLeft = '0px'
283
- parentElement.style.paddingRight = '0px'
284
- // set the new padding based on the new panel position
285
-
286
- parentElement.style[styleProp] = `${
287
- isVertical ? devtoolsWidth : devtoolsHeight
288
- }px`
289
- }
290
-
291
- run()
292
-
293
- if (typeof window !== 'undefined') {
294
- window.addEventListener('resize', run)
295
-
296
- return () => {
297
- window.removeEventListener('resize', run)
298
- Object.entries(previousPaddings).forEach(
299
- ([property, previousValue]) => {
300
- parentElement.style[property as keyof typeof previousPaddings] =
301
- previousValue
302
- },
303
- )
304
- }
305
- }
306
- }
307
- return
308
- }, [isResolvedOpen, panelPosition, devtoolsHeight, devtoolsWidth])
309
-
310
- const { style: panelStyle = {}, ...otherPanelProps } = panelProps
311
-
312
- const {
313
- style: toggleButtonStyle = {},
314
- onClick: onToggleClick,
315
- ...otherToggleButtonProps
316
- } = toggleButtonProps
317
-
318
- // get computed style based on panel position
319
- const style = getSidePanelStyle({
320
- position: panelPosition,
321
- devtoolsTheme: theme,
322
- isOpen: isResolvedOpen,
323
- height: devtoolsHeight,
324
- width: devtoolsWidth,
325
- isResizing,
326
- panelStyle,
327
- })
328
88
 
329
- // Do not render on the server
330
- if (!isMounted()) return null
331
-
332
- return (
333
- <Container
334
- ref={rootRef}
335
- className="ReactQueryDevtools"
336
- aria-label="React Query Devtools"
337
- >
338
- <ThemeProvider theme={theme}>
339
- <ReactQueryDevtoolsPanel
340
- ref={panelRef as any}
341
- queryClient={queryClient}
342
- styleNonce={styleNonce}
343
- position={panelPosition}
344
- onPositionChange={setPanelPosition}
345
- showCloseButton
346
- closeButtonProps={closeButtonProps}
347
- {...otherPanelProps}
348
- style={style}
349
- isOpen={isResolvedOpen}
350
- setIsOpen={setIsOpen}
351
- onDragStart={(e) => handleDragStart(panelRef.current, e)}
352
- />
353
- </ThemeProvider>
354
- {!isResolvedOpen ? (
355
- <button
356
- type="button"
357
- {...otherToggleButtonProps}
358
- aria-label="Open React Query Devtools"
359
- aria-controls="ReactQueryDevtoolsPanel"
360
- aria-haspopup="true"
361
- aria-expanded="false"
362
- onClick={(e) => {
363
- setIsOpen(true)
364
- onToggleClick?.(e)
365
- }}
366
- style={{
367
- background: 'none',
368
- border: 0,
369
- padding: 0,
370
- position: 'fixed',
371
- zIndex: 99999,
372
- display: 'inline-flex',
373
- fontSize: '1.5em',
374
- margin: '.5em',
375
- cursor: 'pointer',
376
- width: 'fit-content',
377
- ...(position === 'top-right'
378
- ? {
379
- top: '0',
380
- right: '0',
381
- }
382
- : position === 'top-left'
383
- ? {
384
- top: '0',
385
- left: '0',
386
- }
387
- : position === 'bottom-right'
388
- ? {
389
- bottom: '0',
390
- right: '0',
391
- }
392
- : {
393
- bottom: '0',
394
- left: '0',
395
- }),
396
- ...toggleButtonStyle,
397
- }}
398
- >
399
- <Logo aria-hidden />
400
- <ScreenReader text="Open React Query Devtools" />
401
- </button>
402
- ) : null}
403
- </Container>
404
- )
405
- }
406
-
407
- const useSubscribeToQueryCache = <T,>(
408
- queryCache: QueryCache,
409
- getSnapshot: () => T,
410
- skip: boolean = false,
411
- ): T => {
412
- return React.useSyncExternalStore(
413
- React.useCallback(
414
- (onStoreChange) => {
415
- if (!skip)
416
- return queryCache.subscribe(notifyManager.batchCalls(onStoreChange))
417
- return () => {
418
- return
419
- }
420
- },
421
- [queryCache, skip],
422
- ),
423
- getSnapshot,
424
- getSnapshot,
425
- )
426
- }
427
-
428
- export const ReactQueryDevtoolsPanel = React.forwardRef<
429
- HTMLDivElement,
430
- DevtoolsPanelOptions
431
- >(function ReactQueryDevtoolsPanel(props, ref): React.ReactElement {
432
- const {
433
- isOpen = true,
434
- styleNonce,
435
- setIsOpen,
436
- queryClient,
437
- onDragStart,
438
- onPositionChange,
439
- showCloseButton,
440
- position,
441
- closeButtonProps = {},
442
- ...panelProps
443
- } = props
444
-
445
- const { onClick: onCloseClick, ...otherCloseButtonProps } = closeButtonProps
446
-
447
- const client = useQueryClient(queryClient)
448
- const queryCache = client.getQueryCache()
449
-
450
- const [sort, setSort] = useLocalStorage(
451
- 'reactQueryDevtoolsSortFn',
452
- Object.keys(sortFns)[0],
453
- )
454
-
455
- const [filter, setFilter] = useLocalStorage('reactQueryDevtoolsFilter', '')
456
-
457
- const [baseSort, setBaseSort] = useLocalStorage(
458
- 'reactQueryDevtoolsBaseSort',
459
- 1,
460
- )
461
-
462
- const sortFn = React.useMemo(() => sortFns[sort as string], [sort])
463
-
464
- const queriesCount = useSubscribeToQueryCache(
465
- queryCache,
466
- () => queryCache.getAll().length,
467
- !isOpen,
468
- )
469
-
470
- const [activeQueryHash, setActiveQueryHash] = useLocalStorage(
471
- 'reactQueryDevtoolsActiveQueryHash',
472
- '',
473
- )
474
-
475
- const queries = React.useMemo(() => {
476
- const unsortedQueries = queryCache.getAll()
477
-
478
- if (queriesCount === 0) {
479
- return []
89
+ return () => {
90
+ devtools.unmount()
480
91
  }
92
+ }, [devtools])
481
93
 
482
- const filtered = filter
483
- ? unsortedQueries.filter(
484
- (item) => rankItem(item.queryHash, filter).passed,
485
- )
486
- : [...unsortedQueries]
487
-
488
- const sorted = sortFn
489
- ? filtered.sort((a, b) => sortFn(a, b) * (baseSort as number))
490
- : filtered
491
-
492
- return sorted
493
- }, [baseSort, sortFn, filter, queriesCount, queryCache])
494
-
495
- const [isMockOffline, setMockOffline] = React.useState(false)
496
-
497
- return (
498
- <ThemeProvider theme={theme}>
499
- <Panel
500
- ref={ref}
501
- className="ReactQueryDevtoolsPanel"
502
- aria-label="React Query Devtools Panel"
503
- id="ReactQueryDevtoolsPanel"
504
- {...panelProps}
505
- style={{
506
- height: defaultPanelSize,
507
- position: 'relative',
508
- ...panelProps.style,
509
- }}
510
- >
511
- <style
512
- nonce={styleNonce}
513
- dangerouslySetInnerHTML={{
514
- __html: `
515
- .ReactQueryDevtoolsPanel * {
516
- scrollbar-color: ${theme.backgroundAlt} ${theme.gray};
517
- }
518
-
519
- .ReactQueryDevtoolsPanel *::-webkit-scrollbar, .ReactQueryDevtoolsPanel scrollbar {
520
- width: 1em;
521
- height: 1em;
522
- }
523
-
524
- .ReactQueryDevtoolsPanel *::-webkit-scrollbar-track, .ReactQueryDevtoolsPanel scrollbar-track {
525
- background: ${theme.backgroundAlt};
526
- }
527
-
528
- .ReactQueryDevtoolsPanel *::-webkit-scrollbar-thumb, .ReactQueryDevtoolsPanel scrollbar-thumb {
529
- background: ${theme.gray};
530
- border-radius: .5em;
531
- border: 3px solid ${theme.backgroundAlt};
532
- }
533
- `,
534
- }}
535
- />
536
- <div
537
- style={getResizeHandleStyle(position)}
538
- onMouseDown={onDragStart}
539
- ></div>
540
-
541
- {isOpen && (
542
- <div
543
- style={{
544
- flex: '1 1 500px',
545
- minHeight: '40%',
546
- maxHeight: '100%',
547
- overflow: 'auto',
548
- borderRight: `1px solid ${theme.grayAlt}`,
549
- display: 'flex',
550
- flexDirection: 'column',
551
- }}
552
- >
553
- <div
554
- style={{
555
- padding: '.5em',
556
- background: theme.backgroundAlt,
557
- display: 'flex',
558
- justifyContent: 'space-between',
559
- alignItems: 'center',
560
- }}
561
- >
562
- <button
563
- type="button"
564
- aria-label="Close React Query Devtools"
565
- aria-controls="ReactQueryDevtoolsPanel"
566
- aria-haspopup="true"
567
- aria-expanded="true"
568
- onClick={() => setIsOpen(false)}
569
- style={{
570
- display: 'inline-flex',
571
- background: 'none',
572
- border: 0,
573
- padding: 0,
574
- marginRight: '.5em',
575
- cursor: 'pointer',
576
- }}
577
- >
578
- <Logo aria-hidden />
579
- <ScreenReader text="Close React Query Devtools" />
580
- </button>
581
- <div
582
- style={{
583
- display: 'flex',
584
- flexDirection: 'column',
585
- }}
586
- >
587
- <div
588
- style={{
589
- display: 'flex',
590
- justifyContent: 'space-between',
591
- alignItems: 'center',
592
- marginBottom: '.5em',
593
- }}
594
- >
595
- <QueryStatusCount queryCache={queryCache} />
596
- {position && onPositionChange ? (
597
- <Select
598
- aria-label="Panel position"
599
- value={position}
600
- style={{ marginInlineStart: '.5em' }}
601
- onChange={(e) => onPositionChange(e.target.value as Side)}
602
- >
603
- <option value="left">Left</option>
604
- <option value="right">Right</option>
605
- <option value="top">Top</option>
606
- <option value="bottom">Bottom</option>
607
- </Select>
608
- ) : null}
609
- </div>
610
- <div
611
- style={{
612
- display: 'flex',
613
- alignItems: 'center',
614
- }}
615
- >
616
- <Input
617
- placeholder="Filter"
618
- aria-label="Filter by queryhash"
619
- value={filter ?? ''}
620
- onChange={(e) => setFilter(e.target.value)}
621
- onKeyDown={(e) => {
622
- if (e.key === 'Escape') setFilter('')
623
- }}
624
- style={{
625
- flex: '1',
626
- marginRight: '.5em',
627
- width: '100%',
628
- }}
629
- />
630
- <Select
631
- aria-label="Sort queries"
632
- value={sort}
633
- onChange={(e) => setSort(e.target.value)}
634
- style={{
635
- flex: '1',
636
- minWidth: 75,
637
- marginRight: '.5em',
638
- }}
639
- >
640
- {Object.keys(sortFns).map((key) => (
641
- <option key={key} value={key}>
642
- Sort by {key}
643
- </option>
644
- ))}
645
- </Select>
646
- <Button
647
- type="button"
648
- onClick={() => setBaseSort((old) => old * -1)}
649
- style={{
650
- padding: '.3em .4em',
651
- marginRight: '.5em',
652
- }}
653
- >
654
- {baseSort === 1 ? '⬆ Asc' : '⬇ Desc'}
655
- </Button>
656
- <Button
657
- type="button"
658
- onClick={() => {
659
- if (isMockOffline) {
660
- onlineManager.setOnline(undefined)
661
- setMockOffline(false)
662
- window.dispatchEvent(new Event('online'))
663
- } else {
664
- onlineManager.setOnline(false)
665
- setMockOffline(true)
666
- }
667
- }}
668
- aria-label={
669
- isMockOffline
670
- ? 'Restore offline mock'
671
- : 'Mock offline behavior'
672
- }
673
- title={
674
- isMockOffline
675
- ? 'Restore offline mock'
676
- : 'Mock offline behavior'
677
- }
678
- style={{
679
- padding: '0',
680
- height: '2em',
681
- }}
682
- >
683
- <svg
684
- xmlns="http://www.w3.org/2000/svg"
685
- width="2em"
686
- height="2em"
687
- viewBox="0 0 24 24"
688
- stroke={isMockOffline ? theme.danger : 'currentColor'}
689
- fill="none"
690
- >
691
- {isMockOffline ? (
692
- <>
693
- <path stroke="none" d="M0 0h24v24H0z" fill="none" />
694
- <line x1="12" y1="18" x2="12.01" y2="18" />
695
- <path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
696
- <path d="M6.343 12.343a7.963 7.963 0 0 1 3.864 -2.14m4.163 .155a7.965 7.965 0 0 1 3.287 2" />
697
- <path d="M3.515 9.515a12 12 0 0 1 3.544 -2.455m3.101 -.92a12 12 0 0 1 10.325 3.374" />
698
- <line x1="3" y1="3" x2="21" y2="21" />
699
- </>
700
- ) : (
701
- <>
702
- <path stroke="none" d="M0 0h24v24H0z" fill="none" />
703
- <line x1="12" y1="18" x2="12.01" y2="18" />
704
- <path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
705
- <path d="M6.343 12.343a8 8 0 0 1 11.314 0" />
706
- <path d="M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" />
707
- </>
708
- )}
709
- </svg>
710
- <ScreenReader
711
- text={
712
- isMockOffline
713
- ? 'Restore offline mock'
714
- : 'Mock offline behavior'
715
- }
716
- />
717
- </Button>
718
- </div>
719
- </div>
720
- </div>
721
- <div
722
- style={{
723
- overflowY: 'auto',
724
- flex: '1',
725
- }}
726
- >
727
- {queries.map((query) => {
728
- return (
729
- <QueryRow
730
- queryKey={query.queryKey}
731
- activeQueryHash={activeQueryHash}
732
- setActiveQueryHash={setActiveQueryHash}
733
- key={query.queryHash}
734
- queryCache={queryCache}
735
- />
736
- )
737
- })}
738
- </div>
739
- </div>
740
- )}
741
-
742
- {activeQueryHash && isOpen ? (
743
- <ActiveQuery
744
- activeQueryHash={activeQueryHash}
745
- queryCache={queryCache}
746
- queryClient={client}
747
- />
748
- ) : null}
749
-
750
- {showCloseButton ? (
751
- <Button
752
- type="button"
753
- aria-controls="ReactQueryDevtoolsPanel"
754
- aria-haspopup="true"
755
- aria-expanded="true"
756
- {...(otherCloseButtonProps as Record<string, unknown>)}
757
- style={{
758
- position: 'absolute',
759
- zIndex: 99999,
760
- margin: '.5em',
761
- bottom: 0,
762
- left: 0,
763
- ...otherCloseButtonProps.style,
764
- }}
765
- onClick={(e) => {
766
- setIsOpen(false)
767
- onCloseClick?.(e)
768
- }}
769
- >
770
- Close
771
- </Button>
772
- ) : null}
773
- </Panel>
774
- </ThemeProvider>
775
- )
776
- })
777
-
778
- const ActiveQuery = ({
779
- queryCache,
780
- activeQueryHash,
781
- queryClient,
782
- }: {
783
- queryCache: QueryCache
784
- activeQueryHash: string
785
- queryClient: QueryClient
786
- }) => {
787
- const activeQuery = useSubscribeToQueryCache(queryCache, () =>
788
- queryCache.getAll().find((query) => query.queryHash === activeQueryHash),
789
- )
790
-
791
- const activeQueryState = useSubscribeToQueryCache(
792
- queryCache,
793
- () =>
794
- queryCache.getAll().find((query) => query.queryHash === activeQueryHash)
795
- ?.state,
796
- )
797
-
798
- const isStale =
799
- useSubscribeToQueryCache(queryCache, () =>
800
- queryCache
801
- .getAll()
802
- .find((query) => query.queryHash === activeQueryHash)
803
- ?.isStale(),
804
- ) ?? false
805
-
806
- const observerCount =
807
- useSubscribeToQueryCache(queryCache, () =>
808
- queryCache
809
- .getAll()
810
- .find((query) => query.queryHash === activeQueryHash)
811
- ?.getObserversCount(),
812
- ) ?? 0
813
-
814
- const handleRefetch = () => {
815
- const promise = activeQuery?.fetch()
816
- promise?.catch(noop)
817
- }
818
-
819
- if (!activeQuery || !activeQueryState) {
820
- return null
821
- }
822
-
823
- return (
824
- <ActiveQueryPanel>
825
- <div
826
- style={{
827
- padding: '.5em',
828
- background: theme.backgroundAlt,
829
- position: 'sticky',
830
- top: 0,
831
- zIndex: 1,
832
- }}
833
- >
834
- Query Details
835
- </div>
836
- <div
837
- style={{
838
- padding: '.5em',
839
- }}
840
- >
841
- <div
842
- style={{
843
- marginBottom: '.5em',
844
- display: 'flex',
845
- alignItems: 'flex-start',
846
- justifyContent: 'space-between',
847
- }}
848
- >
849
- <Code
850
- style={{
851
- lineHeight: '1.8em',
852
- }}
853
- >
854
- <pre
855
- style={{
856
- margin: 0,
857
- padding: 0,
858
- overflow: 'auto',
859
- }}
860
- >
861
- {displayValue(activeQuery.queryKey, true)}
862
- </pre>
863
- </Code>
864
- <span
865
- style={{
866
- padding: '0.3em .6em',
867
- borderRadius: '0.4em',
868
- fontWeight: 'bold',
869
- textShadow: '0 2px 10px black',
870
- background: getQueryStatusColor({
871
- queryState: activeQueryState,
872
- isStale: isStale,
873
- observerCount: observerCount,
874
- theme,
875
- }),
876
- flexShrink: 0,
877
- }}
878
- >
879
- {getQueryStatusLabel(activeQuery)}
880
- </span>
881
- </div>
882
- <div
883
- style={{
884
- marginBottom: '.5em',
885
- display: 'flex',
886
- alignItems: 'center',
887
- justifyContent: 'space-between',
888
- }}
889
- >
890
- Observers: <Code>{observerCount}</Code>
891
- </div>
892
- <div
893
- style={{
894
- display: 'flex',
895
- alignItems: 'center',
896
- justifyContent: 'space-between',
897
- }}
898
- >
899
- Last Updated:{' '}
900
- <Code>
901
- {new Date(activeQueryState.dataUpdatedAt).toLocaleTimeString()}
902
- </Code>
903
- </div>
904
- </div>
905
- <div
906
- style={{
907
- background: theme.backgroundAlt,
908
- padding: '.5em',
909
- position: 'sticky',
910
- top: 0,
911
- zIndex: 1,
912
- }}
913
- >
914
- Actions
915
- </div>
916
- <div
917
- style={{
918
- padding: '0.5em',
919
- }}
920
- >
921
- <Button
922
- type="button"
923
- onClick={handleRefetch}
924
- disabled={activeQueryState.fetchStatus === 'fetching'}
925
- style={{
926
- background: theme.active,
927
- }}
928
- >
929
- Refetch
930
- </Button>{' '}
931
- <Button
932
- type="button"
933
- onClick={() => queryClient.invalidateQueries(activeQuery)}
934
- style={{
935
- background: theme.warning,
936
- color: theme.inputTextColor,
937
- }}
938
- >
939
- Invalidate
940
- </Button>{' '}
941
- <Button
942
- type="button"
943
- onClick={() => queryClient.resetQueries(activeQuery)}
944
- style={{
945
- background: theme.gray,
946
- }}
947
- >
948
- Reset
949
- </Button>{' '}
950
- <Button
951
- type="button"
952
- onClick={() => queryClient.removeQueries(activeQuery)}
953
- style={{
954
- background: theme.danger,
955
- }}
956
- >
957
- Remove
958
- </Button>
959
- </div>
960
- <div
961
- style={{
962
- background: theme.backgroundAlt,
963
- padding: '.5em',
964
- position: 'sticky',
965
- top: 0,
966
- zIndex: 1,
967
- }}
968
- >
969
- Data Explorer
970
- </div>
971
- <div
972
- style={{
973
- padding: '.5em',
974
- }}
975
- >
976
- <Explorer
977
- label="Data"
978
- value={activeQueryState.data}
979
- defaultExpanded={{}}
980
- copyable
981
- />
982
- </div>
983
- <div
984
- style={{
985
- background: theme.backgroundAlt,
986
- padding: '.5em',
987
- position: 'sticky',
988
- top: 0,
989
- zIndex: 1,
990
- }}
991
- >
992
- Query Explorer
993
- </div>
994
- <div
995
- style={{
996
- padding: '.5em',
997
- }}
998
- >
999
- <Explorer
1000
- label="Query"
1001
- value={activeQuery}
1002
- defaultExpanded={{
1003
- queryKey: true,
1004
- }}
1005
- />
1006
- </div>
1007
- </ActiveQueryPanel>
1008
- )
1009
- }
1010
-
1011
- const QueryStatusCount = ({ queryCache }: { queryCache: QueryCache }) => {
1012
- const hasFresh = useSubscribeToQueryCache(
1013
- queryCache,
1014
- () =>
1015
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'fresh')
1016
- .length,
1017
- )
1018
- const hasFetching = useSubscribeToQueryCache(
1019
- queryCache,
1020
- () =>
1021
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'fetching')
1022
- .length,
1023
- )
1024
- const hasPaused = useSubscribeToQueryCache(
1025
- queryCache,
1026
- () =>
1027
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'paused')
1028
- .length,
1029
- )
1030
- const hasStale = useSubscribeToQueryCache(
1031
- queryCache,
1032
- () =>
1033
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'stale')
1034
- .length,
1035
- )
1036
- const hasInactive = useSubscribeToQueryCache(
1037
- queryCache,
1038
- () =>
1039
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'inactive')
1040
- .length,
1041
- )
1042
- return (
1043
- <QueryKeys>
1044
- <QueryKey
1045
- style={{
1046
- background: theme.success,
1047
- opacity: hasFresh ? 1 : 0.3,
1048
- }}
1049
- >
1050
- fresh <Code>({hasFresh})</Code>
1051
- </QueryKey>{' '}
1052
- <QueryKey
1053
- style={{
1054
- background: theme.active,
1055
- opacity: hasFetching ? 1 : 0.3,
1056
- }}
1057
- >
1058
- fetching <Code>({hasFetching})</Code>
1059
- </QueryKey>{' '}
1060
- <QueryKey
1061
- style={{
1062
- background: theme.paused,
1063
- opacity: hasPaused ? 1 : 0.3,
1064
- }}
1065
- >
1066
- paused <Code>({hasPaused})</Code>
1067
- </QueryKey>{' '}
1068
- <QueryKey
1069
- style={{
1070
- background: theme.warning,
1071
- color: 'black',
1072
- textShadow: '0',
1073
- opacity: hasStale ? 1 : 0.3,
1074
- }}
1075
- >
1076
- stale <Code>({hasStale})</Code>
1077
- </QueryKey>{' '}
1078
- <QueryKey
1079
- style={{
1080
- background: theme.gray,
1081
- opacity: hasInactive ? 1 : 0.3,
1082
- }}
1083
- >
1084
- inactive <Code>({hasInactive})</Code>
1085
- </QueryKey>
1086
- </QueryKeys>
1087
- )
94
+ return <div ref={ref}></div>
1088
95
  }
1089
-
1090
- interface QueryRowProps {
1091
- queryKey: QueryKeyType
1092
- setActiveQueryHash: (hash: string) => void
1093
- activeQueryHash?: string
1094
- queryCache: QueryCache
1095
- }
1096
-
1097
- const QueryRow = React.memo(
1098
- ({
1099
- queryKey,
1100
- setActiveQueryHash,
1101
- activeQueryHash,
1102
- queryCache,
1103
- }: QueryRowProps) => {
1104
- const queryHash =
1105
- useSubscribeToQueryCache(
1106
- queryCache,
1107
- () => queryCache.find({ queryKey })?.queryHash,
1108
- ) ?? ''
1109
-
1110
- const queryState = useSubscribeToQueryCache(
1111
- queryCache,
1112
- () => queryCache.find({ queryKey })?.state,
1113
- )
1114
-
1115
- const isStale =
1116
- useSubscribeToQueryCache(queryCache, () =>
1117
- queryCache.find({ queryKey })?.isStale(),
1118
- ) ?? false
1119
-
1120
- const isDisabled =
1121
- useSubscribeToQueryCache(queryCache, () =>
1122
- queryCache.find({ queryKey })?.isDisabled(),
1123
- ) ?? false
1124
-
1125
- const observerCount =
1126
- useSubscribeToQueryCache(queryCache, () =>
1127
- queryCache.find({ queryKey })?.getObserversCount(),
1128
- ) ?? 0
1129
-
1130
- if (!queryState) {
1131
- return null
1132
- }
1133
-
1134
- return (
1135
- <div
1136
- role="button"
1137
- aria-label={`Open query details for ${queryHash}`}
1138
- onClick={() =>
1139
- setActiveQueryHash(activeQueryHash === queryHash ? '' : queryHash)
1140
- }
1141
- style={{
1142
- display: 'flex',
1143
- borderBottom: `solid 1px ${theme.grayAlt}`,
1144
- cursor: 'pointer',
1145
- background:
1146
- queryHash === activeQueryHash ? 'rgba(255,255,255,.1)' : undefined,
1147
- }}
1148
- >
1149
- <div
1150
- style={{
1151
- flex: '0 0 auto',
1152
- width: '2em',
1153
- height: '2em',
1154
- background: getQueryStatusColor({
1155
- queryState,
1156
- isStale,
1157
- observerCount,
1158
- theme,
1159
- }),
1160
- display: 'flex',
1161
- alignItems: 'center',
1162
- justifyContent: 'center',
1163
- fontWeight: 'bold',
1164
- textShadow: isStale ? '0' : '0 0 10px black',
1165
- color: isStale ? 'black' : 'white',
1166
- }}
1167
- >
1168
- {observerCount}
1169
- </div>
1170
- {isDisabled ? (
1171
- <div
1172
- style={{
1173
- flex: '0 0 auto',
1174
- height: '2em',
1175
- background: theme.gray,
1176
- display: 'flex',
1177
- alignItems: 'center',
1178
- fontWeight: 'bold',
1179
- padding: '0 0.5em',
1180
- }}
1181
- >
1182
- disabled
1183
- </div>
1184
- ) : null}
1185
- <Code
1186
- style={{
1187
- padding: '.5em',
1188
- }}
1189
- >
1190
- {`${queryHash}`}
1191
- </Code>
1192
- </div>
1193
- )
1194
- },
1195
- )
1196
-
1197
- // eslint-disable-next-line @typescript-eslint/no-empty-function
1198
- function noop() {}