Skip to content

Commit

Permalink
refactor(react-query-devtools): move devtools components in files (#5150
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
gpichot authored Apr 2, 2023
1 parent 633115a commit a67da4a
Show file tree
Hide file tree
Showing 8 changed files with 1,084 additions and 1,008 deletions.
380 changes: 380 additions & 0 deletions packages/react-query-devtools/src/CachePanel/ActiveQuery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,380 @@
import React from 'react'
import type { QueryCache, QueryClient } from '@tanstack/react-query'

import useSubscribeToQueryCache from '../useSubscribeToQueryCache'
import { Button, Code, Select, ActiveQueryPanel } from '../styledComponents'

import {
getQueryStatusLabel,
getQueryStatusColor,
displayValue,
} from '../utils'
import Explorer from '../Explorer'
import type { DevToolsErrorType } from '../types'
import { defaultTheme as theme } from '../theme'

// eslint-disable-next-line @typescript-eslint/no-empty-function
function noop() {}

/**
* Panel for the query currently being inspected
*
* It displays query details (key, observers...), query actions,
* the data explorer and the query explorer
*/
const ActiveQuery = ({
queryCache,
activeQueryHash,
queryClient,
errorTypes,
}: {
queryCache: QueryCache
activeQueryHash: string
queryClient: QueryClient
errorTypes: DevToolsErrorType[]
}) => {
const activeQuery = useSubscribeToQueryCache(queryCache, () =>
queryCache.getAll().find((query) => query.queryHash === activeQueryHash),
)

const activeQueryState = useSubscribeToQueryCache(
queryCache,
() =>
queryCache.getAll().find((query) => query.queryHash === activeQueryHash)
?.state,
)

const isStale =
useSubscribeToQueryCache(queryCache, () =>
queryCache
.getAll()
.find((query) => query.queryHash === activeQueryHash)
?.isStale(),
) ?? false

const observerCount =
useSubscribeToQueryCache(queryCache, () =>
queryCache
.getAll()
.find((query) => query.queryHash === activeQueryHash)
?.getObserversCount(),
) ?? 0

const handleRefetch = () => {
const promise = activeQuery?.fetch()
promise?.catch(noop)
}

const currentErrorTypeName = React.useMemo(() => {
if (activeQuery && activeQueryState?.error) {
const errorType = errorTypes.find(
(type) =>
type.initializer(activeQuery).toString() ===
activeQueryState.error?.toString(),
)
return errorType?.name
}
return undefined
}, [activeQuery, activeQueryState?.error, errorTypes])

if (!activeQuery || !activeQueryState) {
return null
}

const triggerError = (errorType?: DevToolsErrorType) => {
const error =
errorType?.initializer(activeQuery) ??
new Error('Unknown error from devtools')

const __previousQueryOptions = activeQuery.options

activeQuery.setState({
status: 'error',
error,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
fetchMeta: {
...activeQuery.state.fetchMeta,
__previousQueryOptions,
} as any,
})
}

const restoreQueryAfterLoadingOrError = () => {
activeQuery.fetch(
(activeQuery.state.fetchMeta as any).__previousQueryOptions,
{
// Make sure this fetch will cancel the previous one
cancelRefetch: true,
},
)
}

return (
<ActiveQueryPanel>
<div
style={{
padding: '.5em',
background: theme.backgroundAlt,
position: 'sticky',
top: 0,
zIndex: 1,
}}
>
Query Details
</div>
<div
style={{
padding: '.5em',
}}
>
<div
style={{
marginBottom: '.5em',
display: 'flex',
alignItems: 'flex-start',
justifyContent: 'space-between',
}}
>
<Code
style={{
lineHeight: '1.8em',
}}
>
<pre
style={{
margin: 0,
padding: 0,
overflow: 'auto',
}}
>
{displayValue(activeQuery.queryKey, true)}
</pre>
</Code>
<span
style={{
padding: '0.3em .6em',
borderRadius: '0.4em',
fontWeight: 'bold',
textShadow: '0 2px 10px black',
background: getQueryStatusColor({
queryState: activeQueryState,
isStale: isStale,
observerCount: observerCount,
theme,
}),
flexShrink: 0,
}}
>
{getQueryStatusLabel(activeQuery)}
</span>
</div>
<div
style={{
marginBottom: '.5em',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
Observers: <Code>{observerCount}</Code>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
Last Updated:{' '}
<Code>
{new Date(activeQueryState.dataUpdatedAt).toLocaleTimeString()}
</Code>
</div>
</div>
<div
style={{
background: theme.backgroundAlt,
padding: '.5em',
position: 'sticky',
top: 0,
zIndex: 1,
}}
>
Actions
</div>
<div
style={{
padding: '0.5em',
display: 'flex',
flexWrap: 'wrap',
gap: '0.5em',
alignItems: 'flex-end',
}}
>
<Button
type="button"
onClick={handleRefetch}
disabled={activeQueryState.fetchStatus === 'fetching'}
style={{
background: theme.active,
}}
>
Refetch
</Button>{' '}
<Button
type="button"
onClick={() => queryClient.invalidateQueries(activeQuery)}
style={{
background: theme.warning,
color: theme.inputTextColor,
}}
>
Invalidate
</Button>{' '}
<Button
type="button"
onClick={() => queryClient.resetQueries(activeQuery)}
style={{
background: theme.gray,
}}
>
Reset
</Button>{' '}
<Button
type="button"
onClick={() => queryClient.removeQueries(activeQuery)}
style={{
background: theme.danger,
}}
>
Remove
</Button>{' '}
<Button
type="button"
onClick={() => {
if (activeQuery.state.data === undefined) {
restoreQueryAfterLoadingOrError()
} else {
const __previousQueryOptions = activeQuery.options
// Trigger a fetch in order to trigger suspense as well.
activeQuery.fetch({
...__previousQueryOptions,
queryFn: () => {
return new Promise(() => {
// Never resolve
})
},
gcTime: -1,
})
activeQuery.setState({
data: undefined,
status: 'pending',
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
fetchMeta: {
...activeQuery.state.fetchMeta,
__previousQueryOptions,
} as any,
})
}
}}
style={{
background: theme.paused,
}}
>
{activeQuery.state.status === 'pending' ? 'Restore' : 'Trigger'}{' '}
loading
</Button>{' '}
{errorTypes.length === 0 || activeQuery.state.status === 'error' ? (
<Button
type="button"
onClick={() => {
if (!activeQuery.state.error) {
triggerError()
} else {
queryClient.resetQueries(activeQuery)
}
}}
style={{
background: theme.danger,
}}
>
{activeQuery.state.status === 'error' ? 'Restore' : 'Trigger'} error
</Button>
) : (
<label>
Trigger error:
<Select
value={currentErrorTypeName ?? ''}
style={{ marginInlineStart: '.5em' }}
onChange={(e) => {
const errorType = errorTypes.find(
(t) => t.name === e.target.value,
)

triggerError(errorType)
}}
>
<option key="" value="" />
{errorTypes.map((errorType) => (
<option key={errorType.name} value={errorType.name}>
{errorType.name}
</option>
))}
</Select>
</label>
)}
</div>
<div
style={{
background: theme.backgroundAlt,
padding: '.5em',
position: 'sticky',
top: 0,
zIndex: 1,
}}
>
Data Explorer
</div>
<div
style={{
padding: '.5em',
}}
>
<Explorer
label="Data"
value={activeQueryState.data}
defaultExpanded={{}}
copyable
/>
</div>
<div
style={{
background: theme.backgroundAlt,
padding: '.5em',
position: 'sticky',
top: 0,
zIndex: 1,
}}
>
Query Explorer
</div>
<div
style={{
padding: '.5em',
}}
>
<Explorer
label="Query"
value={activeQuery}
defaultExpanded={{
queryKey: true,
}}
/>
</div>
</ActiveQueryPanel>
)
}

ActiveQuery.displayName = 'ActiveQuery'

export default ActiveQuery
Loading

0 comments on commit a67da4a

Please sign in to comment.