@tanstack/react-query-devtools 4.26.0 → 4.27.0
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.
- package/build/lib/devtools.d.ts +23 -2
- package/build/lib/devtools.esm.js +107 -8
- package/build/lib/devtools.esm.js.map +1 -1
- package/build/lib/devtools.js +106 -8
- package/build/lib/devtools.js.map +1 -1
- package/build/lib/devtools.mjs +107 -8
- package/build/lib/devtools.mjs.map +1 -1
- package/build/lib/index.prod.esm.js +110 -9
- package/build/lib/index.prod.esm.js.map +1 -1
- package/build/lib/index.prod.js +109 -9
- package/build/lib/index.prod.js.map +1 -1
- package/build/lib/index.prod.mjs +110 -9
- package/build/lib/index.prod.mjs.map +1 -1
- package/build/lib/styledComponents.esm.js +3 -1
- package/build/lib/styledComponents.esm.js.map +1 -1
- package/build/lib/styledComponents.js +3 -1
- package/build/lib/styledComponents.js.map +1 -1
- package/build/lib/styledComponents.mjs +3 -1
- package/build/lib/styledComponents.mjs.map +1 -1
- package/build/umd/index.development.js +109 -9
- package/build/umd/index.development.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/devtools.test.tsx +139 -0
- package/src/devtools.tsx +144 -2
- package/src/styledComponents.ts +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/react-query-devtools",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.27.0",
|
|
4
4
|
"description": "Developer tools to interact with and visualize the TanStack/react-query cache",
|
|
5
5
|
"author": "tannerlinsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"react-dom": "^18.2.0",
|
|
47
47
|
"react-dom-17": "npm:react-dom@^17.0.2",
|
|
48
48
|
"react-error-boundary": "^3.1.4",
|
|
49
|
-
"@tanstack/react-query": "4.
|
|
49
|
+
"@tanstack/react-query": "4.27.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@tanstack/match-sorter-utils": "^8.7.0",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
58
58
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
59
|
-
"@tanstack/react-query": "4.
|
|
59
|
+
"@tanstack/react-query": "4.27.0"
|
|
60
60
|
},
|
|
61
61
|
"scripts": {
|
|
62
62
|
"clean": "rimraf ./build",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
sleep,
|
|
12
12
|
createQueryClient,
|
|
13
13
|
} from './utils'
|
|
14
|
+
import UserEvent from '@testing-library/user-event'
|
|
14
15
|
|
|
15
16
|
// TODO: This should be removed with the types for react-error-boundary get updated.
|
|
16
17
|
declare module 'react-error-boundary' {
|
|
@@ -19,6 +20,13 @@ declare module 'react-error-boundary' {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
class CustomError extends Error {
|
|
24
|
+
constructor(message: string) {
|
|
25
|
+
super(message)
|
|
26
|
+
this.name = 'CustomError'
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
Object.defineProperty(window, 'matchMedia', {
|
|
23
31
|
writable: true,
|
|
24
32
|
value: jest.fn().mockImplementation((query: string) => ({
|
|
@@ -915,4 +923,135 @@ describe('ReactQueryDevtools', () => {
|
|
|
915
923
|
fireEvent.click(screen.getByRole('button', { name: /^close$/i }))
|
|
916
924
|
expect(parentElement).toHaveStyle(parentPaddings)
|
|
917
925
|
})
|
|
926
|
+
|
|
927
|
+
it('should simulate loading state', async () => {
|
|
928
|
+
const { queryClient } = createQueryClient()
|
|
929
|
+
let count = 0
|
|
930
|
+
function App() {
|
|
931
|
+
const { data, fetchStatus } = useQuery(['key'], () => {
|
|
932
|
+
count++
|
|
933
|
+
return Promise.resolve('test')
|
|
934
|
+
})
|
|
935
|
+
|
|
936
|
+
return (
|
|
937
|
+
<div>
|
|
938
|
+
<h1>
|
|
939
|
+
{data ?? 'No data'}, {fetchStatus}
|
|
940
|
+
</h1>
|
|
941
|
+
</div>
|
|
942
|
+
)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
renderWithClient(queryClient, <App />, {
|
|
946
|
+
initialIsOpen: true,
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
await screen.findByRole('heading', { name: /test/i })
|
|
950
|
+
|
|
951
|
+
const loadingButton = await screen.findByRole('button', {
|
|
952
|
+
name: 'Trigger loading',
|
|
953
|
+
})
|
|
954
|
+
fireEvent.click(loadingButton)
|
|
955
|
+
|
|
956
|
+
await waitFor(() => {
|
|
957
|
+
expect(screen.getByText('Restore loading')).toBeInTheDocument()
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
await waitFor(() => {
|
|
961
|
+
expect(screen.getByText('No data, fetching')).toBeInTheDocument()
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
fireEvent.click(screen.getByRole('button', { name: /restore loading/i }))
|
|
965
|
+
|
|
966
|
+
await waitFor(() => {
|
|
967
|
+
expect(screen.getByText('test, idle')).toBeInTheDocument()
|
|
968
|
+
})
|
|
969
|
+
|
|
970
|
+
expect(count).toBe(2)
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
it('should simulate error state', async () => {
|
|
974
|
+
const { queryClient } = createQueryClient()
|
|
975
|
+
function App() {
|
|
976
|
+
const { status, error } = useQuery(['key'], () => {
|
|
977
|
+
return Promise.resolve('test')
|
|
978
|
+
})
|
|
979
|
+
|
|
980
|
+
return (
|
|
981
|
+
<div>
|
|
982
|
+
<h1>
|
|
983
|
+
{!!error ? 'Some error' : 'No error'}, {status}
|
|
984
|
+
</h1>
|
|
985
|
+
</div>
|
|
986
|
+
)
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
renderWithClient(queryClient, <App />, {
|
|
990
|
+
initialIsOpen: true,
|
|
991
|
+
})
|
|
992
|
+
|
|
993
|
+
const errorButton = await screen.findByRole('button', {
|
|
994
|
+
name: 'Trigger error',
|
|
995
|
+
})
|
|
996
|
+
fireEvent.click(errorButton)
|
|
997
|
+
|
|
998
|
+
await waitFor(() => {
|
|
999
|
+
expect(screen.getByText('Restore error')).toBeInTheDocument()
|
|
1000
|
+
})
|
|
1001
|
+
|
|
1002
|
+
await waitFor(() => {
|
|
1003
|
+
expect(screen.getByText('Some error, error')).toBeInTheDocument()
|
|
1004
|
+
})
|
|
1005
|
+
|
|
1006
|
+
fireEvent.click(screen.getByRole('button', { name: /Restore error/i }))
|
|
1007
|
+
|
|
1008
|
+
await waitFor(() => {
|
|
1009
|
+
expect(screen.getByText('No error, success')).toBeInTheDocument()
|
|
1010
|
+
})
|
|
1011
|
+
})
|
|
1012
|
+
|
|
1013
|
+
it('should can simulate a specific error', async () => {
|
|
1014
|
+
const { queryClient } = createQueryClient()
|
|
1015
|
+
|
|
1016
|
+
function App() {
|
|
1017
|
+
const { status, error } = useQuery(['key'], () => {
|
|
1018
|
+
return Promise.resolve('test')
|
|
1019
|
+
})
|
|
1020
|
+
|
|
1021
|
+
return (
|
|
1022
|
+
<div data-testid="test">
|
|
1023
|
+
<h1>
|
|
1024
|
+
{error instanceof CustomError
|
|
1025
|
+
? error.message.toString()
|
|
1026
|
+
: 'No error'}
|
|
1027
|
+
, {status}
|
|
1028
|
+
</h1>
|
|
1029
|
+
</div>
|
|
1030
|
+
)
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
renderWithClient(queryClient, <App />, {
|
|
1034
|
+
initialIsOpen: true,
|
|
1035
|
+
errorTypes: [
|
|
1036
|
+
{
|
|
1037
|
+
name: 'error1',
|
|
1038
|
+
initializer: () => new CustomError('error1'),
|
|
1039
|
+
},
|
|
1040
|
+
],
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
const errorOption = await screen.findByLabelText('Trigger error:')
|
|
1044
|
+
|
|
1045
|
+
UserEvent.selectOptions(errorOption, 'error1')
|
|
1046
|
+
|
|
1047
|
+
await waitFor(() => {
|
|
1048
|
+
expect(screen.getByText('error1, error')).toBeInTheDocument()
|
|
1049
|
+
})
|
|
1050
|
+
|
|
1051
|
+
fireEvent.click(screen.getByRole('button', { name: /Restore error/i }))
|
|
1052
|
+
|
|
1053
|
+
await waitFor(() => {
|
|
1054
|
+
expect(screen.getByText('No error, success')).toBeInTheDocument()
|
|
1055
|
+
})
|
|
1056
|
+
})
|
|
918
1057
|
})
|
package/src/devtools.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
QueryClient,
|
|
7
7
|
QueryKey as QueryKeyType,
|
|
8
8
|
ContextOptions,
|
|
9
|
+
Query,
|
|
9
10
|
} from '@tanstack/react-query'
|
|
10
11
|
import {
|
|
11
12
|
useQueryClient,
|
|
@@ -41,6 +42,19 @@ import { ThemeProvider, defaultTheme as theme } from './theme'
|
|
|
41
42
|
import { getQueryStatusLabel, getQueryStatusColor } from './utils'
|
|
42
43
|
import Explorer from './Explorer'
|
|
43
44
|
import Logo from './Logo'
|
|
45
|
+
import { useMemo } from 'react'
|
|
46
|
+
|
|
47
|
+
export interface DevToolsErrorType {
|
|
48
|
+
/**
|
|
49
|
+
* The name of the error.
|
|
50
|
+
*/
|
|
51
|
+
name: string
|
|
52
|
+
/**
|
|
53
|
+
* How the error is initialized. Whatever it returns MUST implement toString() so
|
|
54
|
+
* we can check against the current error.
|
|
55
|
+
*/
|
|
56
|
+
initializer: (query: Query) => { toString(): string }
|
|
57
|
+
}
|
|
44
58
|
|
|
45
59
|
export interface DevtoolsOptions extends ContextOptions {
|
|
46
60
|
/**
|
|
@@ -79,6 +93,10 @@ export interface DevtoolsOptions extends ContextOptions {
|
|
|
79
93
|
* nonce for style element for CSP
|
|
80
94
|
*/
|
|
81
95
|
styleNonce?: string
|
|
96
|
+
/**
|
|
97
|
+
* Use this so you can define custom errors that can be shown in the devtools.
|
|
98
|
+
*/
|
|
99
|
+
errorTypes?: DevToolsErrorType[]
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
interface DevtoolsPanelOptions extends ContextOptions {
|
|
@@ -123,6 +141,10 @@ interface DevtoolsPanelOptions extends ContextOptions {
|
|
|
123
141
|
* 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.
|
|
124
142
|
*/
|
|
125
143
|
closeButtonProps?: React.ComponentPropsWithoutRef<'button'>
|
|
144
|
+
/**
|
|
145
|
+
* Use this so you can define custom errors that can be shown in the devtools.
|
|
146
|
+
*/
|
|
147
|
+
errorTypes?: DevToolsErrorType[]
|
|
126
148
|
}
|
|
127
149
|
|
|
128
150
|
export function ReactQueryDevtools({
|
|
@@ -135,6 +157,7 @@ export function ReactQueryDevtools({
|
|
|
135
157
|
context,
|
|
136
158
|
styleNonce,
|
|
137
159
|
panelPosition: initialPanelPosition = 'bottom',
|
|
160
|
+
errorTypes = [],
|
|
138
161
|
}: DevtoolsOptions): React.ReactElement | null {
|
|
139
162
|
const rootRef = React.useRef<HTMLDivElement>(null)
|
|
140
163
|
const panelRef = React.useRef<HTMLDivElement>(null)
|
|
@@ -343,6 +366,7 @@ export function ReactQueryDevtools({
|
|
|
343
366
|
isOpen={isResolvedOpen}
|
|
344
367
|
setIsOpen={setIsOpen}
|
|
345
368
|
onDragStart={(e) => handleDragStart(panelRef.current, e)}
|
|
369
|
+
errorTypes={errorTypes}
|
|
346
370
|
/>
|
|
347
371
|
</ThemeProvider>
|
|
348
372
|
{!isResolvedOpen ? (
|
|
@@ -433,6 +457,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef<
|
|
|
433
457
|
showCloseButton,
|
|
434
458
|
position,
|
|
435
459
|
closeButtonProps = {},
|
|
460
|
+
errorTypes = [],
|
|
436
461
|
...panelProps
|
|
437
462
|
} = props
|
|
438
463
|
|
|
@@ -605,6 +630,8 @@ export const ReactQueryDevtoolsPanel = React.forwardRef<
|
|
|
605
630
|
style={{
|
|
606
631
|
display: 'flex',
|
|
607
632
|
alignItems: 'center',
|
|
633
|
+
flexWrap: 'wrap',
|
|
634
|
+
gap: '0.5em',
|
|
608
635
|
}}
|
|
609
636
|
>
|
|
610
637
|
<Input
|
|
@@ -617,7 +644,6 @@ export const ReactQueryDevtoolsPanel = React.forwardRef<
|
|
|
617
644
|
}}
|
|
618
645
|
style={{
|
|
619
646
|
flex: '1',
|
|
620
|
-
marginRight: '.5em',
|
|
621
647
|
width: '100%',
|
|
622
648
|
}}
|
|
623
649
|
/>
|
|
@@ -738,6 +764,7 @@ export const ReactQueryDevtoolsPanel = React.forwardRef<
|
|
|
738
764
|
activeQueryHash={activeQueryHash}
|
|
739
765
|
queryCache={queryCache}
|
|
740
766
|
queryClient={queryClient}
|
|
767
|
+
errorTypes={errorTypes}
|
|
741
768
|
/>
|
|
742
769
|
) : null}
|
|
743
770
|
|
|
@@ -773,10 +800,12 @@ const ActiveQuery = ({
|
|
|
773
800
|
queryCache,
|
|
774
801
|
activeQueryHash,
|
|
775
802
|
queryClient,
|
|
803
|
+
errorTypes,
|
|
776
804
|
}: {
|
|
777
805
|
queryCache: QueryCache
|
|
778
806
|
activeQueryHash: string
|
|
779
807
|
queryClient: QueryClient
|
|
808
|
+
errorTypes: DevToolsErrorType[]
|
|
780
809
|
}) => {
|
|
781
810
|
const activeQuery = useSubscribeToQueryCache(queryCache, () =>
|
|
782
811
|
queryCache.getAll().find((query) => query.queryHash === activeQueryHash),
|
|
@@ -810,10 +839,46 @@ const ActiveQuery = ({
|
|
|
810
839
|
promise?.catch(noop)
|
|
811
840
|
}
|
|
812
841
|
|
|
842
|
+
const currentErrorTypeName = useMemo(() => {
|
|
843
|
+
if (activeQuery && activeQueryState?.error) {
|
|
844
|
+
const errorType = errorTypes.find(
|
|
845
|
+
(type) =>
|
|
846
|
+
type.initializer(activeQuery).toString() ===
|
|
847
|
+
activeQueryState.error?.toString(),
|
|
848
|
+
)
|
|
849
|
+
return errorType?.name
|
|
850
|
+
}
|
|
851
|
+
return undefined
|
|
852
|
+
}, [activeQuery, activeQueryState?.error, errorTypes])
|
|
853
|
+
|
|
813
854
|
if (!activeQuery || !activeQueryState) {
|
|
814
855
|
return null
|
|
815
856
|
}
|
|
816
857
|
|
|
858
|
+
const triggerError = (errorType?: DevToolsErrorType) => {
|
|
859
|
+
const error =
|
|
860
|
+
errorType?.initializer(activeQuery) ??
|
|
861
|
+
new Error('Unknown error from devtools')
|
|
862
|
+
|
|
863
|
+
const __previousQueryOptions = activeQuery.options
|
|
864
|
+
|
|
865
|
+
activeQuery.setState({
|
|
866
|
+
status: 'error',
|
|
867
|
+
error,
|
|
868
|
+
fetchMeta: {
|
|
869
|
+
...activeQuery.state.fetchMeta,
|
|
870
|
+
__previousQueryOptions,
|
|
871
|
+
},
|
|
872
|
+
})
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const restoreQueryAfterLoadingOrError = () => {
|
|
876
|
+
activeQuery.fetch(activeQuery.state.fetchMeta.__previousQueryOptions, {
|
|
877
|
+
// Make sure this fetch will cancel the previous one
|
|
878
|
+
cancelRefetch: true,
|
|
879
|
+
})
|
|
880
|
+
}
|
|
881
|
+
|
|
817
882
|
return (
|
|
818
883
|
<ActiveQueryPanel>
|
|
819
884
|
<div
|
|
@@ -910,6 +975,10 @@ const ActiveQuery = ({
|
|
|
910
975
|
<div
|
|
911
976
|
style={{
|
|
912
977
|
padding: '0.5em',
|
|
978
|
+
display: 'flex',
|
|
979
|
+
flexWrap: 'wrap',
|
|
980
|
+
gap: '0.5em',
|
|
981
|
+
alignItems: 'flex-end',
|
|
913
982
|
}}
|
|
914
983
|
>
|
|
915
984
|
<Button
|
|
@@ -949,7 +1018,80 @@ const ActiveQuery = ({
|
|
|
949
1018
|
}}
|
|
950
1019
|
>
|
|
951
1020
|
Remove
|
|
952
|
-
</Button>
|
|
1021
|
+
</Button>{' '}
|
|
1022
|
+
<Button
|
|
1023
|
+
type="button"
|
|
1024
|
+
onClick={() => {
|
|
1025
|
+
if (activeQuery.state.data === undefined) {
|
|
1026
|
+
restoreQueryAfterLoadingOrError()
|
|
1027
|
+
} else {
|
|
1028
|
+
const __previousQueryOptions = activeQuery.options
|
|
1029
|
+
// Trigger a fetch in order to trigger suspense as well.
|
|
1030
|
+
activeQuery.fetch({
|
|
1031
|
+
...__previousQueryOptions,
|
|
1032
|
+
queryFn: () => {
|
|
1033
|
+
return new Promise(() => {
|
|
1034
|
+
// Never resolve
|
|
1035
|
+
})
|
|
1036
|
+
},
|
|
1037
|
+
cacheTime: -1,
|
|
1038
|
+
})
|
|
1039
|
+
activeQuery.setState({
|
|
1040
|
+
data: undefined,
|
|
1041
|
+
status: 'loading',
|
|
1042
|
+
fetchMeta: {
|
|
1043
|
+
...activeQuery.state.fetchMeta,
|
|
1044
|
+
__previousQueryOptions,
|
|
1045
|
+
},
|
|
1046
|
+
})
|
|
1047
|
+
}
|
|
1048
|
+
}}
|
|
1049
|
+
style={{
|
|
1050
|
+
background: theme.paused,
|
|
1051
|
+
}}
|
|
1052
|
+
>
|
|
1053
|
+
{activeQuery.state.status === 'loading' ? 'Restore' : 'Trigger'}{' '}
|
|
1054
|
+
loading
|
|
1055
|
+
</Button>{' '}
|
|
1056
|
+
{errorTypes.length === 0 || activeQuery.state.status === 'error' ? (
|
|
1057
|
+
<Button
|
|
1058
|
+
type="button"
|
|
1059
|
+
onClick={() => {
|
|
1060
|
+
if (!activeQuery.state.error) {
|
|
1061
|
+
triggerError()
|
|
1062
|
+
} else {
|
|
1063
|
+
queryClient.resetQueries(activeQuery)
|
|
1064
|
+
}
|
|
1065
|
+
}}
|
|
1066
|
+
style={{
|
|
1067
|
+
background: theme.danger,
|
|
1068
|
+
}}
|
|
1069
|
+
>
|
|
1070
|
+
{activeQuery.state.status === 'error' ? 'Restore' : 'Trigger'} error
|
|
1071
|
+
</Button>
|
|
1072
|
+
) : (
|
|
1073
|
+
<label>
|
|
1074
|
+
Trigger error:
|
|
1075
|
+
<Select
|
|
1076
|
+
value={currentErrorTypeName ?? ''}
|
|
1077
|
+
style={{ marginInlineStart: '.5em' }}
|
|
1078
|
+
onChange={(e) => {
|
|
1079
|
+
const errorType = errorTypes.find(
|
|
1080
|
+
(t) => t.name === e.target.value,
|
|
1081
|
+
)
|
|
1082
|
+
|
|
1083
|
+
triggerError(errorType)
|
|
1084
|
+
}}
|
|
1085
|
+
>
|
|
1086
|
+
<option key="" value="" />
|
|
1087
|
+
{errorTypes.map((errorType) => (
|
|
1088
|
+
<option key={errorType.name} value={errorType.name}>
|
|
1089
|
+
{errorType.name}
|
|
1090
|
+
</option>
|
|
1091
|
+
))}
|
|
1092
|
+
</Select>
|
|
1093
|
+
</label>
|
|
1094
|
+
)}
|
|
953
1095
|
</div>
|
|
954
1096
|
<div
|
|
955
1097
|
style={{
|
package/src/styledComponents.ts
CHANGED