Skip to content
Closed
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
153 changes: 142 additions & 11 deletions examples/react/basic/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,155 @@
import { createRoot } from 'react-dom/client'
import {
QueryClient,
QueryClientProvider,
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import { useState } from 'react'
import Devtools from './setup'
import { queryPlugin } from './plugin'

setTimeout(() => {
queryPlugin.emit('test', {
title: 'Test Event',
description:
'This is a test event from the TanStack Query Devtools plugin.',
const queryClient = new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
})

type Post = {
id: number
title: string
body: string
}

function Posts({
setPostId,
}: {
setPostId: React.Dispatch<React.SetStateAction<number>>
}) {
const queryClient = useQueryClient()
const { status, data, error, isFetching } = usePosts()

return (
<div>
<h1>Posts</h1>
<div>
{status === 'pending' ? (
'Loading...'
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
<>
<div>
{data.map((post) => (
<p key={post.id}>
<a
onClick={() => setPostId(post.id)}
href="#"
style={
// We can access the query data here to show bold links for
// ones that are cached
queryClient.getQueryData(['post', post.id])
? {
fontWeight: 'bold',
color: 'green',
}
: {}
}
>
{post.title}
</a>
</p>
))}
</div>
<div>{isFetching ? 'Background Updating...' : ' '}</div>
</>
)}
</div>
</div>
)
}

const getPostById = async (id: number): Promise<Post> => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/posts/${id}`,
)
return await response.json()
}

function usePost(postId: number) {
return useQuery({
queryKey: ['post', postId],
queryFn: () => getPostById(postId),
enabled: !!postId,
})
}, 1000)
}

queryPlugin.on('test', (event) => {
console.log('Received test event:', event)
})
function Post({
postId,
setPostId,
}: {
postId: number
setPostId: React.Dispatch<React.SetStateAction<number>>
}) {
const { status, data, error, isFetching } = usePost(postId)

return (
<div>
<div>
<a onClick={() => setPostId(-1)} href="#">
Back
</a>
</div>
{!postId || status === 'pending' ? (
'Loading...'
) : status === 'error' ? (
<span>Error: {error.message}</span>
) : (
<>
<h1>{data.title}</h1>
<div>
<p>{data.body}</p>
</div>
<div>{isFetching ? 'Background Updating...' : ' '}</div>
</>
)}
</div>
)
}
function usePosts() {
return useQuery({
queryKey: ['posts'],
queryFn: async (): Promise<Array<Post>> => {
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
return await response.json()
},
})
}
function App() {
const [postId, setPostId] = useState(-1)

return (
<div>
<QueryClientProvider client={queryClient}>
<p>
As you visit the posts below, you will notice them in a loading state
the first time you load them. However, after you return to this list
and click on any posts you have already visited again, you will see
them load instantly and background refresh right before your eyes!{' '}
<strong>
(You may need to throttle your network speed to simulate longer
loading sequences)
</strong>
</p>
{postId > -1 ? (
<Post postId={postId} setPostId={setPostId} />
) : (
<Posts setPostId={setPostId} />
)}
<Devtools />
</QueryClientProvider>
<h1>TanStack Devtools React Basic Example</h1>
<Devtools />
</div>
)
}
Expand Down
31 changes: 13 additions & 18 deletions examples/react/basic/src/setup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
import {
Expand Down Expand Up @@ -57,26 +56,22 @@ const routeTree = rootRoute.addChildren([indexRoute, aboutRoute])

const router = createRouter({ routeTree })

const queryClient = new QueryClient()

export default function DevtoolsExample() {
return (
<>
<QueryClientProvider client={queryClient}>
<TanStackDevtools
plugins={[
{
name: 'TanStack Query',
render: <ReactQueryDevtoolsPanel />,
},
{
name: 'TanStack Router',
render: <TanStackRouterDevtoolsPanel router={router} />,
},
]}
/>
<RouterProvider router={router} />
</QueryClientProvider>
<TanStackDevtools
plugins={[
{
name: 'Tanstack Query',
render: <ReactQueryDevtoolsPanel />,
},
{
name: 'Tanstack Router',
render: <TanStackRouterDevtoolsPanel router={router} />,
},
]}
/>
<RouterProvider router={router} />
</>
)
}
11 changes: 8 additions & 3 deletions packages/devtools/src/components/content-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { useDevtoolsSettings } from '../context/use-devtools-context'
import { Show } from 'solid-js'
import {
useDetachedWindowControls,
useDevtoolsSettings,
} from '../context/use-devtools-context'
import { useStyles } from '../styles/use-styles'
import type { JSX } from 'solid-js/jsx-runtime'

Expand All @@ -9,14 +13,15 @@ export const ContentPanel = (props: {
}) => {
const styles = useStyles()
const { settings } = useDevtoolsSettings()
const { isDetached } = useDetachedWindowControls()
return (
<div ref={props.ref} class={styles().devtoolsPanel}>
{props.handleDragStart ? (
<Show when={props.handleDragStart && !isDetached}>
<div
class={styles().dragHandle(settings().panelLocation)}
onMouseDown={props.handleDragStart}
></div>
) : null}
</Show>
{props.children}
</div>
)
Expand Down
11 changes: 8 additions & 3 deletions packages/devtools/src/components/main-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import clsx from 'clsx'
import { useDevtoolsSettings, useHeight } from '../context/use-devtools-context'
import {
useDetachedWindowControls,
useDevtoolsSettings,
useHeight,
} from '../context/use-devtools-context'
import { useStyles } from '../styles/use-styles'
import { TANSTACK_DEVTOOLS } from '../utils/storage'
import type { Accessor, JSX } from 'solid-js'
Expand All @@ -12,14 +16,15 @@ export const MainPanel = (props: {
const styles = useStyles()
const { height } = useHeight()
const { settings } = useDevtoolsSettings()
const { isDetached } = useDetachedWindowControls()
return (
<div
id={TANSTACK_DEVTOOLS}
style={{
height: height() + 'px',
height: isDetached ? window.innerHeight + 'px' : height() + 'px',
}}
class={clsx(
styles().devtoolsPanelContainer(settings().panelLocation),
styles().devtoolsPanelContainer(settings().panelLocation, isDetached),
styles().devtoolsPanelContainerAnimation(props.isOpen(), height()),
styles().devtoolsPanelContainerVisibility(props.isOpen()),
styles().devtoolsPanelContainerResizing(props.isResizing),
Expand Down
98 changes: 78 additions & 20 deletions packages/devtools/src/components/tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import clsx from 'clsx'
import { For } from 'solid-js'
import { useStyles } from '../styles/use-styles'
import { useDevtoolsState } from '../context/use-devtools-context'
import {
useDetachedWindowControls,
useDevtoolsState,
} from '../context/use-devtools-context'
import { tabs } from '../tabs'
import {
TANSTACK_DEVTOOLS_DETACHED_OWNER,
TANSTACK_DEVTOOLS_IS_DETACHED,
setSessionItem,
setStorageItem,
} from '../utils/storage'

interface TabsProps {
toggleOpen: () => void
Expand All @@ -11,7 +20,22 @@ interface TabsProps {
export const Tabs = (props: TabsProps) => {
const styles = useStyles()
const { state, setState } = useDevtoolsState()
const { setDetachedWindowOwner, detachedWindowOwner, detachedWindow } =
useDetachedWindowControls()
const handleDetachment = () => {
const detachedWindow = window.open(
window.location.href,
'',
`popup,width=${window.innerWidth},height=${state().height},top=${window.screen.height},left=${window.screenLeft}}`,
)

if (detachedWindow) {
setDetachedWindowOwner(true)
setStorageItem(TANSTACK_DEVTOOLS_IS_DETACHED, 'true')
setSessionItem(TANSTACK_DEVTOOLS_DETACHED_OWNER, 'true')
detachedWindow.TDT_MOUNTED = true
}
}
return (
<div class={styles().tabContainer}>
<For each={tabs}>
Expand All @@ -25,26 +49,60 @@ export const Tabs = (props: TabsProps) => {
</button>
)}
</For>
<button
type="button"
class={clsx(styles().tab, 'close')}
onClick={() => props.toggleOpen()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
{!detachedWindow && (
<div
style={{
'margin-top': 'auto',
}}
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
{!detachedWindowOwner() && (
<button
type="button"
class={clsx(styles().tab, 'detach')}
onClick={handleDetachment}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-picture-in-picture-icon lucide-picture-in-picture"
>
<path d="M2 10h6V4" />
<path d="m2 4 6 6" />
<path d="M21 10V7a2 2 0 0 0-2-2h-7" />
<path d="M3 14v2a2 2 0 0 0 2 2h3" />
<rect x="12" y="14" width="10" height="7" rx="1" />
</svg>
</button>
)}
<button
type="button"
class={clsx(styles().tab, 'close')}
onClick={() => props.toggleOpen()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
</button>
</div>
)}
</div>
)
}
Loading