Skip to content

Commit a67da4a

Browse files
authored
refactor(react-query-devtools): move devtools components in files (#5150)
* refactor(react-query-devtools): move QueryStatusCount header to subdirectory * refactor(react-query-devtools): move QueryRow to subdirectory * refactor(react-query-devtools): move ActiveQuery to subdirectory * refactor(react-query-devtools): move Cache Panel to subdirectory * lint * refactor: add display names * fix: typo
1 parent 633115a commit a67da4a

File tree

8 files changed

+1084
-1008
lines changed

8 files changed

+1084
-1008
lines changed
Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
import React from 'react'
2+
import type { QueryCache, QueryClient } from '@tanstack/react-query'
3+
4+
import useSubscribeToQueryCache from '../useSubscribeToQueryCache'
5+
import { Button, Code, Select, ActiveQueryPanel } from '../styledComponents'
6+
7+
import {
8+
getQueryStatusLabel,
9+
getQueryStatusColor,
10+
displayValue,
11+
} from '../utils'
12+
import Explorer from '../Explorer'
13+
import type { DevToolsErrorType } from '../types'
14+
import { defaultTheme as theme } from '../theme'
15+
16+
// eslint-disable-next-line @typescript-eslint/no-empty-function
17+
function noop() {}
18+
19+
/**
20+
* Panel for the query currently being inspected
21+
*
22+
* It displays query details (key, observers...), query actions,
23+
* the data explorer and the query explorer
24+
*/
25+
const ActiveQuery = ({
26+
queryCache,
27+
activeQueryHash,
28+
queryClient,
29+
errorTypes,
30+
}: {
31+
queryCache: QueryCache
32+
activeQueryHash: string
33+
queryClient: QueryClient
34+
errorTypes: DevToolsErrorType[]
35+
}) => {
36+
const activeQuery = useSubscribeToQueryCache(queryCache, () =>
37+
queryCache.getAll().find((query) => query.queryHash === activeQueryHash),
38+
)
39+
40+
const activeQueryState = useSubscribeToQueryCache(
41+
queryCache,
42+
() =>
43+
queryCache.getAll().find((query) => query.queryHash === activeQueryHash)
44+
?.state,
45+
)
46+
47+
const isStale =
48+
useSubscribeToQueryCache(queryCache, () =>
49+
queryCache
50+
.getAll()
51+
.find((query) => query.queryHash === activeQueryHash)
52+
?.isStale(),
53+
) ?? false
54+
55+
const observerCount =
56+
useSubscribeToQueryCache(queryCache, () =>
57+
queryCache
58+
.getAll()
59+
.find((query) => query.queryHash === activeQueryHash)
60+
?.getObserversCount(),
61+
) ?? 0
62+
63+
const handleRefetch = () => {
64+
const promise = activeQuery?.fetch()
65+
promise?.catch(noop)
66+
}
67+
68+
const currentErrorTypeName = React.useMemo(() => {
69+
if (activeQuery && activeQueryState?.error) {
70+
const errorType = errorTypes.find(
71+
(type) =>
72+
type.initializer(activeQuery).toString() ===
73+
activeQueryState.error?.toString(),
74+
)
75+
return errorType?.name
76+
}
77+
return undefined
78+
}, [activeQuery, activeQueryState?.error, errorTypes])
79+
80+
if (!activeQuery || !activeQueryState) {
81+
return null
82+
}
83+
84+
const triggerError = (errorType?: DevToolsErrorType) => {
85+
const error =
86+
errorType?.initializer(activeQuery) ??
87+
new Error('Unknown error from devtools')
88+
89+
const __previousQueryOptions = activeQuery.options
90+
91+
activeQuery.setState({
92+
status: 'error',
93+
error,
94+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
95+
fetchMeta: {
96+
...activeQuery.state.fetchMeta,
97+
__previousQueryOptions,
98+
} as any,
99+
})
100+
}
101+
102+
const restoreQueryAfterLoadingOrError = () => {
103+
activeQuery.fetch(
104+
(activeQuery.state.fetchMeta as any).__previousQueryOptions,
105+
{
106+
// Make sure this fetch will cancel the previous one
107+
cancelRefetch: true,
108+
},
109+
)
110+
}
111+
112+
return (
113+
<ActiveQueryPanel>
114+
<div
115+
style={{
116+
padding: '.5em',
117+
background: theme.backgroundAlt,
118+
position: 'sticky',
119+
top: 0,
120+
zIndex: 1,
121+
}}
122+
>
123+
Query Details
124+
</div>
125+
<div
126+
style={{
127+
padding: '.5em',
128+
}}
129+
>
130+
<div
131+
style={{
132+
marginBottom: '.5em',
133+
display: 'flex',
134+
alignItems: 'flex-start',
135+
justifyContent: 'space-between',
136+
}}
137+
>
138+
<Code
139+
style={{
140+
lineHeight: '1.8em',
141+
}}
142+
>
143+
<pre
144+
style={{
145+
margin: 0,
146+
padding: 0,
147+
overflow: 'auto',
148+
}}
149+
>
150+
{displayValue(activeQuery.queryKey, true)}
151+
</pre>
152+
</Code>
153+
<span
154+
style={{
155+
padding: '0.3em .6em',
156+
borderRadius: '0.4em',
157+
fontWeight: 'bold',
158+
textShadow: '0 2px 10px black',
159+
background: getQueryStatusColor({
160+
queryState: activeQueryState,
161+
isStale: isStale,
162+
observerCount: observerCount,
163+
theme,
164+
}),
165+
flexShrink: 0,
166+
}}
167+
>
168+
{getQueryStatusLabel(activeQuery)}
169+
</span>
170+
</div>
171+
<div
172+
style={{
173+
marginBottom: '.5em',
174+
display: 'flex',
175+
alignItems: 'center',
176+
justifyContent: 'space-between',
177+
}}
178+
>
179+
Observers: <Code>{observerCount}</Code>
180+
</div>
181+
<div
182+
style={{
183+
display: 'flex',
184+
alignItems: 'center',
185+
justifyContent: 'space-between',
186+
}}
187+
>
188+
Last Updated:{' '}
189+
<Code>
190+
{new Date(activeQueryState.dataUpdatedAt).toLocaleTimeString()}
191+
</Code>
192+
</div>
193+
</div>
194+
<div
195+
style={{
196+
background: theme.backgroundAlt,
197+
padding: '.5em',
198+
position: 'sticky',
199+
top: 0,
200+
zIndex: 1,
201+
}}
202+
>
203+
Actions
204+
</div>
205+
<div
206+
style={{
207+
padding: '0.5em',
208+
display: 'flex',
209+
flexWrap: 'wrap',
210+
gap: '0.5em',
211+
alignItems: 'flex-end',
212+
}}
213+
>
214+
<Button
215+
type="button"
216+
onClick={handleRefetch}
217+
disabled={activeQueryState.fetchStatus === 'fetching'}
218+
style={{
219+
background: theme.active,
220+
}}
221+
>
222+
Refetch
223+
</Button>{' '}
224+
<Button
225+
type="button"
226+
onClick={() => queryClient.invalidateQueries(activeQuery)}
227+
style={{
228+
background: theme.warning,
229+
color: theme.inputTextColor,
230+
}}
231+
>
232+
Invalidate
233+
</Button>{' '}
234+
<Button
235+
type="button"
236+
onClick={() => queryClient.resetQueries(activeQuery)}
237+
style={{
238+
background: theme.gray,
239+
}}
240+
>
241+
Reset
242+
</Button>{' '}
243+
<Button
244+
type="button"
245+
onClick={() => queryClient.removeQueries(activeQuery)}
246+
style={{
247+
background: theme.danger,
248+
}}
249+
>
250+
Remove
251+
</Button>{' '}
252+
<Button
253+
type="button"
254+
onClick={() => {
255+
if (activeQuery.state.data === undefined) {
256+
restoreQueryAfterLoadingOrError()
257+
} else {
258+
const __previousQueryOptions = activeQuery.options
259+
// Trigger a fetch in order to trigger suspense as well.
260+
activeQuery.fetch({
261+
...__previousQueryOptions,
262+
queryFn: () => {
263+
return new Promise(() => {
264+
// Never resolve
265+
})
266+
},
267+
gcTime: -1,
268+
})
269+
activeQuery.setState({
270+
data: undefined,
271+
status: 'pending',
272+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
273+
fetchMeta: {
274+
...activeQuery.state.fetchMeta,
275+
__previousQueryOptions,
276+
} as any,
277+
})
278+
}
279+
}}
280+
style={{
281+
background: theme.paused,
282+
}}
283+
>
284+
{activeQuery.state.status === 'pending' ? 'Restore' : 'Trigger'}{' '}
285+
loading
286+
</Button>{' '}
287+
{errorTypes.length === 0 || activeQuery.state.status === 'error' ? (
288+
<Button
289+
type="button"
290+
onClick={() => {
291+
if (!activeQuery.state.error) {
292+
triggerError()
293+
} else {
294+
queryClient.resetQueries(activeQuery)
295+
}
296+
}}
297+
style={{
298+
background: theme.danger,
299+
}}
300+
>
301+
{activeQuery.state.status === 'error' ? 'Restore' : 'Trigger'} error
302+
</Button>
303+
) : (
304+
<label>
305+
Trigger error:
306+
<Select
307+
value={currentErrorTypeName ?? ''}
308+
style={{ marginInlineStart: '.5em' }}
309+
onChange={(e) => {
310+
const errorType = errorTypes.find(
311+
(t) => t.name === e.target.value,
312+
)
313+
314+
triggerError(errorType)
315+
}}
316+
>
317+
<option key="" value="" />
318+
{errorTypes.map((errorType) => (
319+
<option key={errorType.name} value={errorType.name}>
320+
{errorType.name}
321+
</option>
322+
))}
323+
</Select>
324+
</label>
325+
)}
326+
</div>
327+
<div
328+
style={{
329+
background: theme.backgroundAlt,
330+
padding: '.5em',
331+
position: 'sticky',
332+
top: 0,
333+
zIndex: 1,
334+
}}
335+
>
336+
Data Explorer
337+
</div>
338+
<div
339+
style={{
340+
padding: '.5em',
341+
}}
342+
>
343+
<Explorer
344+
label="Data"
345+
value={activeQueryState.data}
346+
defaultExpanded={{}}
347+
copyable
348+
/>
349+
</div>
350+
<div
351+
style={{
352+
background: theme.backgroundAlt,
353+
padding: '.5em',
354+
position: 'sticky',
355+
top: 0,
356+
zIndex: 1,
357+
}}
358+
>
359+
Query Explorer
360+
</div>
361+
<div
362+
style={{
363+
padding: '.5em',
364+
}}
365+
>
366+
<Explorer
367+
label="Query"
368+
value={activeQuery}
369+
defaultExpanded={{
370+
queryKey: true,
371+
}}
372+
/>
373+
</div>
374+
</ActiveQueryPanel>
375+
)
376+
}
377+
378+
ActiveQuery.displayName = 'ActiveQuery'
379+
380+
export default ActiveQuery

0 commit comments

Comments
 (0)