Skip to content

Commit 1a12a6a

Browse files
authored
feat: react-query-next-experimental package (#5598)
* chore: fix a copy-paste error * chore: bootstrap package * chore: setup package * chore: allow passing with no tests * fix: remove 'use client' from index bundle * chore: cleanup copy/paste error * chore: fix prettier * refactor: replace ref with direct props access * fix: do not write to refs during render * refactor: inline function into useEffect to avoid useCallback * fix: eslint no-shadow * refactor: namespace id * refactor: removed pointless check * fix: set to empty array on cleanup * fix: adapt for v5 changes * chore: fix build * docs: add streaming example * chore: fix outdated lockfile * chore: fix prettier * refactor: remove isSubscribed ref * refactor: re-arrange comment * chore: remove comments * chore: fix broken lock file * feat: allow customization of hydrate / dehydrate options and make sure we fall back to the defaultShouldDehydrate function, which only dehydrates successful queries
1 parent e310db4 commit 1a12a6a

22 files changed

+1001
-81
lines changed

babel.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ module.exports = {
3838
'./packages/react-query/**',
3939
'./packages/react-query-devtools/**',
4040
'./packages/react-query-persist-client/**',
41+
'./packages/react-query-next-experimental/**',
4142
],
4243
presets: ['@babel/react'],
4344
},

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@
278278
"label": "Next.js",
279279
"to": "react/examples/react/nextjs"
280280
},
281+
{
282+
"label": "Next.js app with streaming",
283+
"to": "react/examples/react/nextjs-suspense-streaming"
284+
},
281285
{
282286
"label": "React Native",
283287
"to": "react/examples/react/react-native"
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
eslint: {
4+
ignoreDuringBuilds: true,
5+
},
6+
experimental: {
7+
appDir: true,
8+
serverActions: true,
9+
},
10+
webpack: (config) => {
11+
if (config.name === 'server') config.optimization.concatenateModules = false
12+
13+
return config
14+
},
15+
}
16+
17+
module.exports = nextConfig
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@tanstack/query-example-nextjs-suspense-streaming",
3+
"private": true,
4+
"license": "MIT",
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start"
9+
},
10+
"dependencies": {
11+
"@tanstack/react-query": "^v5.0.0-alpha.68",
12+
"@tanstack/react-query-devtools": "^v5.0.0-alpha.68",
13+
"next": "^13.4.4",
14+
"react": "^18.2.0",
15+
"react-dom": "^18.2.0",
16+
"superjson": "^1.12.3"
17+
},
18+
"devDependencies": {
19+
"@types/node": "20.2.5",
20+
"@types/react": "18.2.8",
21+
"typescript": "5.1.3"
22+
}
23+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { NextResponse } from 'next/server'
2+
3+
export async function GET(request: Request) {
4+
const { searchParams } = new URL(request.url)
5+
const wait = Number(searchParams.get('wait'))
6+
7+
await new Promise((resolve) => setTimeout(resolve, wait))
8+
9+
return NextResponse.json(`waited ${wait}ms`)
10+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Providers } from './providers'
2+
3+
export const metadata = {
4+
title: 'Next.js',
5+
description: 'Generated by Next.js',
6+
}
7+
8+
export default function RootLayout({
9+
children,
10+
}: {
11+
children: React.ReactNode
12+
}) {
13+
return (
14+
<html lang="en">
15+
<body>
16+
<Providers>{children}</Providers>
17+
</body>
18+
</html>
19+
)
20+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
'use client'
2+
import { useQuery } from '@tanstack/react-query'
3+
import { Suspense } from 'react'
4+
5+
// export const runtime = "edge"; // 'nodejs' (default) | 'edge'
6+
7+
function getBaseURL() {
8+
if (typeof window !== 'undefined') {
9+
return ''
10+
}
11+
if (process.env.VERCEL_URL) {
12+
return `https://${process.env.VERCEL_URL}`
13+
}
14+
return 'http://localhost:3000'
15+
}
16+
const baseUrl = getBaseURL()
17+
function useWaitQuery(props: { wait: number }) {
18+
const query = useQuery({
19+
queryKey: ['wait', props.wait],
20+
queryFn: async () => {
21+
const path = `/api/wait?wait=${props.wait}`
22+
const url = baseUrl + path
23+
24+
console.log('fetching', url)
25+
const res: string = await (
26+
await fetch(url, {
27+
cache: 'no-store',
28+
})
29+
).json()
30+
return res
31+
},
32+
suspense: true,
33+
})
34+
35+
return [query.data as string, query] as const
36+
}
37+
38+
function MyComponent(props: { wait: number }) {
39+
const [data] = useWaitQuery(props)
40+
41+
return <div>result: {data}</div>
42+
}
43+
44+
export default function MyPage() {
45+
return (
46+
<>
47+
<Suspense fallback={<div>waiting 100....</div>}>
48+
<MyComponent wait={100} />
49+
</Suspense>
50+
<Suspense fallback={<div>waiting 200....</div>}>
51+
<MyComponent wait={200} />
52+
</Suspense>
53+
<Suspense fallback={<div>waiting 300....</div>}>
54+
<MyComponent wait={300} />
55+
</Suspense>
56+
<Suspense fallback={<div>waiting 400....</div>}>
57+
<MyComponent wait={400} />
58+
</Suspense>
59+
<Suspense fallback={<div>waiting 500....</div>}>
60+
<MyComponent wait={500} />
61+
</Suspense>
62+
<Suspense fallback={<div>waiting 600....</div>}>
63+
<MyComponent wait={600} />
64+
</Suspense>
65+
<Suspense fallback={<div>waiting 700....</div>}>
66+
<MyComponent wait={700} />
67+
</Suspense>
68+
69+
<fieldset>
70+
<legend>
71+
combined <code>Suspense</code>-container
72+
</legend>
73+
<Suspense
74+
fallback={
75+
<>
76+
<div>waiting 800....</div>
77+
<div>waiting 900....</div>
78+
<div>waiting 1000....</div>
79+
</>
80+
}
81+
>
82+
<MyComponent wait={800} />
83+
<MyComponent wait={900} />
84+
<MyComponent wait={1000} />
85+
</Suspense>
86+
</fieldset>
87+
</>
88+
)
89+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// app/providers.jsx
2+
'use client'
3+
4+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
5+
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
6+
import React from 'react'
7+
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'
8+
9+
export function Providers(props: { children: React.ReactNode }) {
10+
const [queryClient] = React.useState(
11+
() =>
12+
new QueryClient({
13+
defaultOptions: {
14+
queries: {
15+
staleTime: 5 * 1000,
16+
},
17+
},
18+
}),
19+
)
20+
21+
return (
22+
<QueryClientProvider client={queryClient}>
23+
<ReactQueryStreamedHydration>
24+
{props.children}
25+
</ReactQueryStreamedHydration>
26+
<ReactQueryDevtools initialIsOpen={false} />
27+
</QueryClientProvider>
28+
)
29+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es5",
4+
"lib": ["dom", "dom.iterable", "esnext"],
5+
"allowJs": true,
6+
"skipLibCheck": true,
7+
"strict": true,
8+
"forceConsistentCasingInFileNames": true,
9+
"noEmit": true,
10+
"esModuleInterop": true,
11+
"module": "esnext",
12+
"moduleResolution": "node",
13+
"resolveJsonModule": true,
14+
"isolatedModules": true,
15+
"jsx": "preserve",
16+
"incremental": true,
17+
"plugins": [
18+
{
19+
"name": "next"
20+
}
21+
]
22+
},
23+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
24+
"exclude": ["node_modules"]
25+
}

packages/react-query-devtools/vitest.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineConfig } from 'vitest/config'
22

33
export default defineConfig({
44
test: {
5-
name: 'react-query-persist-client',
5+
name: 'react-query-devtools',
66
dir: './src',
77
watch: false,
88
setupFiles: ['test-setup.ts'],
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @ts-check
2+
3+
/** @type {import('eslint').Linter.Config} */
4+
const config = {
5+
extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'],
6+
parserOptions: {
7+
tsconfigRootDir: __dirname,
8+
project: './tsconfig.eslint.json',
9+
},
10+
rules: {
11+
'react/jsx-key': ['error', { checkFragmentShorthand: true }],
12+
'react-hooks/exhaustive-deps': 'error',
13+
},
14+
}
15+
16+
module.exports = config
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{
2+
"name": "@tanstack/react-query-next-experimental",
3+
"version": "5.0.0-alpha.67",
4+
"description": "Hydration utils for React Query in the NextJs app directory",
5+
"author": "tannerlinsley",
6+
"license": "MIT",
7+
"repository": "tanstack/query",
8+
"homepage": "https://tanstack.com/query",
9+
"funding": {
10+
"type": "github",
11+
"url": "https://github.com/sponsors/tannerlinsley"
12+
},
13+
"type": "module",
14+
"types": "build/lib/index.d.ts",
15+
"main": "build/lib/index.legacy.cjs",
16+
"module": "build/lib/index.legacy.js",
17+
"exports": {
18+
".": {
19+
"types": "./build/lib/index.d.ts",
20+
"import": "./build/lib/index.js",
21+
"require": "./build/lib/index.cjs",
22+
"default": "./build/lib/index.cjs"
23+
},
24+
"./package.json": "./package.json"
25+
},
26+
"sideEffects": false,
27+
"files": [
28+
"build/lib/*",
29+
"src"
30+
],
31+
"scripts": {
32+
"clean": "rimraf ./build && rimraf ./coverage",
33+
"test:eslint": "eslint --ext .ts,.tsx ./src",
34+
"test:types": "tsc --noEmit",
35+
"test:lib": "vitest run --coverage --passWithNoTests",
36+
"test:lib:dev": "pnpm run test:lib --watch",
37+
"test:build": "publint --strict",
38+
"build": "pnpm build:rollup && pnpm build:types",
39+
"build:rollup": "rollup --config rollup.config.js",
40+
"build:types": "tsc --emitDeclarationOnly"
41+
},
42+
"devDependencies": {
43+
"@tanstack/react-query": "workspace:*",
44+
"@types/react": "^18.2.4",
45+
"@types/react-dom": "^18.2.4",
46+
"next": "^13.4.6",
47+
"react": "^18.2.0",
48+
"react-dom": "^18.2.0",
49+
"react-error-boundary": "^3.1.4"
50+
},
51+
"peerDependencies": {
52+
"@tanstack/react-query": "workspace:*",
53+
"next": "^13.0.0",
54+
"react": "^18.0.0",
55+
"react-dom": "^18.0.0"
56+
}
57+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @ts-check
2+
3+
import { defineConfig } from 'rollup'
4+
import { buildConfigs } from '../../scripts/getRollupConfig.js'
5+
6+
export default defineConfig(
7+
buildConfigs({
8+
name: 'react-query-next-experimental',
9+
outputFile: 'index',
10+
entryFile: './src/index.ts',
11+
}),
12+
)

0 commit comments

Comments
 (0)