Skip to content

🏝️ TanStack Query DevTools for Expo/React Native! 🚀 #8846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions docs/framework/react/devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ Wave your hands in the air and shout hooray because React Query comes with dedic

When you begin your React Query journey, you'll want these devtools by your side. They help visualize all the inner workings of React Query and will likely save you hours of debugging if you find yourself in a pinch!

> Please note that for now, the devtools **do not support React Native**. If you would like to help us make the devtools platform-agnostic, please let us know!

> Exciting News: We now have a separate package for React Native React Query DevTools! This new addition brings native support, allowing you to integrate DevTools directly into your React Native projects. Check it out and contribute here: [react-native-react-query-devtools](https://github.com/LovesWorking/react-native-react-query-devtools)

> An external tool is also available that enables the use of React Query DevTools via an external dashboard. Find out more and contribute on [react-query-external-sync](https://github.com/LovesWorking/react-query-external-sync)
> For React Native users: A third-party native macOS app is available for debugging React Query in ANY js-based application. Monitor queries across devices in real-time. Check it out here: [rn-better-dev-tools](https://github.com/LovesWorking/rn-better-dev-tools)

> Note that since version 5, the dev tools support observing mutations as well.

Expand Down
15 changes: 10 additions & 5 deletions docs/framework/react/react-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ id: react-native
title: React Native
---

React Query is designed to work out of the box with React Native, with the exception of the devtools, which are only supported with React DOM at this time.
React Query is designed to work out of the box with React Native.

There is a 3rd party [Expo](https://docs.expo.dev/) plugin which you can try: https://github.com/expo/dev-plugins/tree/main/packages/react-query
## DevTools Support

There is a 3rd party [Flipper](https://fbflipper.com/docs/getting-started/react-native/) plugin which you can try: https://github.com/bgaleotti/react-query-native-devtools
There are several options available for React Native DevTools integration:

There is a 3rd party [Reactotron](https://github.com/infinitered/reactotron/) plugin which you can try: https://github.com/hsndmr/reactotron-react-query
1. **Native macOS App**: A 3rd party app for debugging React Query in any js-based application:
https://github.com/LovesWorking/rn-better-dev-tools

If you would like to help us make the built-in devtools platform agnostic, please let us know!
2. **Flipper Plugin**: A 3rd party plugin for Flipper users:
https://github.com/bgaleotti/react-query-native-devtools

3. **Reactotron Plugin**: A 3rd party plugin for Reactotron users:
https://github.com/hsndmr/reactotron-react-query

## Online status management

Expand Down
106 changes: 92 additions & 14 deletions packages/query-devtools/src/Devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ import type {
Query,
QueryCache,
QueryCacheNotifyEvent,
QueryClient,
QueryState,
} from '@tanstack/query-core'
import type { StorageObject, StorageSetter } from '@solid-primitives/storage'
Expand Down Expand Up @@ -120,6 +121,18 @@ export const Devtools: Component<DevtoolsPanelProps> = (props) => {
const styles = createMemo(() => {
return theme() === 'dark' ? darkStyles(css) : lightStyles(css)
})
const onlineManager = createMemo(
() => useQueryDevtoolsContext().onlineManager,
)
onMount(() => {
const unsubscribe = onlineManager().subscribe((online) => {
setOffline(!online)
})

onCleanup(() => {
unsubscribe()
})
})

const pip = usePiPWindow()

Expand Down Expand Up @@ -922,8 +935,10 @@ export const ContentView: Component<ContentViewProps> = (props) => {
<button
onClick={() => {
if (selectedView() === 'queries') {
sendDevToolsEvent({ type: 'CLEAR_QUERY_CACHE' })
query_cache().clear()
} else {
sendDevToolsEvent({ type: 'CLEAR_MUTATION_CACHE' })
mutation_cache().clear()
}
}}
Expand All @@ -939,13 +954,7 @@ export const ContentView: Component<ContentViewProps> = (props) => {
</button>
<button
onClick={() => {
if (offline()) {
onlineManager().setOnline(true)
setOffline(false)
} else {
onlineManager().setOnline(false)
setOffline(true)
}
onlineManager().setOnline(!onlineManager().isOnline())
}}
class={cx(
styles().actionsBtn,
Expand Down Expand Up @@ -1768,29 +1777,43 @@ const QueryDetails = () => {
const color = createMemo(() => getQueryStatusColorByLabel(statusLabel()))

const handleRefetch = () => {
sendDevToolsEvent({ type: 'REFETCH', queryHash: activeQuery()?.queryHash })
const promise = activeQuery()?.fetch()
promise?.catch(() => {})
}

const triggerError = (errorType?: DevtoolsErrorType) => {
const activeQueryVal = activeQuery()
if (!activeQueryVal) return
sendDevToolsEvent({
type: 'TRIGGER_ERROR',
queryHash: activeQueryVal.queryHash,
metadata: { error: errorType?.name },
})
const error =
errorType?.initializer(activeQuery()!) ??
errorType?.initializer(activeQueryVal) ??
new Error('Unknown error from devtools')

const __previousQueryOptions = activeQuery()!.options
const __previousQueryOptions = activeQueryVal.options

activeQuery()!.setState({
activeQueryVal.setState({
status: 'error',
error,
fetchMeta: {
...activeQuery()!.state.fetchMeta,
...activeQueryVal.state.fetchMeta,
__previousQueryOptions,
} as any,
} as QueryState<unknown, Error>)
}

const restoreQueryAfterLoadingOrError = () => {
const activeQueryVal = activeQuery()!
const activeQueryVal = activeQuery()
if (!activeQueryVal) return

sendDevToolsEvent({
type: 'RESTORE_LOADING',
queryHash: activeQueryVal.queryHash,
})
const previousState = activeQueryVal.state
const previousOptions = activeQueryVal.state.fetchMeta
? (activeQueryVal.state.fetchMeta as any).__previousQueryOptions
Expand Down Expand Up @@ -1899,7 +1922,13 @@ const QueryDetails = () => {
'tsqd-query-details-actions-btn',
'tsqd-query-details-action-invalidate',
)}
onClick={() => queryClient.invalidateQueries(activeQuery())}
onClick={() => {
sendDevToolsEvent({
type: 'INVALIDATE',
queryHash: activeQuery()?.queryHash,
})
queryClient.invalidateQueries(activeQuery())
}}
disabled={queryStatus() === 'pending'}
>
<span
Expand All @@ -1917,7 +1946,13 @@ const QueryDetails = () => {
'tsqd-query-details-actions-btn',
'tsqd-query-details-action-reset',
)}
onClick={() => queryClient.resetQueries(activeQuery())}
onClick={() => {
sendDevToolsEvent({
type: 'RESET',
queryHash: activeQuery()?.queryHash,
})
queryClient.resetQueries(activeQuery())
}}
disabled={queryStatus() === 'pending'}
>
<span
Expand All @@ -1936,6 +1971,10 @@ const QueryDetails = () => {
'tsqd-query-details-action-remove',
)}
onClick={() => {
sendDevToolsEvent({
type: 'REMOVE',
queryHash: activeQuery()?.queryHash,
})
queryClient.removeQueries(activeQuery())
setSelectedQueryHash(null)
}}
Expand Down Expand Up @@ -1964,6 +2003,10 @@ const QueryDetails = () => {
} else {
const activeQueryVal = activeQuery()
if (!activeQueryVal) return
sendDevToolsEvent({
type: 'TRIGGER_LOADING',
queryHash: activeQueryVal.queryHash,
})
const __previousQueryOptions = activeQueryVal.options
// Trigger a fetch in order to trigger suspense as well.
activeQueryVal.fetch({
Expand Down Expand Up @@ -2006,6 +2049,10 @@ const QueryDetails = () => {
if (!activeQuery()!.state.error) {
triggerError()
} else {
sendDevToolsEvent({
type: 'RESTORE_ERROR',
queryHash: activeQuery()?.queryHash,
})
queryClient.resetQueries(activeQuery())
}
}}
Expand Down Expand Up @@ -2438,6 +2485,37 @@ const createSubscribeToMutationCacheBatcher = <T,>(
return value
}

export type DevToolsActionType =
| 'REFETCH'
| 'INVALIDATE'
| 'RESET'
| 'REMOVE'
| 'TRIGGER_ERROR'
| 'RESTORE_ERROR'
| 'TRIGGER_LOADING'
| 'RESTORE_LOADING'
| 'CLEAR_MUTATION_CACHE'
| 'CLEAR_QUERY_CACHE'

export const DEV_TOOLS_EVENT = '@tanstack/query-devtools-event'

export const sendDevToolsEvent = ({
type,
queryHash,
metadata,
}: {
type: DevToolsActionType
queryHash?: string
metadata?: Record<string, unknown>
}) => {
const event = new CustomEvent(DEV_TOOLS_EVENT, {
detail: { type, queryHash, metadata },
bubbles: true,
cancelable: true,
})
window.dispatchEvent(event)
}

const stylesFactory = (
theme: 'light' | 'dark',
css: (typeof goober)['css'],
Expand Down
Loading