Skip to content

Commit

Permalink
Merge branch 'main' into v5
Browse files Browse the repository at this point in the history
# Conflicts:
#	docs/react/guides/caching.md
#	docs/react/guides/ssr.md
#	docs/svelte/ssr.md
#	examples/svelte/auto-refetching/src/routes/+page.svelte
#	examples/svelte/basic/src/lib/Post.svelte
#	examples/svelte/optimistic-updates-typescript/src/routes/+page.svelte
#	examples/svelte/playground/src/routes/Todos.svelte
#	examples/svelte/simple/src/lib/Simple.svelte
#	examples/svelte/ssr/src/lib/Post.svelte
#	package.json
#	packages/query-core/src/mutationObserver.ts
#	packages/react-query-devtools/package.json
#	packages/react-query-persist-client/package.json
#	packages/react-query/package.json
#	packages/react-query/src/__tests__/suspense.test.tsx
#	packages/react-query/src/__tests__/useMutation.test.tsx
#	packages/react-query/src/__tests__/useQueries.test.tsx
#	packages/svelte-query/src/__tests__/utils.ts
#	packages/svelte-query/src/createBaseQuery.ts
#	packages/svelte-query/src/createInfiniteQuery.ts
#	packages/svelte-query/src/createMutation.ts
#	packages/svelte-query/src/createQueries.ts
#	packages/svelte-query/src/createQuery.ts
#	packages/svelte-query/src/index.ts
#	packages/svelte-query/src/types.ts
#	packages/svelte-query/src/useHydrate.ts
#	packages/svelte-query/src/useIsFetching.ts
#	packages/svelte-query/src/useIsMutating.ts
#	packages/svelte-query/src/useQueryClient.ts
#	pnpm-lock.yaml
  • Loading branch information
lachlancollins committed Jan 25, 2023
2 parents 2505f38 + 9404df4 commit e61c784
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 17 deletions.
196 changes: 182 additions & 14 deletions docs/react/guides/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ The exact implementation of these mechanisms may vary from platform to platform,
- Static Generation (SSG)
- Server-side Rendering (SSR)

React Query supports both of these forms of pre-rendering regardless of what platform you may be using
React Query supports both of these forms of pre-rendering regardless of what platform you may be using.

> Note: For notes about how to integrate with the new beta `/app`-folder in Next.js, see further down in this guide.
### Using `initialData`

Expand Down Expand Up @@ -82,7 +84,7 @@ Now you are ready to prefetch some data in your pages with either [`getStaticPro

```tsx
// pages/posts.jsx
import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query';
import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query'

export async function getStaticProps() {
const queryClient = new QueryClient()
Expand All @@ -103,7 +105,10 @@ function Posts() {

// This query was not prefetched on the server and will not start
// fetching until on the client, both patterns are fine to mix
const { data: otherData } = useQuery({ queryKey: ['posts-2'], queryFn: getPosts })
const { data: otherData } = useQuery({
queryKey: ['posts-2'],
queryFn: getPosts,
})

// ...
}
Expand Down Expand Up @@ -196,7 +201,7 @@ Now you are ready to prefetch some data in your [`loader`](https://remix.run/doc

```tsx
// pages/posts.tsx
import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query';
import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query'

export async function loader() {
const queryClient = new QueryClient()
Expand All @@ -213,7 +218,10 @@ function Posts() {

// This query was not prefetched on the server and will not start
// fetching until on the client, both patterns are fine to mix
const { data: otherData } = useQuery({ queryKey: ['posts-2'], queryFn: getPosts })
const { data: otherData } = useQuery({
queryKey: ['posts-2'],
queryFn: getPosts,
})

// ...
}
Expand Down Expand Up @@ -241,7 +249,7 @@ This guide is at-best, a high level overview of how SSR with React Query should
```tsx
import { dehydrate, HydrationBoundary, QueryClient, QueryClientProvider } from '@tanstack/react-query';

async function handleRequest (req, res) {
async function handleRequest(req, res) {
const queryClient = new QueryClient()
await queryClient.prefetchQuery(['key'], fn)
const dehydratedState = dehydrate(queryClient)
Expand Down Expand Up @@ -288,40 +296,200 @@ ReactDOM.hydrate(
<App />
</HydrationBoundary>
</QueryClientProvider>,
document.getElementById('root')
document.getElementById('root'),
)
```

## Using Experimental `app` Directory in Next.js 13

> **WARNING:** The `app` directory introduced in Next.js 13 is currently in beta, and it is not recommended for use in production. The API is not stable.
>
> This guide is provided as is to supply a quick start for early exploration of Next.js 13's experimental features and does not represent the final APIs.
Both prefetching approaches, using `initialData` or `<Hydrate>`, are available within the `app` directory.

- Prefetch the data in a Server Component and prop drill `initialData` to Client Components
- Quick to set up for simple cases
- May need to prop drill through multiple layers of Client Components
- May need to prop drill to multiple Client Components using the same query
- Query refetching is based on when the page loads instead of when the data was prefetched on the server
- Prefetch the query on the server, dehydrate the cache and rehydrate it on the client with `<Hydrate>`
- Requires slightly more setup up front
- No need to prop drill
- Query refetching is based on when the query was prefetched on the server

### `<QueryClientProvider>` is required by both the `initialData` and `<Hydrate>` prefetching approaches

The hooks provided by the `react-query` package need to retrieve a `QueryClient` from their context. Wrap your component tree with `<QueryClientProvider>` and pass it an instance of `QueryClient`.

```tsx
// app/providers.jsx
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

export default function Providers({ children }) {
const [queryClient] = React.useState(() => new QueryClient())

return (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
)
}
```

```tsx
// app/layout.jsx
import Providers from './providers'

export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}
```

### Using `initialData`

Fetch your initial data in a Server Component higher up in the component tree, and pass it to your Client Component as a prop.

```tsx
// app/page.jsx
export default async function Home() {
const initialData = await getPosts()

return <Posts posts={initialData} />
}
```

```tsx
// app/posts.jsx
'use client'

import { useQuery } from '@tanstack/react-query'

export function Posts(props) {
const { data } = useQuery({
queryKey: ['posts'],
queryFn: getPosts,
initialData: props.posts,
})

// ...
}
```

### Using `<Hydrate>`

Create a request-scoped singleton instance of `QueryClient`. **This ensures that data is not shared between different users and requests, while still only creating the QueryClient once per request.**

```tsx
// app/getQueryClient.jsx
import { QueryClient } from '@tanstack/react-query'
import { cache } from 'react'

const getQueryClient = cache(() => new QueryClient())
export default getQueryClient
```

Fetch your data in a Server Component higher up in the component tree than the Client Components that use the prefetched queries. Your prefetched queries will be available to all components deeper down the component tree.

- Retrieve the `QueryClient` singleton instance
- Prefetch the data using the client's prefetchQuery method and wait for it to complete
- Use `dehydrate` to obtain the dehydrated state of the prefetched queries from the query cache
- Wrap the component tree that needs the prefetched queries inside `<Hydrate>`, and provide it with the dehydrated state
- You can fetch inside multiple Server Components and use `<Hydrate>` in multiple places

> NOTE: TypeScript currently complains of a type error when using async Server Components. As a temporary workaround, use `{/* @ts-expect-error Server Component */}` when calling this component inside another. For more information, see [End-to-End Type Safety](https://beta.nextjs.org/docs/configuring/typescript#end-to-end-type-safety) in the Next.js 13 beta docs.
```tsx
// app/hydratedPosts.jsx
import { dehydrate, Hydrate } from '@tanstack/react-query'
import getQueryClient from './getQueryClient'

export default async function HydratedPosts() {
const queryClient = getQueryClient()
await queryClient.prefetchQuery(['posts'], getPosts)
const dehydratedState = dehydrate(queryClient)

return (
<Hydrate state={dehydratedState}>
<Posts />
</Hydrate>
)
}
```

During server rendering, calls to `useQuery` nested within the `<Hydrate>` Client Component will have access to prefetched data provided in the state property.

```tsx
// app/posts.jsx
'use client'

import { useQuery } from '@tanstack/react-query'

export default function Posts() {
// This useQuery could just as well happen in some deeper child to
// the "HydratedPosts"-component, data will be available immediately either way
const { data } = useQuery({ queryKey: ['posts'], queryFn: getPosts })

// This query was not prefetched on the server and will not start
// fetching until on the client, both patterns are fine to mix
const { data: otherData } = useQuery({
queryKey: ['posts-2'],
queryFn: getPosts,
})

// ...
}
```

As demonstrated, it's fine to prefetch some queries and let others fetch on the client. This means you can control what content server renders or not by adding or removing `prefetchQuery` for a specific query.

### Streaming, Suspense and server-side fetching

Right now, you always have to `await` the data in the Server Component. In the future, the goal is to be able to _start_ prefetching in a Server Component but not block rendering, instead streaming markup and data to the client incrementally as it gets available. This is currently lacking support in both React and Query.

Similarily, you _must_ currently prefetch the data in a Server Component if you want it to be server rendered. A `useQuery()` call even with the `suspense` option enabled will not fetch data on the server, only on the client. We hope to support this in the future, but exact details are still unknown.

## Custom SSR with suspense

If you do not want to provide `prefetchQuery()` for all your queries in the SSR you can use suspense.

### Server

```tsx
import { dehydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
dehydrate,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import ssrPrepass from 'react-ssr-prepass'

async function handleRequest (req, res) {
async function handleRequest(req, res) {
const queryClient = new QueryClient()

// React SSR does not support ErrorBoundary
try {
// Traverse the tree and fetch all Suspense data (thrown promises)
await ssrPrepass(<App />);
await ssrPrepass(<App />)
} catch (e) {
console.error(e);
console.error(e)
// Send the index.html (without SSR) on error, so user can try to recover and see something
return res.sendFile('path/to/dist/index.html');
return res.sendFile('path/to/dist/index.html')
}

const html = ReactDOM.renderToString(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</QueryClientProvider>,
)

const dehydratedState = dehydrate(queryClient);
const dehydratedState = dehydrate(queryClient)

res.send(`
<html>
Expand Down
6 changes: 3 additions & 3 deletions docs/svelte/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const load: PageLoad = async () => {
const query = createQuery({
queryKey: ['posts'],
queryFn: getPosts,
initialData: posts
initialData: data.posts
})
</script>
```
Expand Down Expand Up @@ -107,9 +107,9 @@ export const load: LayoutLoad = async () => {
```markdown
<script lang="ts">
import { QueryClientProvider } from '@tanstack/svelte-query'
import type { PageData } from './$types'
import type { LayoutData } from './$types'

export let data: PageData
export let data: LayoutData
</script>

<QueryClientProvider client={data.queryClient}>
Expand Down
16 changes: 16 additions & 0 deletions packages/query-core/src/tests/mutations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,20 @@ describe('mutations', () => {
expect(onSuccess).not.toHaveBeenCalled()
expect(onSettled).not.toHaveBeenCalled()
})

test('mutate update the mutation state even without an active subscription', async () => {
const onSuccess = jest.fn()
const onSettled = jest.fn()

const mutation = new MutationObserver(queryClient, {
mutationFn: async () => {
return 'update'
},
})

await mutation.mutate(undefined, { onSuccess, onSettled })
expect(mutation.getCurrentResult().data).toEqual('update')
expect(onSuccess).not.toHaveBeenCalled()
expect(onSettled).not.toHaveBeenCalled()
})
})
1 change: 1 addition & 0 deletions packages/react-query-devtools/src/Explorer.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'

import { displayValue, styled } from './utils'
Expand Down
1 change: 1 addition & 0 deletions packages/react-query-devtools/src/devtools.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'
import type {
QueryCache,
Expand Down
1 change: 1 addition & 0 deletions packages/react-query-devtools/src/theme.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'

export const defaultTheme = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'

import type { PersistQueryClientOptions } from '@tanstack/query-persist-client-core'
Expand Down
1 change: 1 addition & 0 deletions packages/react-query/src/HydrationBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'

import type { HydrateOptions, QueryClient } from '@tanstack/query-core'
Expand Down
1 change: 1 addition & 0 deletions packages/react-query/src/QueryClientProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'

import type { QueryClient } from '@tanstack/query-core'
Expand Down
1 change: 1 addition & 0 deletions packages/react-query/src/QueryErrorResetBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'

// CONTEXT
Expand Down
1 change: 1 addition & 0 deletions packages/react-query/src/isRestoring.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
'use client'
import * as React from 'react'

const IsRestoringContext = React.createContext(false)
Expand Down

0 comments on commit e61c784

Please sign in to comment.