snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
25
examples/react/nextjs/.gitignore
vendored
Normal file
25
examples/react/nextjs/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
10
examples/react/nextjs/README.md
Normal file
10
examples/react/nextjs/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Example
|
||||
|
||||
In this simple example, we integrate React-Query seamlessly with Next.js data fetching methods to fetch queries in the server and hydrate them in the browser.
|
||||
|
||||
In addition to fetching and mutating data, React-Query analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run.
|
||||
|
||||
To run this example:
|
||||
|
||||
- `npm install`
|
||||
- `npm run dev`
|
||||
13
examples/react/nextjs/next.config.js
Normal file
13
examples/react/nextjs/next.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
22
examples/react/nextjs/package.json
Normal file
22
examples/react/nextjs/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@tanstack/query-example-react-nextjs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack",
|
||||
"build": "next build",
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.102.8",
|
||||
"@tanstack/react-query-devtools": "^5.102.8",
|
||||
"next": "^16.0.7",
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"typescript": "5.8.3"
|
||||
}
|
||||
}
|
||||
34
examples/react/nextjs/src/components/Header.tsx
Normal file
34
examples/react/nextjs/src/components/Header.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import Link from 'next/link'
|
||||
|
||||
export const Header = () => {
|
||||
const { pathname } = useRouter()
|
||||
|
||||
return (
|
||||
<header>
|
||||
<Link href="/" className={pathname === '/' ? 'is-active' : ''}>
|
||||
Home
|
||||
</Link>
|
||||
<Link
|
||||
href="/client-only"
|
||||
className={pathname === '/client-only' ? 'is-active' : ''}
|
||||
>
|
||||
Client-Only
|
||||
</Link>
|
||||
<style jsx>{`
|
||||
header {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
a {
|
||||
font-size: 14px;
|
||||
margin-right: 15px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.is-active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
`}</style>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
19
examples/react/nextjs/src/components/InfoBox.tsx
Normal file
19
examples/react/nextjs/src/components/InfoBox.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
const InfoBox = ({ children }: { children: React.ReactNode }) => (
|
||||
<div className="info">
|
||||
<style jsx>{`
|
||||
.info {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
border-top: 1px solid #ececec;
|
||||
border-bottom: 1px solid #ececec;
|
||||
}
|
||||
`}</style>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export { InfoBox }
|
||||
50
examples/react/nextjs/src/components/Layout.tsx
Normal file
50
examples/react/nextjs/src/components/Layout.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React from 'react'
|
||||
|
||||
export const Layout = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<main>
|
||||
{children}
|
||||
<style jsx global>{`
|
||||
* {
|
||||
font-family:
|
||||
Menlo, Monaco, 'Lucida Console', 'Liberation Mono',
|
||||
'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New',
|
||||
monospace, serif;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 25px 50px;
|
||||
}
|
||||
a {
|
||||
color: #22bad9;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
}
|
||||
article {
|
||||
margin: 0 auto;
|
||||
max-width: 650px;
|
||||
}
|
||||
button {
|
||||
align-items: center;
|
||||
background-color: #22bad9;
|
||||
border: 0;
|
||||
color: white;
|
||||
display: flex;
|
||||
padding: 5px 7px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
button:active {
|
||||
background-color: #1b9db7;
|
||||
}
|
||||
button:disabled {
|
||||
background-color: #b5bebf;
|
||||
}
|
||||
button:focus {
|
||||
outline: none;
|
||||
}
|
||||
`}</style>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
67
examples/react/nextjs/src/components/PostList.tsx
Normal file
67
examples/react/nextjs/src/components/PostList.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import React, { useState } from 'react'
|
||||
import { usePosts } from '../hooks/usePosts'
|
||||
|
||||
export const PostList = () => {
|
||||
const [postCount, setPostCount] = useState(10)
|
||||
const { data, isPending, isFetching } = usePosts(postCount)
|
||||
|
||||
if (isPending) return <div>Loading</div>
|
||||
|
||||
return (
|
||||
<section>
|
||||
<ul>
|
||||
{data?.map((post, index) => (
|
||||
<li key={post.id}>
|
||||
{index + 1}. {post.title}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{postCount <= 90 && (
|
||||
<button
|
||||
onClick={() => setPostCount(postCount + 10)}
|
||||
disabled={isFetching}
|
||||
>
|
||||
{isFetching ? 'Loading...' : 'Show More'}
|
||||
</button>
|
||||
)}
|
||||
<style jsx>{`
|
||||
section {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
li {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
div {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
a {
|
||||
font-size: 14px;
|
||||
margin-right: 10px;
|
||||
text-decoration: none;
|
||||
padding-bottom: 0;
|
||||
border: 0;
|
||||
}
|
||||
span {
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
button:before {
|
||||
align-self: center;
|
||||
border-style: solid;
|
||||
border-width: 6px 4px 0 4px;
|
||||
border-color: #ffffff transparent transparent transparent;
|
||||
content: '';
|
||||
height: 0;
|
||||
margin-right: 5px;
|
||||
width: 0;
|
||||
}
|
||||
`}</style>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
4
examples/react/nextjs/src/components/index.ts
Normal file
4
examples/react/nextjs/src/components/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './Header'
|
||||
export * from './InfoBox'
|
||||
export * from './Layout'
|
||||
export * from './PostList'
|
||||
22
examples/react/nextjs/src/hooks/usePosts.ts
Normal file
22
examples/react/nextjs/src/hooks/usePosts.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
type Post = {
|
||||
id: number
|
||||
title: string
|
||||
body: string
|
||||
}
|
||||
|
||||
const fetchPosts = async (limit = 10): Promise<Array<Post>> => {
|
||||
const response = await fetch('https://jsonplaceholder.typicode.com/posts')
|
||||
const data = await response.json()
|
||||
return data.filter((x: Post) => x.id <= limit)
|
||||
}
|
||||
|
||||
const usePosts = (limit: number) => {
|
||||
return useQuery({
|
||||
queryKey: ['posts', limit],
|
||||
queryFn: () => fetchPosts(limit),
|
||||
})
|
||||
}
|
||||
|
||||
export { usePosts, fetchPosts }
|
||||
21
examples/react/nextjs/src/pages/_app.tsx
Normal file
21
examples/react/nextjs/src/pages/_app.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
HydrationBoundary,
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
} from '@tanstack/react-query'
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
const [queryClient] = React.useState(() => new QueryClient())
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<HydrationBoundary state={pageProps.dehydratedState}>
|
||||
<Component {...pageProps} />
|
||||
</HydrationBoundary>
|
||||
<ReactQueryDevtools />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
14
examples/react/nextjs/src/pages/client-only.tsx
Normal file
14
examples/react/nextjs/src/pages/client-only.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import { Header, InfoBox, Layout, PostList } from '../components'
|
||||
|
||||
const ClientOnly = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Header />
|
||||
<InfoBox>ℹ️ This data is loaded on client and not prefetched</InfoBox>
|
||||
<PostList />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default ClientOnly
|
||||
33
examples/react/nextjs/src/pages/index.tsx
Normal file
33
examples/react/nextjs/src/pages/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
import { QueryClient, dehydrate, noop } from '@tanstack/react-query'
|
||||
import { Header, InfoBox, Layout, PostList } from '../components'
|
||||
import { fetchPosts } from '../hooks/usePosts'
|
||||
|
||||
const Home = () => {
|
||||
return (
|
||||
<Layout>
|
||||
<Header />
|
||||
<InfoBox>ℹ️ This page shows how to use SSG with React-Query.</InfoBox>
|
||||
<PostList />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
await queryClient
|
||||
.query({
|
||||
queryKey: ['posts', 10],
|
||||
queryFn: () => fetchPosts(10),
|
||||
})
|
||||
.catch(noop)
|
||||
|
||||
return {
|
||||
props: {
|
||||
dehydratedState: dehydrate(queryClient),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default Home
|
||||
20
examples/react/nextjs/tsconfig.json
Normal file
20
examples/react/nextjs/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user