Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
TkDodo authored Jun 8, 2024
2 parents 347c4d4 + f2db185 commit e28ea02
Show file tree
Hide file tree
Showing 161 changed files with 6,278 additions and 3,800 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,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 }}
4 changes: 2 additions & 2 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,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 }}
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
6 changes: 5 additions & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -849,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
6 changes: 6 additions & 0 deletions docs/framework/react/community/community-projects.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ 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
Expand Down
97 changes: 88 additions & 9 deletions docs/framework/react/guides/advanced-ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ The first step of any React Query setup is always to create a `queryClient` and
'use client'

// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import {
isServer,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'

function makeQueryClient() {
return new QueryClient({
Expand All @@ -47,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 @@ -354,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 @@ -370,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 @@ -389,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
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
8 changes: 6 additions & 2 deletions docs/framework/react/guides/prefetching.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,18 @@ This starts fetching `'article-comments'` immediately and flattens the waterfall
If you want to prefetch together with Suspense, you will have to do things a bit differently. You can't use `useSuspenseQueries` to prefetch, since the prefetch would block the component from rendering. You also can not use `useQuery` for the prefetch, because that wouldn't start the prefetch until after suspenseful query had resolved. What you can do is add a small `usePrefetchQuery` function (we might add this to the library itself at a later point):

```tsx
const usePrefetchQuery = (...args) => {
function usePrefetchQuery(options) {
const queryClient = useQueryClient()

// This happens in render, but is safe to do because ensureQueryData
// only fetches if there is no data in the cache for this query. This
// means we know no observers are watching the data so the side effect
// is not observable, which is safe.
queryClient.ensureQueryData(...args)
if (!queryClient.getQueryState(options.queryKey)) {
queryClient.ensureQueryData(options).catch(() => {
// Avoid uncaught error
})
}
}
```

Expand Down
12 changes: 8 additions & 4 deletions docs/framework/react/guides/suspense.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,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 @@ -137,13 +141,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
2 changes: 2 additions & 0 deletions docs/framework/react/react-native.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ title: React Native

React Query is designed to work out of the box with React Native, with the exception of the devtools, which are only supported with React DOM at this time.

There is a 3rd party [Expo](https://docs.expo.dev/) plugin which you can try: https://github.com/expo/dev-plugins/tree/main/packages/react-query

There is a 3rd party [Flipper](https://fbflipper.com/docs/getting-started/react-native/) plugin which you can try: https://github.com/bgaleotti/react-query-native-devtools

There is a 3rd party [Reactotron](https://github.com/infinitered/reactotron/) plugin which you can try: https://github.com/hsndmr/reactotron-react-query
Expand Down
6 changes: 4 additions & 2 deletions docs/framework/react/reference/hydration.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const dehydratedState = dehydrate(queryClient, {
- You **should not** rely on the exact format of this response, it is not part of the public API and can change at any time
- This result is not in serialized form, you need to do that yourself if desired

### limitations
### Limitations

Some storage systems (such as browser [Web Storage API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API)) require values to be JSON serializable. If you need to dehydrate values that are not automatically serializable to JSON (like `Error` or `undefined`), you have to serialize them for yourself. Since only successful queries are included per default, to also include `Errors`, you have to provide `shouldDehydrateQuery`, e.g.:

Expand Down Expand Up @@ -88,7 +88,7 @@ hydrate(queryClient, dehydratedState, options)

### Limitations

If the queries included in dehydration already exist in the queryCache, `hydrate` does not overwrite them and they will be **silently** discarded.
If the queries you're trying to hydrate already exist in the queryCache, `hydrate` will only overwrite them if the data is newer than the data present in the cache. Otherwise, it will **not** get applied.

[//]: # 'HydrationBoundary'

Expand All @@ -104,6 +104,8 @@ function App() {
}
```

> Note: Only `queries` can be dehydrated with an `HydrationBoundary`.
**Options**

- `state: DehydratedState`
Expand Down
1 change: 1 addition & 0 deletions docs/reference/QueryClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Its available methods are:
- `defaultOptions?: DefaultOptions`
- Optional
- Define defaults for all queries and mutations using this queryClient.
- You can also define defaults to be used for [hydration](../../framework/react/reference/hydration.md)

## `queryClient.fetchQuery`

Expand Down
23 changes: 10 additions & 13 deletions examples/angular/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^17.3.9",
"@angular/common": "^17.3.9",
"@angular/compiler": "^17.3.9",
"@angular/core": "^17.3.9",
"@angular/forms": "^17.3.9",
"@angular/platform-browser": "^17.3.9",
"@angular/platform-browser-dynamic": "^17.3.9",
"@angular/router": "^17.3.9",
"@tanstack/angular-query-experimental": "^5.37.1",
"@angular/common": "^17.3.10",
"@angular/compiler": "^17.3.10",
"@angular/core": "^17.3.10",
"@angular/platform-browser": "^17.3.10",
"@angular/platform-browser-dynamic": "^17.3.10",
"@tanstack/angular-query-experimental": "^5.40.1",
"rxjs": "^7.8.1",
"tslib": "^2.6.2",
"zone.js": "^0.14.6"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.7",
"@angular/cli": "^17.3.7",
"@angular/compiler-cli": "^17.3.9",
"@tanstack/angular-query-devtools-experimental": "^5.37.1",
"@angular-devkit/build-angular": "^17.3.8",
"@angular/cli": "^17.3.8",
"@angular/compiler-cli": "^17.3.10",
"@tanstack/angular-query-devtools-experimental": "^5.40.1",
"typescript": "5.3.3"
}
}
23 changes: 10 additions & 13 deletions examples/angular/infinite-query-with-max-pages/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^17.3.9",
"@angular/common": "^17.3.9",
"@angular/compiler": "^17.3.9",
"@angular/core": "^17.3.9",
"@angular/forms": "^17.3.9",
"@angular/platform-browser": "^17.3.9",
"@angular/platform-browser-dynamic": "^17.3.9",
"@angular/router": "^17.3.9",
"@tanstack/angular-query-experimental": "^5.37.1",
"@angular/common": "^17.3.10",
"@angular/compiler": "^17.3.10",
"@angular/core": "^17.3.10",
"@angular/platform-browser": "^17.3.10",
"@angular/platform-browser-dynamic": "^17.3.10",
"@tanstack/angular-query-experimental": "^5.40.1",
"rxjs": "^7.8.1",
"tslib": "^2.6.2",
"zone.js": "^0.14.6"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.7",
"@angular/cli": "^17.3.7",
"@angular/compiler-cli": "^17.3.9",
"@tanstack/angular-query-devtools-experimental": "^5.37.1",
"@angular-devkit/build-angular": "^17.3.8",
"@angular/cli": "^17.3.8",
"@angular/compiler-cli": "^17.3.10",
"@tanstack/angular-query-devtools-experimental": "^5.40.1",
"typescript": "5.3.3"
}
}
Loading

0 comments on commit e28ea02

Please sign in to comment.