Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into feat-infinite-query-options
Browse files Browse the repository at this point in the history
  • Loading branch information
XantreDev committed Jun 8, 2024
2 parents 13dfe98 + bf500ae commit ab3e21b
Show file tree
Hide file tree
Showing 212 changed files with 29,123 additions and 23,273 deletions.
8 changes: 3 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ jobs:
with:
fetch-depth: '0'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
Expand Down Expand Up @@ -58,5 +56,5 @@ jobs:
TAG: ${{ inputs.tag }}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
directory: packages
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
8 changes: 3 additions & 5 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ jobs:
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
uses: pnpm/action-setup@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
Expand All @@ -45,5 +43,5 @@ jobs:
run: npx nx-cloud stop-all-agents
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
directory: packages
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
link-workspace-packages=true
prefer-workspace-packages=true
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.19.0
v22.2.0
8 changes: 7 additions & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ coverage:
status:
project:
default:
target: 90%
target: auto
threshold: 1%
base: auto

comment:
layout: 'header, reach, diff, flags, components'
behavior: default
require_changes: false
require_base: false
require_head: true
hide_project_coverage: false

component_management:
individual_components:
Expand Down
49 changes: 41 additions & 8 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@
"label": "Query Functions",
"to": "framework/vue/guides/query-functions"
},
{
"label": "Query Options",
"to": "framework/vue/guides/query-options"
},
{
"label": "Network Mode",
"to": "framework/vue/guides/network-mode"
Expand Down Expand Up @@ -369,10 +373,6 @@
"label": "Placeholder Query Data",
"to": "framework/vue/guides/placeholder-query-data"
},
{
"label": "Prefetching",
"to": "framework/vue/guides/prefetching"
},
{
"label": "Mutations",
"to": "framework/vue/guides/mutations"
Expand All @@ -386,7 +386,7 @@
"to": "framework/vue/guides/invalidations-from-mutations"
},
{
"label": "Updates from Mutation",
"label": "Updates from Mutation Responses",
"to": "framework/vue/guides/updates-from-mutation-responses"
},
{
Expand All @@ -405,6 +405,10 @@
"label": "Filters",
"to": "framework/vue/guides/filters"
},
{
"label": "Prefetching",
"to": "framework/vue/guides/prefetching"
},
{
"label": "SSR & Nuxt",
"to": "framework/vue/guides/ssr"
Expand Down Expand Up @@ -691,10 +695,22 @@
"label": "useIsMutating",
"to": "framework/vue/reference/useIsMutating"
},
{
"label": "useMutationState",
"to": "framework/vue/reference/useMutationState"
},
{
"label": "useQueryClient",
"to": "framework/vue/reference/useQueryClient"
},
{
"label": "queryOptions",
"to": "framework/vue/reference/queryOptions"
},
{
"label": "infiniteQueryOptions",
"to": "framework/vue/reference/infiniteQueryOptions"
},
{
"label": "hydration",
"to": "framework/vue/reference/hydration"
Expand Down Expand Up @@ -758,8 +774,8 @@
"to": "framework/vue/community/tkdodos-blog"
},
{
"label": "Query Key Factory",
"to": "framework/vue/community/lukemorales-query-key-factory"
"label": "Community Projects",
"to": "framework/vue/community/community-projects"
}
]
}
Expand Down Expand Up @@ -833,9 +849,13 @@
"to": "framework/react/examples/rick-morty"
},
{
"label": "Next.js",
"label": "Next.js Pages",
"to": "framework/react/examples/nextjs"
},
{
"label": "Next.js app with prefetching",
"to": "framework/react/examples/nextjs-app-prefetching"
},
{
"label": "Next.js app with streaming",
"to": "framework/react/examples/nextjs-suspense-streaming"
Expand Down Expand Up @@ -1000,6 +1020,19 @@
"to": "framework/react/plugins/createPersister"
}
]
},
{
"label": "vue",
"children": [
{
"label": "broadcastQueryClient (Experimental)",
"to": "framework/vue/plugins/broadcastQueryClient"
},
{
"label": "createPersister (Experimental)",
"to": "framework/vue/plugins/createPersister"
}
]
}
]
}
Expand Down
12 changes: 12 additions & 0 deletions docs/framework/react/community/community-projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,24 @@ A library for creating typesafe standardized query keys, useful for cache manage

Link: https://github.com/lukemorales/query-key-factory

## Rapini

🥬 OpenAPI to React Query (or SWR) & Axios

Link: https://github.com/rametta/rapini

## React Query Kit

🕊️ A toolkit for ReactQuery that makes ReactQuery hooks reusable and typesafe

Link: https://github.com/liaoliao666/react-query-kit

## React Query Rewind

Time travel and visualize state during development

Link: https://reactqueryrewind.com/

## React Query Swagger

Generate React Query hooks based on Swagger API definitions
Expand Down
2 changes: 1 addition & 1 deletion docs/framework/react/devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function App() {
- Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
- `shadowDOMTarget?: ShadowRoot`
- Default behavior will apply the devtool's styles to the head tag within the DOM.
- Use this to pass a shadow DOM target to the devtools so that the styles will be applied within the shadow DOM instad of within the head tag in the light DOM.
- Use this to pass a shadow DOM target to the devtools so that the styles will be applied within the shadow DOM instead of within the head tag in the light DOM.

## Devtools in production

Expand Down
101 changes: 89 additions & 12 deletions docs/framework/react/guides/advanced-ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ The first step of any React Query setup is always to create a `queryClient` and
// In Next.js, this file would be called: app/providers.jsx
'use client'

// We can not useState or useRef in a server component, which is why we are
// extracting this part out into it's own file with 'use client' on top
import { useState } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'

function makeQueryClient() {
return new QueryClient({
Expand All @@ -49,12 +51,12 @@ function makeQueryClient() {
let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
if (typeof window === 'undefined') {
if (isServer) {
// Server: always make a new query client
return makeQueryClient()
} else {
// Browser: make a new query client if we don't already have one
// This is very important so we don't re-make a new client if React
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient()
Expand Down Expand Up @@ -356,9 +358,80 @@ The Next.js app router automatically streams any part of the application that is

With the prefetching patterns described above, React Query is perfectly compatible with this form of streaming. As the data for each Suspense boundary resolves, Next.js can render and stream the finished content to the browser. This works even if you are using `useQuery` as outlined above because the suspending actually happens when you `await` the prefetch.

Note that right now, you have to await all prefetches for this to work. This means all prefetches are considered critical content and will block that Suspense boundary.
As of React Query v5.40.0, you don't have to `await` all prefetches for this to work, as `pending` Queries can also be dehydrated and sent to the client. This lets you kick off prefetches as early as possible without letting them block an entire Suspense boundary, and streams the _data_ to the client as the query finishes. This can be useful for example if you want to prefetch some content that is only visible after some user interaction, or say if you want to `await` and render the first page of an infinite query, but start prefetching page 2 without blocking rendering.

To make this work, we have to instruct the `queryClient` to also `dehydrate` pending Queries. We can do this globally, or by passing that option directly to `hydrate`:

```tsx
// app/get-query-client.ts
import { QueryClient, defaultShouldDehydrateQuery } from '@tanstack/react-query'

function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
},
dehydrate: {
// per default, only successful Queries are included,
// this includes pending Queries as well
shouldDehydrateQuery: (query) =>
defaultShouldDehydrateQuery(query) ||
query.state.status === 'pending',
},
},
})
}
```

> Note: This works in NextJs and Server Components because React can serialize Promises over the wire when you pass them down to Client Components.
Then, all we need to do is provide a `HydrationBoundary`, but we don't need to `await` prefetches anymore:

```tsx
// app/posts/page.jsx
import {
dehydrate,
HydrationBoundary,
QueryClient,
} from '@tanstack/react-query'
import { getQueryClient } from './get-query-client'
import Posts from './posts'

// the function doesn't need to be `async` because we don't `await` anything
export default function PostsPage() {
const queryClient = getQueryClient()

As an aside, in the future it might be possible to skip the await for "optional" prefetches that are not critical for this Suspense boundary. This would let you kick off prefetches as early as possible without letting them block an entire Suspense boundary, and streaming the _data_ to the client as the query finishes. This could be useful for example if you want to prefetch some content that is only visible after some user interaction, or say if you want to await and render the first page of an infinite query, but start prefetching page 2 without blocking rendering.
// look ma, no await
queryClient.prefetchQuery({
queryKey: ['posts'],
queryFn: getPosts,
})

return (
<HydrationBoundary state={dehydrate(queryClient)}>
<Posts />
</HydrationBoundary>
)
}
```

On the client, the Promise will be put into the QueryCache for us. That means we can now call `useSuspenseQuery` inside the `Posts` component to "use" that Promise (which was created on the Server):

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

export default function Posts() {
const { data } = useSuspenseQuery({ queryKey: ['posts'], queryFn: getPosts })

// ...
}
```

> Note that you could also `useQuery` instead of `useSuspenseQuery`, and the Promise would still be picked up correctly. However, NextJs won't suspend in that case and the component will render in the `pending` status, which also opts out of server rendering the content.
For more information, check out the [Next.js App with Prefetching Example](../../examples/nextjs-app-prefetching).

## Experimental streaming without prefetching in Next.js

Expand All @@ -372,7 +445,11 @@ To achieve this, wrap your app in the `ReactQueryStreamedHydration` component:
// app/providers.tsx
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import * as React from 'react'
import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'

Expand All @@ -391,13 +468,13 @@ function makeQueryClient() {
let browserQueryClient: QueryClient | undefined = undefined

function getQueryClient() {
if (typeof window === 'undefined') {
if (isServer) {
// Server: always make a new query client
return makeQueryClient()
} else {
// Browser: make a new query client if we don't already have one
// This is very important so we don't re-make a new client if React
// supsends during the initial render. This may not be needed if we
// This is very important, so we don't re-make a new client if React
// suspends during the initial render. This may not be needed if we
// have a suspense boundary BELOW the creation of the query client
if (!browserQueryClient) browserQueryClient = makeQueryClient()
return browserQueryClient
Expand Down
3 changes: 1 addition & 2 deletions docs/framework/react/guides/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,8 +388,6 @@ export default function App() {

We also have an extensive [offline example](../../examples/offline) that covers both queries and mutations.

[//]: # 'Materials'

## Mutation Scopes

Per default, all mutations run in parallel - even if you invoke `.mutate()` of the same mutation multiple times. Mutations can be given a `scope` with an `id` to avoid that. All mutations with the same `scope.id` will run in serial, which means when they are triggered, they will start in `isPaused: true` state if there is already a mutation for that scope in progress. They will be put into a queue and will automatically resume once their time in the queue has come.
Expand All @@ -406,6 +404,7 @@ const mutation = useMutation({
```

[//]: # 'ExampleScopes'
[//]: # 'Materials'

## Further reading

Expand Down
6 changes: 4 additions & 2 deletions docs/framework/react/guides/optimistic-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ This is the simpler variant, as it doesn't interact with the cache directly.
[//]: # 'ExampleUI1'

```tsx
const { isPending, submittedAt, variables, mutate, isError } = useMutation({
const addTodoMutation = useMutation({
mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }),
// make sure to _return_ the Promise from the query invalidation
// so that the mutation stays in `pending` state until the refetch is finished
onSettled: async () => {
return await queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})

const { isPending, submittedAt, variables, mutate, isError } = addTodoMutation
```

[//]: # 'ExampleUI1'

you will then have access to `addTodoMutation.variables`, which contain the added todo. In your UI list, where the query is rendered, you can append another item to the list while the mutation is `pending`:
you will then have access to `addTodoMutation.variables`, which contain the added todo. In your UI list, where the query is rendered, you can append another item to the list while the mutation `isPending`:

[//]: # 'ExampleUI2'

Expand Down
Loading

0 comments on commit ab3e21b

Please sign in to comment.