@tanstack/react-query-devtools 5.0.0-alpha.12 → 5.0.0-alpha.14

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 (72) hide show
  1. package/build/lib/CachePanel/ActiveQuery.d.ts +18 -0
  2. package/build/lib/CachePanel/ActiveQuery.esm.js +271 -0
  3. package/build/lib/CachePanel/ActiveQuery.esm.js.map +1 -0
  4. package/build/lib/CachePanel/ActiveQuery.js +275 -0
  5. package/build/lib/CachePanel/ActiveQuery.js.map +1 -0
  6. package/build/lib/CachePanel/ActiveQuery.mjs +257 -0
  7. package/build/lib/CachePanel/ActiveQuery.mjs.map +1 -0
  8. package/build/lib/CachePanel/CachePanel.d.ts +57 -0
  9. package/build/lib/CachePanel/CachePanel.esm.js +301 -0
  10. package/build/lib/CachePanel/CachePanel.esm.js.map +1 -0
  11. package/build/lib/CachePanel/CachePanel.js +306 -0
  12. package/build/lib/CachePanel/CachePanel.js.map +1 -0
  13. package/build/lib/CachePanel/CachePanel.mjs +301 -0
  14. package/build/lib/CachePanel/CachePanel.mjs.map +1 -0
  15. package/build/lib/CachePanel/Header/QueryStatusCount.d.ts +7 -0
  16. package/build/lib/CachePanel/Header/QueryStatusCount.esm.js +49 -0
  17. package/build/lib/CachePanel/Header/QueryStatusCount.esm.js.map +1 -0
  18. package/build/lib/CachePanel/Header/QueryStatusCount.js +53 -0
  19. package/build/lib/CachePanel/Header/QueryStatusCount.js.map +1 -0
  20. package/build/lib/CachePanel/Header/QueryStatusCount.mjs +49 -0
  21. package/build/lib/CachePanel/Header/QueryStatusCount.mjs.map +1 -0
  22. package/build/lib/CachePanel/QueryRow.d.ts +13 -0
  23. package/build/lib/CachePanel/QueryRow.esm.js +98 -0
  24. package/build/lib/CachePanel/QueryRow.esm.js.map +1 -0
  25. package/build/lib/CachePanel/QueryRow.js +102 -0
  26. package/build/lib/CachePanel/QueryRow.js.map +1 -0
  27. package/build/lib/CachePanel/QueryRow.mjs +82 -0
  28. package/build/lib/CachePanel/QueryRow.mjs.map +1 -0
  29. package/build/lib/devtools.d.ts +3 -64
  30. package/build/lib/devtools.esm.js +2 -669
  31. package/build/lib/devtools.esm.js.map +1 -1
  32. package/build/lib/devtools.js +3 -669
  33. package/build/lib/devtools.js.map +1 -1
  34. package/build/lib/devtools.mjs +2 -639
  35. package/build/lib/devtools.mjs.map +1 -1
  36. package/build/lib/index.esm.js +2 -1
  37. package/build/lib/index.esm.js.map +1 -1
  38. package/build/lib/index.js +2 -1
  39. package/build/lib/index.js.map +1 -1
  40. package/build/lib/index.mjs +2 -1
  41. package/build/lib/index.mjs.map +1 -1
  42. package/build/lib/index.prod.esm.js +745 -713
  43. package/build/lib/index.prod.esm.js.map +1 -1
  44. package/build/lib/index.prod.js +742 -710
  45. package/build/lib/index.prod.js.map +1 -1
  46. package/build/lib/index.prod.mjs +745 -713
  47. package/build/lib/index.prod.mjs.map +1 -1
  48. package/build/lib/styledComponents.esm.js +8 -0
  49. package/build/lib/styledComponents.esm.js.map +1 -1
  50. package/build/lib/styledComponents.js +8 -0
  51. package/build/lib/styledComponents.js.map +1 -1
  52. package/build/lib/styledComponents.mjs +8 -0
  53. package/build/lib/styledComponents.mjs.map +1 -1
  54. package/build/lib/types.d.ts +11 -0
  55. package/build/lib/useSubscribeToQueryCache.d.ts +3 -0
  56. package/build/lib/useSubscribeToQueryCache.esm.js +15 -0
  57. package/build/lib/useSubscribeToQueryCache.esm.js.map +1 -0
  58. package/build/lib/useSubscribeToQueryCache.js +19 -0
  59. package/build/lib/useSubscribeToQueryCache.js.map +1 -0
  60. package/build/lib/useSubscribeToQueryCache.mjs +15 -0
  61. package/build/lib/useSubscribeToQueryCache.mjs.map +1 -0
  62. package/build/umd/index.development.js +2385 -2353
  63. package/build/umd/index.development.js.map +1 -1
  64. package/package.json +1 -1
  65. package/src/CachePanel/ActiveQuery.tsx +380 -0
  66. package/src/CachePanel/CachePanel.tsx +427 -0
  67. package/src/CachePanel/Header/QueryStatusCount.tsx +93 -0
  68. package/src/CachePanel/QueryRow.tsx +125 -0
  69. package/src/devtools.tsx +5 -1008
  70. package/src/styledComponents.ts +16 -0
  71. package/src/types.ts +12 -0
  72. package/src/useSubscribeToQueryCache.ts +26 -0
package/src/devtools.tsx CHANGED
@@ -1,58 +1,24 @@
1
1
  'use client'
2
2
  import * as React from 'react'
3
- import type {
4
- QueryCache,
5
- QueryClient,
6
- QueryKey as QueryKeyType,
7
- Query,
8
- DefaultError,
9
- } from '@tanstack/react-query'
10
- import {
11
- useQueryClient,
12
- onlineManager,
13
- notifyManager,
14
- } from '@tanstack/react-query'
15
- import { rankItem } from '@tanstack/match-sorter-utils'
3
+ import type { QueryClient } from '@tanstack/react-query'
16
4
  import useLocalStorage from './useLocalStorage'
17
5
  import {
18
6
  isVerticalSide,
19
- sortFns,
20
7
  useIsMounted,
21
8
  getSidePanelStyle,
22
9
  minPanelSize,
23
- getResizeHandleStyle,
24
10
  getSidedProp,
25
11
  defaultPanelSize,
26
- displayValue,
27
12
  } from './utils'
28
13
  import type { Corner, Side } from './utils'
29
- import {
30
- Panel,
31
- QueryKeys,
32
- QueryKey,
33
- Button,
34
- Code,
35
- Input,
36
- Select,
37
- ActiveQueryPanel,
38
- } from './styledComponents'
39
14
  import ScreenReader from './screenreader'
40
15
  import { ThemeProvider, defaultTheme as theme } from './theme'
41
- import { getQueryStatusLabel, getQueryStatusColor } from './utils'
42
- import Explorer from './Explorer'
43
16
  import Logo from './Logo'
44
- import { useMemo } from 'react'
45
17
 
46
- export interface DevToolsErrorType {
47
- /**
48
- * The name of the error.
49
- */
50
- name: string
51
- /**
52
- * How the error is initialized.
53
- */
54
- initializer: (query: Query) => DefaultError
55
- }
18
+ import type { DevToolsErrorType } from './types'
19
+ import ReactQueryDevtoolsPanel from './CachePanel/CachePanel'
20
+
21
+ export { default as ReactQueryDevtoolsPanel } from './CachePanel/CachePanel'
56
22
 
57
23
  export interface DevtoolsOptions {
58
24
  /**
@@ -101,58 +67,6 @@ export interface DevtoolsOptions {
101
67
  errorTypes?: DevToolsErrorType[]
102
68
  }
103
69
 
104
- interface DevtoolsPanelOptions {
105
- /**
106
- * The standard React style object used to style a component with inline styles
107
- */
108
- style?: React.CSSProperties
109
- /**
110
- * The standard React className property used to style a component with classes
111
- */
112
- className?: string
113
- /**
114
- * A boolean variable indicating whether the panel is open or closed
115
- */
116
- isOpen?: boolean
117
- /**
118
- * nonce for style element for CSP
119
- */
120
- styleNonce?: string
121
- /**
122
- * A function that toggles the open and close state of the panel
123
- */
124
- setIsOpen: (isOpen: boolean) => void
125
- /**
126
- * Handles the opening and closing the devtools panel
127
- */
128
- onDragStart: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
129
- /**
130
- * The position of the React Query devtools panel.
131
- * Defaults to 'bottom'.
132
- */
133
- position?: Side
134
- /**
135
- * Handles the panel position select change
136
- */
137
- onPositionChange?: (side: Side) => void
138
- /**
139
- * Show a close button inside the panel
140
- */
141
- showCloseButton?: boolean
142
- /**
143
- * 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.
144
- */
145
- closeButtonProps?: React.ComponentPropsWithoutRef<'button'>
146
- /**
147
- * Custom instance of QueryClient
148
- */
149
- queryClient?: QueryClient
150
- /**
151
- * Use this so you can define custom errors that can be shown in the devtools.
152
- */
153
- errorTypes?: DevToolsErrorType[]
154
- }
155
-
156
70
  export function ReactQueryDevtools({
157
71
  initialIsOpen,
158
72
  panelProps = {},
@@ -427,920 +341,3 @@ export function ReactQueryDevtools({
427
341
  </Container>
428
342
  )
429
343
  }
430
-
431
- const useSubscribeToQueryCache = <T,>(
432
- queryCache: QueryCache,
433
- getSnapshot: () => T,
434
- skip: boolean = false,
435
- ): T => {
436
- return React.useSyncExternalStore(
437
- React.useCallback(
438
- (onStoreChange) => {
439
- if (!skip)
440
- return queryCache.subscribe(notifyManager.batchCalls(onStoreChange))
441
- return () => {
442
- return
443
- }
444
- },
445
- [queryCache, skip],
446
- ),
447
- getSnapshot,
448
- getSnapshot,
449
- )
450
- }
451
-
452
- export const ReactQueryDevtoolsPanel = React.forwardRef<
453
- HTMLDivElement,
454
- DevtoolsPanelOptions
455
- >(function ReactQueryDevtoolsPanel(props, ref): React.ReactElement {
456
- const {
457
- isOpen = true,
458
- styleNonce,
459
- setIsOpen,
460
- queryClient,
461
- onDragStart,
462
- onPositionChange,
463
- showCloseButton,
464
- position,
465
- closeButtonProps = {},
466
- errorTypes = [],
467
- ...panelProps
468
- } = props
469
-
470
- const { onClick: onCloseClick, ...otherCloseButtonProps } = closeButtonProps
471
-
472
- const client = useQueryClient(queryClient)
473
- const queryCache = client.getQueryCache()
474
-
475
- const [sort, setSort] = useLocalStorage(
476
- 'reactQueryDevtoolsSortFn',
477
- Object.keys(sortFns)[0],
478
- )
479
-
480
- const [filter, setFilter] = useLocalStorage('reactQueryDevtoolsFilter', '')
481
-
482
- const [baseSort, setBaseSort] = useLocalStorage(
483
- 'reactQueryDevtoolsBaseSort',
484
- 1,
485
- )
486
-
487
- const sortFn = React.useMemo(() => sortFns[sort as string], [sort])
488
-
489
- const queriesCount = useSubscribeToQueryCache(
490
- queryCache,
491
- () => queryCache.getAll().length,
492
- !isOpen,
493
- )
494
-
495
- const [activeQueryHash, setActiveQueryHash] = useLocalStorage(
496
- 'reactQueryDevtoolsActiveQueryHash',
497
- '',
498
- )
499
-
500
- const queries = React.useMemo(() => {
501
- const unsortedQueries = queryCache.getAll()
502
-
503
- if (queriesCount === 0) {
504
- return []
505
- }
506
-
507
- const filtered = filter
508
- ? unsortedQueries.filter(
509
- (item) => rankItem(item.queryHash, filter).passed,
510
- )
511
- : [...unsortedQueries]
512
-
513
- const sorted = sortFn
514
- ? filtered.sort((a, b) => sortFn(a, b) * (baseSort as number))
515
- : filtered
516
-
517
- return sorted
518
- }, [baseSort, sortFn, filter, queriesCount, queryCache])
519
-
520
- const [isMockOffline, setMockOffline] = React.useState(false)
521
-
522
- return (
523
- <ThemeProvider theme={theme}>
524
- <Panel
525
- ref={ref}
526
- className="ReactQueryDevtoolsPanel"
527
- aria-label="React Query Devtools Panel"
528
- id="ReactQueryDevtoolsPanel"
529
- {...panelProps}
530
- style={{
531
- height: defaultPanelSize,
532
- position: 'relative',
533
- ...panelProps.style,
534
- }}
535
- >
536
- <style
537
- nonce={styleNonce}
538
- dangerouslySetInnerHTML={{
539
- __html: `
540
- .ReactQueryDevtoolsPanel * {
541
- scrollbar-color: ${theme.backgroundAlt} ${theme.gray};
542
- }
543
-
544
- .ReactQueryDevtoolsPanel *::-webkit-scrollbar, .ReactQueryDevtoolsPanel scrollbar {
545
- width: 1em;
546
- height: 1em;
547
- }
548
-
549
- .ReactQueryDevtoolsPanel *::-webkit-scrollbar-track, .ReactQueryDevtoolsPanel scrollbar-track {
550
- background: ${theme.backgroundAlt};
551
- }
552
-
553
- .ReactQueryDevtoolsPanel *::-webkit-scrollbar-thumb, .ReactQueryDevtoolsPanel scrollbar-thumb {
554
- background: ${theme.gray};
555
- border-radius: .5em;
556
- border: 3px solid ${theme.backgroundAlt};
557
- }
558
- `,
559
- }}
560
- />
561
- <div
562
- style={getResizeHandleStyle(position)}
563
- onMouseDown={onDragStart}
564
- ></div>
565
-
566
- {isOpen && (
567
- <div
568
- style={{
569
- flex: '1 1 500px',
570
- minHeight: '40%',
571
- maxHeight: '100%',
572
- overflow: 'auto',
573
- borderRight: `1px solid ${theme.grayAlt}`,
574
- display: 'flex',
575
- flexDirection: 'column',
576
- }}
577
- >
578
- <div
579
- style={{
580
- padding: '.5em',
581
- background: theme.backgroundAlt,
582
- display: 'flex',
583
- justifyContent: 'space-between',
584
- alignItems: 'center',
585
- }}
586
- >
587
- <button
588
- type="button"
589
- aria-label="Close React Query Devtools"
590
- aria-controls="ReactQueryDevtoolsPanel"
591
- aria-haspopup="true"
592
- aria-expanded="true"
593
- onClick={() => setIsOpen(false)}
594
- style={{
595
- display: 'inline-flex',
596
- background: 'none',
597
- border: 0,
598
- padding: 0,
599
- marginRight: '.5em',
600
- cursor: 'pointer',
601
- }}
602
- >
603
- <Logo aria-hidden />
604
- <ScreenReader text="Close React Query Devtools" />
605
- </button>
606
- <div
607
- style={{
608
- display: 'flex',
609
- flexDirection: 'column',
610
- }}
611
- >
612
- <div
613
- style={{
614
- display: 'flex',
615
- justifyContent: 'space-between',
616
- alignItems: 'center',
617
- marginBottom: '.5em',
618
- }}
619
- >
620
- <QueryStatusCount queryCache={queryCache} />
621
- {position && onPositionChange ? (
622
- <Select
623
- aria-label="Panel position"
624
- value={position}
625
- style={{ marginInlineStart: '.5em' }}
626
- onChange={(e) => onPositionChange(e.target.value as Side)}
627
- >
628
- <option value="left">Left</option>
629
- <option value="right">Right</option>
630
- <option value="top">Top</option>
631
- <option value="bottom">Bottom</option>
632
- </Select>
633
- ) : null}
634
- </div>
635
- <div
636
- style={{
637
- display: 'flex',
638
- alignItems: 'center',
639
- flexWrap: 'wrap',
640
- gap: '0.5em',
641
- }}
642
- >
643
- <Input
644
- placeholder="Filter"
645
- aria-label="Filter by queryhash"
646
- value={filter ?? ''}
647
- onChange={(e) => setFilter(e.target.value)}
648
- onKeyDown={(e) => {
649
- if (e.key === 'Escape') setFilter('')
650
- }}
651
- style={{
652
- flex: '1',
653
- width: '100%',
654
- }}
655
- />
656
- <Select
657
- aria-label="Sort queries"
658
- value={sort}
659
- onChange={(e) => setSort(e.target.value)}
660
- style={{
661
- flex: '1',
662
- minWidth: 75,
663
- marginRight: '.5em',
664
- }}
665
- >
666
- {Object.keys(sortFns).map((key) => (
667
- <option key={key} value={key}>
668
- Sort by {key}
669
- </option>
670
- ))}
671
- </Select>
672
- <Button
673
- type="button"
674
- onClick={() => setBaseSort((old) => old * -1)}
675
- style={{
676
- padding: '.3em .4em',
677
- marginRight: '.5em',
678
- }}
679
- >
680
- {baseSort === 1 ? '⬆ Asc' : '⬇ Desc'}
681
- </Button>
682
- <Button
683
- type="button"
684
- onClick={() => {
685
- if (isMockOffline) {
686
- onlineManager.setOnline(undefined)
687
- setMockOffline(false)
688
- window.dispatchEvent(new Event('online'))
689
- } else {
690
- onlineManager.setOnline(false)
691
- setMockOffline(true)
692
- window.dispatchEvent(new Event('offline'))
693
- }
694
- }}
695
- aria-label={
696
- isMockOffline
697
- ? 'Restore offline mock'
698
- : 'Mock offline behavior'
699
- }
700
- title={
701
- isMockOffline
702
- ? 'Restore offline mock'
703
- : 'Mock offline behavior'
704
- }
705
- style={{
706
- padding: '0',
707
- height: '2em',
708
- }}
709
- >
710
- <svg
711
- xmlns="http://www.w3.org/2000/svg"
712
- width="2em"
713
- height="2em"
714
- viewBox="0 0 24 24"
715
- stroke={isMockOffline ? theme.danger : 'currentColor'}
716
- fill="none"
717
- >
718
- {isMockOffline ? (
719
- <>
720
- <path stroke="none" d="M0 0h24v24H0z" fill="none" />
721
- <line x1="12" y1="18" x2="12.01" y2="18" />
722
- <path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
723
- <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" />
724
- <path d="M3.515 9.515a12 12 0 0 1 3.544 -2.455m3.101 -.92a12 12 0 0 1 10.325 3.374" />
725
- <line x1="3" y1="3" x2="21" y2="21" />
726
- </>
727
- ) : (
728
- <>
729
- <path stroke="none" d="M0 0h24v24H0z" fill="none" />
730
- <line x1="12" y1="18" x2="12.01" y2="18" />
731
- <path d="M9.172 15.172a4 4 0 0 1 5.656 0" />
732
- <path d="M6.343 12.343a8 8 0 0 1 11.314 0" />
733
- <path d="M3.515 9.515c4.686 -4.687 12.284 -4.687 17 0" />
734
- </>
735
- )}
736
- </svg>
737
- <ScreenReader
738
- text={
739
- isMockOffline
740
- ? 'Restore offline mock'
741
- : 'Mock offline behavior'
742
- }
743
- />
744
- </Button>
745
- </div>
746
- </div>
747
- </div>
748
- <div
749
- style={{
750
- overflowY: 'auto',
751
- flex: '1',
752
- }}
753
- >
754
- {queries.map((query) => {
755
- return (
756
- <QueryRow
757
- queryKey={query.queryKey}
758
- activeQueryHash={activeQueryHash}
759
- setActiveQueryHash={setActiveQueryHash}
760
- key={query.queryHash}
761
- queryCache={queryCache}
762
- />
763
- )
764
- })}
765
- </div>
766
- </div>
767
- )}
768
-
769
- {activeQueryHash && isOpen ? (
770
- <ActiveQuery
771
- activeQueryHash={activeQueryHash}
772
- queryCache={queryCache}
773
- queryClient={client}
774
- errorTypes={errorTypes}
775
- />
776
- ) : null}
777
-
778
- {showCloseButton ? (
779
- <Button
780
- type="button"
781
- aria-controls="ReactQueryDevtoolsPanel"
782
- aria-haspopup="true"
783
- aria-expanded="true"
784
- {...(otherCloseButtonProps as Record<string, unknown>)}
785
- style={{
786
- position: 'absolute',
787
- zIndex: 99999,
788
- margin: '.5em',
789
- bottom: 0,
790
- left: 0,
791
- ...otherCloseButtonProps.style,
792
- }}
793
- onClick={(e) => {
794
- setIsOpen(false)
795
- onCloseClick?.(e)
796
- }}
797
- >
798
- Close
799
- </Button>
800
- ) : null}
801
- </Panel>
802
- </ThemeProvider>
803
- )
804
- })
805
-
806
- const ActiveQuery = ({
807
- queryCache,
808
- activeQueryHash,
809
- queryClient,
810
- errorTypes,
811
- }: {
812
- queryCache: QueryCache
813
- activeQueryHash: string
814
- queryClient: QueryClient
815
- errorTypes: DevToolsErrorType[]
816
- }) => {
817
- const activeQuery = useSubscribeToQueryCache(queryCache, () =>
818
- queryCache.getAll().find((query) => query.queryHash === activeQueryHash),
819
- )
820
-
821
- const activeQueryState = useSubscribeToQueryCache(
822
- queryCache,
823
- () =>
824
- queryCache.getAll().find((query) => query.queryHash === activeQueryHash)
825
- ?.state,
826
- )
827
-
828
- const isStale =
829
- useSubscribeToQueryCache(queryCache, () =>
830
- queryCache
831
- .getAll()
832
- .find((query) => query.queryHash === activeQueryHash)
833
- ?.isStale(),
834
- ) ?? false
835
-
836
- const observerCount =
837
- useSubscribeToQueryCache(queryCache, () =>
838
- queryCache
839
- .getAll()
840
- .find((query) => query.queryHash === activeQueryHash)
841
- ?.getObserversCount(),
842
- ) ?? 0
843
-
844
- const handleRefetch = () => {
845
- const promise = activeQuery?.fetch()
846
- promise?.catch(noop)
847
- }
848
-
849
- const currentErrorTypeName = useMemo(() => {
850
- if (activeQuery && activeQueryState?.error) {
851
- const errorType = errorTypes.find(
852
- (type) =>
853
- type.initializer(activeQuery).toString() ===
854
- activeQueryState.error?.toString(),
855
- )
856
- return errorType?.name
857
- }
858
- return undefined
859
- }, [activeQuery, activeQueryState?.error, errorTypes])
860
-
861
- if (!activeQuery || !activeQueryState) {
862
- return null
863
- }
864
-
865
- const triggerError = (errorType?: DevToolsErrorType) => {
866
- const error =
867
- errorType?.initializer(activeQuery) ??
868
- new Error('Unknown error from devtools')
869
-
870
- const __previousQueryOptions = activeQuery.options
871
-
872
- activeQuery.setState({
873
- status: 'error',
874
- error,
875
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
876
- fetchMeta: {
877
- ...activeQuery.state.fetchMeta,
878
- __previousQueryOptions,
879
- } as any,
880
- })
881
- }
882
-
883
- const restoreQueryAfterLoadingOrError = () => {
884
- activeQuery.fetch(
885
- (activeQuery.state.fetchMeta as any).__previousQueryOptions,
886
- {
887
- // Make sure this fetch will cancel the previous one
888
- cancelRefetch: true,
889
- },
890
- )
891
- }
892
-
893
- return (
894
- <ActiveQueryPanel>
895
- <div
896
- style={{
897
- padding: '.5em',
898
- background: theme.backgroundAlt,
899
- position: 'sticky',
900
- top: 0,
901
- zIndex: 1,
902
- }}
903
- >
904
- Query Details
905
- </div>
906
- <div
907
- style={{
908
- padding: '.5em',
909
- }}
910
- >
911
- <div
912
- style={{
913
- marginBottom: '.5em',
914
- display: 'flex',
915
- alignItems: 'flex-start',
916
- justifyContent: 'space-between',
917
- }}
918
- >
919
- <Code
920
- style={{
921
- lineHeight: '1.8em',
922
- }}
923
- >
924
- <pre
925
- style={{
926
- margin: 0,
927
- padding: 0,
928
- overflow: 'auto',
929
- }}
930
- >
931
- {displayValue(activeQuery.queryKey, true)}
932
- </pre>
933
- </Code>
934
- <span
935
- style={{
936
- padding: '0.3em .6em',
937
- borderRadius: '0.4em',
938
- fontWeight: 'bold',
939
- textShadow: '0 2px 10px black',
940
- background: getQueryStatusColor({
941
- queryState: activeQueryState,
942
- isStale: isStale,
943
- observerCount: observerCount,
944
- theme,
945
- }),
946
- flexShrink: 0,
947
- }}
948
- >
949
- {getQueryStatusLabel(activeQuery)}
950
- </span>
951
- </div>
952
- <div
953
- style={{
954
- marginBottom: '.5em',
955
- display: 'flex',
956
- alignItems: 'center',
957
- justifyContent: 'space-between',
958
- }}
959
- >
960
- Observers: <Code>{observerCount}</Code>
961
- </div>
962
- <div
963
- style={{
964
- display: 'flex',
965
- alignItems: 'center',
966
- justifyContent: 'space-between',
967
- }}
968
- >
969
- Last Updated:{' '}
970
- <Code>
971
- {new Date(activeQueryState.dataUpdatedAt).toLocaleTimeString()}
972
- </Code>
973
- </div>
974
- </div>
975
- <div
976
- style={{
977
- background: theme.backgroundAlt,
978
- padding: '.5em',
979
- position: 'sticky',
980
- top: 0,
981
- zIndex: 1,
982
- }}
983
- >
984
- Actions
985
- </div>
986
- <div
987
- style={{
988
- padding: '0.5em',
989
- display: 'flex',
990
- flexWrap: 'wrap',
991
- gap: '0.5em',
992
- alignItems: 'flex-end',
993
- }}
994
- >
995
- <Button
996
- type="button"
997
- onClick={handleRefetch}
998
- disabled={activeQueryState.fetchStatus === 'fetching'}
999
- style={{
1000
- background: theme.active,
1001
- }}
1002
- >
1003
- Refetch
1004
- </Button>{' '}
1005
- <Button
1006
- type="button"
1007
- onClick={() => queryClient.invalidateQueries(activeQuery)}
1008
- style={{
1009
- background: theme.warning,
1010
- color: theme.inputTextColor,
1011
- }}
1012
- >
1013
- Invalidate
1014
- </Button>{' '}
1015
- <Button
1016
- type="button"
1017
- onClick={() => queryClient.resetQueries(activeQuery)}
1018
- style={{
1019
- background: theme.gray,
1020
- }}
1021
- >
1022
- Reset
1023
- </Button>{' '}
1024
- <Button
1025
- type="button"
1026
- onClick={() => queryClient.removeQueries(activeQuery)}
1027
- style={{
1028
- background: theme.danger,
1029
- }}
1030
- >
1031
- Remove
1032
- </Button>{' '}
1033
- <Button
1034
- type="button"
1035
- onClick={() => {
1036
- if (activeQuery.state.data === undefined) {
1037
- restoreQueryAfterLoadingOrError()
1038
- } else {
1039
- const __previousQueryOptions = activeQuery.options
1040
- // Trigger a fetch in order to trigger suspense as well.
1041
- activeQuery.fetch({
1042
- ...__previousQueryOptions,
1043
- queryFn: () => {
1044
- return new Promise(() => {
1045
- // Never resolve
1046
- })
1047
- },
1048
- gcTime: -1,
1049
- })
1050
- activeQuery.setState({
1051
- data: undefined,
1052
- status: 'pending',
1053
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1054
- fetchMeta: {
1055
- ...activeQuery.state.fetchMeta,
1056
- __previousQueryOptions,
1057
- } as any,
1058
- })
1059
- }
1060
- }}
1061
- style={{
1062
- background: theme.paused,
1063
- }}
1064
- >
1065
- {activeQuery.state.status === 'pending' ? 'Restore' : 'Trigger'}{' '}
1066
- loading
1067
- </Button>{' '}
1068
- {errorTypes.length === 0 || activeQuery.state.status === 'error' ? (
1069
- <Button
1070
- type="button"
1071
- onClick={() => {
1072
- if (!activeQuery.state.error) {
1073
- triggerError()
1074
- } else {
1075
- queryClient.resetQueries(activeQuery)
1076
- }
1077
- }}
1078
- style={{
1079
- background: theme.danger,
1080
- }}
1081
- >
1082
- {activeQuery.state.status === 'error' ? 'Restore' : 'Trigger'} error
1083
- </Button>
1084
- ) : (
1085
- <label>
1086
- Trigger error:
1087
- <Select
1088
- value={currentErrorTypeName ?? ''}
1089
- style={{ marginInlineStart: '.5em' }}
1090
- onChange={(e) => {
1091
- const errorType = errorTypes.find(
1092
- (t) => t.name === e.target.value,
1093
- )
1094
-
1095
- triggerError(errorType)
1096
- }}
1097
- >
1098
- <option key="" value="" />
1099
- {errorTypes.map((errorType) => (
1100
- <option key={errorType.name} value={errorType.name}>
1101
- {errorType.name}
1102
- </option>
1103
- ))}
1104
- </Select>
1105
- </label>
1106
- )}
1107
- </div>
1108
- <div
1109
- style={{
1110
- background: theme.backgroundAlt,
1111
- padding: '.5em',
1112
- position: 'sticky',
1113
- top: 0,
1114
- zIndex: 1,
1115
- }}
1116
- >
1117
- Data Explorer
1118
- </div>
1119
- <div
1120
- style={{
1121
- padding: '.5em',
1122
- }}
1123
- >
1124
- <Explorer
1125
- label="Data"
1126
- value={activeQueryState.data}
1127
- defaultExpanded={{}}
1128
- copyable
1129
- />
1130
- </div>
1131
- <div
1132
- style={{
1133
- background: theme.backgroundAlt,
1134
- padding: '.5em',
1135
- position: 'sticky',
1136
- top: 0,
1137
- zIndex: 1,
1138
- }}
1139
- >
1140
- Query Explorer
1141
- </div>
1142
- <div
1143
- style={{
1144
- padding: '.5em',
1145
- }}
1146
- >
1147
- <Explorer
1148
- label="Query"
1149
- value={activeQuery}
1150
- defaultExpanded={{
1151
- queryKey: true,
1152
- }}
1153
- />
1154
- </div>
1155
- </ActiveQueryPanel>
1156
- )
1157
- }
1158
-
1159
- const QueryStatusCount = ({ queryCache }: { queryCache: QueryCache }) => {
1160
- const hasFresh = useSubscribeToQueryCache(
1161
- queryCache,
1162
- () =>
1163
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'fresh')
1164
- .length,
1165
- )
1166
- const hasFetching = useSubscribeToQueryCache(
1167
- queryCache,
1168
- () =>
1169
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'fetching')
1170
- .length,
1171
- )
1172
- const hasPaused = useSubscribeToQueryCache(
1173
- queryCache,
1174
- () =>
1175
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'paused')
1176
- .length,
1177
- )
1178
- const hasStale = useSubscribeToQueryCache(
1179
- queryCache,
1180
- () =>
1181
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'stale')
1182
- .length,
1183
- )
1184
- const hasInactive = useSubscribeToQueryCache(
1185
- queryCache,
1186
- () =>
1187
- queryCache.getAll().filter((q) => getQueryStatusLabel(q) === 'inactive')
1188
- .length,
1189
- )
1190
- return (
1191
- <QueryKeys>
1192
- <QueryKey
1193
- style={{
1194
- background: theme.success,
1195
- opacity: hasFresh ? 1 : 0.3,
1196
- }}
1197
- >
1198
- fresh <Code>({hasFresh})</Code>
1199
- </QueryKey>{' '}
1200
- <QueryKey
1201
- style={{
1202
- background: theme.active,
1203
- opacity: hasFetching ? 1 : 0.3,
1204
- }}
1205
- >
1206
- fetching <Code>({hasFetching})</Code>
1207
- </QueryKey>{' '}
1208
- <QueryKey
1209
- style={{
1210
- background: theme.paused,
1211
- opacity: hasPaused ? 1 : 0.3,
1212
- }}
1213
- >
1214
- paused <Code>({hasPaused})</Code>
1215
- </QueryKey>{' '}
1216
- <QueryKey
1217
- style={{
1218
- background: theme.warning,
1219
- color: 'black',
1220
- textShadow: '0',
1221
- opacity: hasStale ? 1 : 0.3,
1222
- }}
1223
- >
1224
- stale <Code>({hasStale})</Code>
1225
- </QueryKey>{' '}
1226
- <QueryKey
1227
- style={{
1228
- background: theme.gray,
1229
- opacity: hasInactive ? 1 : 0.3,
1230
- }}
1231
- >
1232
- inactive <Code>({hasInactive})</Code>
1233
- </QueryKey>
1234
- </QueryKeys>
1235
- )
1236
- }
1237
-
1238
- interface QueryRowProps {
1239
- queryKey: QueryKeyType
1240
- setActiveQueryHash: (hash: string) => void
1241
- activeQueryHash?: string
1242
- queryCache: QueryCache
1243
- }
1244
-
1245
- const QueryRow = React.memo(
1246
- ({
1247
- queryKey,
1248
- setActiveQueryHash,
1249
- activeQueryHash,
1250
- queryCache,
1251
- }: QueryRowProps) => {
1252
- const queryHash =
1253
- useSubscribeToQueryCache(
1254
- queryCache,
1255
- () => queryCache.find({ queryKey })?.queryHash,
1256
- ) ?? ''
1257
-
1258
- const queryState = useSubscribeToQueryCache(
1259
- queryCache,
1260
- () => queryCache.find({ queryKey })?.state,
1261
- )
1262
-
1263
- const isStale =
1264
- useSubscribeToQueryCache(queryCache, () =>
1265
- queryCache.find({ queryKey })?.isStale(),
1266
- ) ?? false
1267
-
1268
- const isDisabled =
1269
- useSubscribeToQueryCache(queryCache, () =>
1270
- queryCache.find({ queryKey })?.isDisabled(),
1271
- ) ?? false
1272
-
1273
- const observerCount =
1274
- useSubscribeToQueryCache(queryCache, () =>
1275
- queryCache.find({ queryKey })?.getObserversCount(),
1276
- ) ?? 0
1277
-
1278
- if (!queryState) {
1279
- return null
1280
- }
1281
-
1282
- return (
1283
- <div
1284
- role="button"
1285
- aria-label={`Open query details for ${queryHash}`}
1286
- onClick={() =>
1287
- setActiveQueryHash(activeQueryHash === queryHash ? '' : queryHash)
1288
- }
1289
- style={{
1290
- display: 'flex',
1291
- borderBottom: `solid 1px ${theme.grayAlt}`,
1292
- cursor: 'pointer',
1293
- background:
1294
- queryHash === activeQueryHash ? 'rgba(255,255,255,.1)' : undefined,
1295
- }}
1296
- >
1297
- <div
1298
- style={{
1299
- flex: '0 0 auto',
1300
- width: '2em',
1301
- height: '2em',
1302
- background: getQueryStatusColor({
1303
- queryState,
1304
- isStale,
1305
- observerCount,
1306
- theme,
1307
- }),
1308
- display: 'flex',
1309
- alignItems: 'center',
1310
- justifyContent: 'center',
1311
- fontWeight: 'bold',
1312
- textShadow: isStale ? '0' : '0 0 10px black',
1313
- color: isStale ? 'black' : 'white',
1314
- }}
1315
- >
1316
- {observerCount}
1317
- </div>
1318
- {isDisabled ? (
1319
- <div
1320
- style={{
1321
- flex: '0 0 auto',
1322
- height: '2em',
1323
- background: theme.gray,
1324
- display: 'flex',
1325
- alignItems: 'center',
1326
- fontWeight: 'bold',
1327
- padding: '0 0.5em',
1328
- }}
1329
- >
1330
- disabled
1331
- </div>
1332
- ) : null}
1333
- <Code
1334
- style={{
1335
- padding: '.5em',
1336
- }}
1337
- >
1338
- {`${queryHash}`}
1339
- </Code>
1340
- </div>
1341
- )
1342
- },
1343
- )
1344
-
1345
- // eslint-disable-next-line @typescript-eslint/no-empty-function
1346
- function noop() {}