Skip to content

Commit a262982

Browse files
committed
Merge branch 'main' into refactor/maybe-promise
2 parents 4c22b57 + 10b4c2f commit a262982

File tree

155 files changed

+2200
-1138
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+2200
-1138
lines changed

.github/renovate.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"@types/node",
2121
"@types/react",
2222
"@types/react-dom",
23-
"eslint-plugin-react-compiler",
2423
"node",
2524
"react",
2625
"react-dom",

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ If you have been assigned to fix an issue or develop a new feature, please follo
2323
pnpm install
2424
```
2525

26-
- We use [pnpm](https://pnpm.io/) v9 for package management (run in case of pnpm-related issues).
26+
- We use [pnpm](https://pnpm.io/) v10 for package management (run in case of pnpm-related issues).
2727

2828
```bash
2929
corepack enable && corepack prepare
@@ -57,7 +57,7 @@ If you have been assigned to fix an issue or develop a new feature, please follo
5757
The documentations for all the TanStack projects are hosted on [tanstack.com](https://tanstack.com), which is a TanStack Start application (https://github.com/TanStack/tanstack.com). You need to run this app locally to preview your changes in the `TanStack/query` docs.
5858

5959
> [!NOTE]
60-
> The website fetches the doc pages from GitHub in production, and searches for them at `../query/docs` in development. Your local clone of `TanStack/query` needs to be in the same directory as the local clone of `TansStack/tanstack.com`.
60+
> The website fetches the doc pages from GitHub in production, and searches for them at `../query/docs` in development. Your local clone of `TanStack/query` needs to be in the same directory as the local clone of `TanStack/tanstack.com`.
6161

6262
You can follow these steps to set up the docs for local development:
6363

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,16 @@ Still on **React Query v4**? No problem! Check out the v4 docs here: https://tan
5050
<a href="https://www.speakeasy.com/product/react-query?utm_source=tanstack&utm_campaign=tanstack">
5151
<picture>
5252
<source
53-
srcset="https://tanstack.com/_build/assets/speakeasy-dark-BjP-Hd9M.svg"
53+
srcset="https://tanstack.com/assets/speakeasy-dark-BjP-Hd9M.svg"
5454
media="(prefers-color-scheme: dark)"
5555
/>
5656
<source
57-
srcset="https://tanstack.com/_build/assets/speakeasy-light-UpY7QmwQ.svg"
57+
srcset="https://tanstack.com/assets/speakeasy-light-UpY7QmwQ.svg"
5858
media="(prefers-color-scheme: light)"
5959
/>
6060
<!-- fallback -->
6161
<img
62-
src="https://tanstack.com/_build/assets/speakeasy-light-UpY7QmwQ.svg"
62+
src="https://tanstack.com/assets/speakeasy-light-UpY7QmwQ.svg"
6363
alt="Speakeasy Logo"
6464
/>
6565
</picture>

docs/framework/react/guides/important-defaults.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@ Out of the box, TanStack Query is configured with **aggressive but sane** defaul
99

1010
> To change this behavior, you can configure your queries both globally and per-query using the `staleTime` option. Specifying a longer `staleTime` means queries will not refetch their data as often
1111
12+
- A Query that has a `staleTime` set is considered **fresh** until that `staleTime` has elapsed.
13+
14+
- set `staleTime` to e.g. `2 * 60 * 1000` to make sure data is read from the cache, without triggering any kinds of refetches, for 2 minutes, or until the Query is [invalidated manually](./query-invalidation.md).
15+
- set `staleTime` to `Infinity` to never trigger a refetch until the Query is [invalidated manually](./query-invalidation.md).
16+
- set `staleTime` to `'static'` to **never** trigger a refetch, even if the Query is [invalidated manually](./query-invalidation.md).
17+
1218
- Stale queries are refetched automatically in the background when:
1319
- New instances of the query mount
1420
- The window is refocused
1521
- The network is reconnected
16-
- The query is optionally configured with a refetch interval
1722

18-
> To change this functionality, you can use options like `refetchOnMount`, `refetchOnWindowFocus`, `refetchOnReconnect` and `refetchInterval`.
23+
> Setting `staleTime` is the recommended way to avoid excessive refetches, but you can also customize the points in time for refetches by setting options like `refetchOnMount`, `refetchOnWindowFocus` and `refetchOnReconnect`.
24+
25+
- Queries can optionally be configured with a `refetchInterval` to trigger refetches periodically, which is independent of the `staleTime` setting.
1926

2027
- Query results that have no more active instances of `useQuery`, `useInfiniteQuery` or query observers are labeled as "inactive" and remain in the cache in case they are used again at a later time.
2128
- By default, "inactive" queries are garbage collected after **5 minutes**.

docs/framework/react/guides/mutations.md

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,233 @@ useMutation({
182182

183183
[//]: # 'Example5'
184184

185-
You might find that you want to **trigger additional callbacks** beyond the ones defined on `useMutation` when calling `mutate`. This can be used to trigger component-specific side effects. To do that, you can provide any of the same callback options to the `
185+
You might find that you want to **trigger additional callbacks** beyond the ones defined on `useMutation` when calling `mutate`. This can be used to trigger component-specific side effects. To do that, you can provide any of the same callback options to the `mutate` function after your mutation variable. Supported options include: `onSuccess`, `onError` and `onSettled`. Please keep in mind that those additional callbacks won't run if your component unmounts _before_ the mutation finishes.
186+
187+
[//]: # 'Example6'
188+
189+
```tsx
190+
useMutation({
191+
mutationFn: addTodo,
192+
onSuccess: (data, variables, context) => {
193+
// I will fire first
194+
},
195+
onError: (error, variables, context) => {
196+
// I will fire first
197+
},
198+
onSettled: (data, error, variables, context) => {
199+
// I will fire first
200+
},
201+
})
202+
203+
mutate(todo, {
204+
onSuccess: (data, variables, context) => {
205+
// I will fire second!
206+
},
207+
onError: (error, variables, context) => {
208+
// I will fire second!
209+
},
210+
onSettled: (data, error, variables, context) => {
211+
// I will fire second!
212+
},
213+
})
214+
```
215+
216+
[//]: # 'Example6'
217+
218+
### Consecutive mutations
219+
220+
There is a slight difference in handling `onSuccess`, `onError` and `onSettled` callbacks when it comes to consecutive mutations. When passed to the `mutate` function, they will be fired up only _once_ and only if the component is still mounted. This is due to the fact that mutation observer is removed and resubscribed every time when the `mutate` function is called. On the contrary, `useMutation` handlers execute for each `mutate` call.
221+
222+
> Be aware that most likely, `mutationFn` passed to `useMutation` is asynchronous. In that case, the order in which mutations are fulfilled may differ from the order of `mutate` function calls.
223+
224+
[//]: # 'Example7'
225+
226+
```tsx
227+
useMutation({
228+
mutationFn: addTodo,
229+
onSuccess: (data, variables, context) => {
230+
// Will be called 3 times
231+
},
232+
})
233+
234+
const todos = ['Todo 1', 'Todo 2', 'Todo 3']
235+
todos.forEach((todo) => {
236+
mutate(todo, {
237+
onSuccess: (data, variables, context) => {
238+
// Will execute only once, for the last mutation (Todo 3),
239+
// regardless which mutation resolves first
240+
},
241+
})
242+
})
243+
```
244+
245+
[//]: # 'Example7'
246+
247+
## Promises
248+
249+
Use `mutateAsync` instead of `mutate` to get a promise which will resolve on success or throw on an error. This can for example be used to compose side effects.
250+
251+
[//]: # 'Example8'
252+
253+
```tsx
254+
const mutation = useMutation({ mutationFn: addTodo })
255+
256+
try {
257+
const todo = await mutation.mutateAsync(todo)
258+
console.log(todo)
259+
} catch (error) {
260+
console.error(error)
261+
} finally {
262+
console.log('done')
263+
}
264+
```
265+
266+
[//]: # 'Example8'
267+
268+
## Retry
269+
270+
By default, TanStack Query will not retry a mutation on error, but it is possible with the `retry` option:
271+
272+
[//]: # 'Example9'
273+
274+
```tsx
275+
const mutation = useMutation({
276+
mutationFn: addTodo,
277+
retry: 3,
278+
})
279+
```
280+
281+
[//]: # 'Example9'
282+
283+
If mutations fail because the device is offline, they will be retried in the same order when the device reconnects.
284+
285+
## Persist mutations
286+
287+
Mutations can be persisted to storage if needed and resumed at a later point. This can be done with the hydration functions:
288+
289+
[//]: # 'Example10'
290+
291+
```tsx
292+
const queryClient = new QueryClient()
293+
294+
// Define the "addTodo" mutation
295+
queryClient.setMutationDefaults(['addTodo'], {
296+
mutationFn: addTodo,
297+
onMutate: async (variables) => {
298+
// Cancel current queries for the todos list
299+
await queryClient.cancelQueries({ queryKey: ['todos'] })
300+
301+
// Create optimistic todo
302+
const optimisticTodo = { id: uuid(), title: variables.title }
303+
304+
// Add optimistic todo to todos list
305+
queryClient.setQueryData(['todos'], (old) => [...old, optimisticTodo])
306+
307+
// Return context with the optimistic todo
308+
return { optimisticTodo }
309+
},
310+
onSuccess: (result, variables, context) => {
311+
// Replace optimistic todo in the todos list with the result
312+
queryClient.setQueryData(['todos'], (old) =>
313+
old.map((todo) =>
314+
todo.id === context.optimisticTodo.id ? result : todo,
315+
),
316+
)
317+
},
318+
onError: (error, variables, context) => {
319+
// Remove optimistic todo from the todos list
320+
queryClient.setQueryData(['todos'], (old) =>
321+
old.filter((todo) => todo.id !== context.optimisticTodo.id),
322+
)
323+
},
324+
retry: 3,
325+
})
326+
327+
// Start mutation in some component:
328+
const mutation = useMutation({ mutationKey: ['addTodo'] })
329+
mutation.mutate({ title: 'title' })
330+
331+
// If the mutation has been paused because the device is for example offline,
332+
// Then the paused mutation can be dehydrated when the application quits:
333+
const state = dehydrate(queryClient)
334+
335+
// The mutation can then be hydrated again when the application is started:
336+
hydrate(queryClient, state)
337+
338+
// Resume the paused mutations:
339+
queryClient.resumePausedMutations()
340+
```
341+
342+
[//]: # 'Example10'
343+
344+
### Persisting Offline mutations
345+
346+
If you persist offline mutations with the [persistQueryClient plugin](../plugins/persistQueryClient.md), mutations cannot be resumed when the page is reloaded unless you provide a default mutation function.
347+
348+
This is a technical limitation. When persisting to an external storage, only the state of mutations is persisted, as functions cannot be serialized. After hydration, the component that triggers the mutation might not be mounted, so calling `resumePausedMutations` might yield an error: `No mutationFn found`.
349+
350+
[//]: # 'Example11'
351+
352+
```tsx
353+
const persister = createSyncStoragePersister({
354+
storage: window.localStorage,
355+
})
356+
const queryClient = new QueryClient({
357+
defaultOptions: {
358+
queries: {
359+
gcTime: 1000 * 60 * 60 * 24, // 24 hours
360+
},
361+
},
362+
})
363+
364+
// we need a default mutation function so that paused mutations can resume after a page reload
365+
queryClient.setMutationDefaults(['todos'], {
366+
mutationFn: ({ id, data }) => {
367+
return api.updateTodo(id, data)
368+
},
369+
})
370+
371+
export default function App() {
372+
return (
373+
<PersistQueryClientProvider
374+
client={queryClient}
375+
persistOptions={{ persister }}
376+
onSuccess={() => {
377+
// resume mutations after initial restore from localStorage was successful
378+
queryClient.resumePausedMutations()
379+
}}
380+
>
381+
<RestOfTheApp />
382+
</PersistQueryClientProvider>
383+
)
384+
}
385+
```
386+
387+
[//]: # 'Example11'
388+
389+
We also have an extensive [offline example](../examples/offline) that covers both queries and mutations.
390+
391+
## Mutation Scopes
392+
393+
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.
394+
395+
[//]: # 'ExampleScopes'
396+
397+
```tsx
398+
const mutation = useMutation({
399+
mutationFn: addTodo,
400+
scope: {
401+
id: 'todo',
402+
},
403+
})
404+
```
405+
406+
[//]: # 'ExampleScopes'
407+
[//]: # 'Materials'
408+
409+
## Further reading
410+
411+
For more information about mutations, have a look at [#12: Mastering Mutations in React Query](../community/tkdodos-blog.md#12-mastering-mutations-in-react-query) from
412+
the Community Resources.
413+
414+
[//]: # 'Materials'

docs/framework/react/guides/query-keys.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ id: query-keys
33
title: Query Keys
44
---
55

6-
At its core, TanStack Query manages query caching for you based on query keys. Query keys have to be an Array at the top level, and can be as simple as an Array with a single string, or as complex as an array of many strings and nested objects. As long as the query key is serializable, and **unique to the query's data**, you can use it!
6+
At its core, TanStack Query manages query caching for you based on query keys. Query keys have to be an Array at the top level, and can be as simple as an Array with a single string, or as complex as an array of many strings and nested objects. As long as the query key is serializable using `JSON.stringify`, and **unique to the query's data**, you can use it!
77

88
## Simple Query Keys
99

docs/framework/react/guides/ssr.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ Note that the queries are no longer fetched on the client, instead their data wa
509509

510510
We simply can not know before we have fetched the feed if we also need to fetch graph data, they are dependent queries. Because this happens on the server where latency is generally both lower and more stable, this often isn't such a big deal.
511511

512-
Amazing, we've mostly flattened our waterfalls! There's a catch though. Let's call this page the `/feed` page, and let's pretend we also have another page like `/posts`. If we type in `www.example.com/feed` directly in the url bar and hit enter, we get all these great server rendering benefits, BUT, if we instead type in `www.example.com/posts` and then **click a link** to `/feed`, we're back to to this:
512+
Amazing, we've mostly flattened our waterfalls! There's a catch though. Let's call this page the `/feed` page, and let's pretend we also have another page like `/posts`. If we type in `www.example.com/feed` directly in the url bar and hit enter, we get all these great server rendering benefits, BUT, if we instead type in `www.example.com/posts` and then **click a link** to `/feed`, we're back to this:
513513

514514
```
515515
1. |> JS for <Feed>

docs/framework/react/plugins/createPersister.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,17 @@ This function can be used to sporadically clean up stoage from `expired`, `buste
111111
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
112112
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.
113113

114-
### `persisterRestoreAll(queryClient: QueryClient): Promise<void>`
114+
### `restoreQueries(queryClient: QueryClient, filters): Promise<void>`
115115

116-
This function can be used to restore all queries that are currently stored by persister in one go.
117-
For example when your app is starting up in offline mode, or you want data from previous session to be immediately available without intermediate `loading` state.
116+
This function can be used to restore queries that are currently stored by persister.
117+
For example when your app is starting up in offline mode, or you want all or only specific data from previous session to be immediately available without intermediate `loading` state.
118+
119+
The filter object supports the following properties:
120+
121+
- `queryKey?: QueryKey`
122+
- Set this property to define a query key to match on.
123+
- `exact?: boolean`
124+
- If you don't want to search queries inclusively by query key, you can pass the `exact: true` option to return only the query with the exact query key you have passed.
118125

119126
For this function to work, your storage must expose `entries` method that would return a `key-value tuple array`.
120127
For example `Object.entries(localStorage)` for `localStorage` or `entries` from `idb-keyval`.

docs/framework/react/reference/useQuery.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,13 @@ const {
9090
- This function receives a `retryAttempt` integer and the actual Error and returns the delay to apply before the next attempt in milliseconds.
9191
- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
9292
- A function like `attempt => attempt * 1000` applies linear backoff.
93-
- `staleTime: number | ((query: Query) => number)`
93+
- `staleTime: number | 'static' ((query: Query) => number | 'static')`
9494
- Optional
9595
- Defaults to `0`
9696
- The time in milliseconds after which data is considered stale. This value only applies to the hook it is defined on.
97-
- If set to `Infinity`, the data will never be considered stale
97+
- If set to `Infinity`, the data will not be considered stale unless manually invalidated
9898
- If set to a function, the function will be executed with the query to compute a `staleTime`.
99+
- If set to `'static'`, the data will never be considered stale
99100
- `gcTime: number | Infinity`
100101
- Defaults to `5 * 60 * 1000` (5 minutes) or `Infinity` during SSR
101102
- The time in milliseconds that unused/inactive cache data remains in memory. When a query's cache becomes unused or inactive, that cache data will be garbage collected after this duration. When different garbage collection times are specified, the longest one will be used.
@@ -116,21 +117,21 @@ const {
116117
- Defaults to `true`
117118
- If set to `true`, the query will refetch on mount if the data is stale.
118119
- If set to `false`, the query will not refetch on mount.
119-
- If set to `"always"`, the query will always refetch on mount.
120+
- If set to `"always"`, the query will always refetch on mount (except when `staleTime: 'static'` is used).
120121
- If set to a function, the function will be executed with the query to compute the value
121122
- `refetchOnWindowFocus: boolean | "always" | ((query: Query) => boolean | "always")`
122123
- Optional
123124
- Defaults to `true`
124125
- If set to `true`, the query will refetch on window focus if the data is stale.
125126
- If set to `false`, the query will not refetch on window focus.
126-
- If set to `"always"`, the query will always refetch on window focus.
127+
- If set to `"always"`, the query will always refetch on window focus (except when `staleTime: 'static'` is used).
127128
- If set to a function, the function will be executed with the query to compute the value
128129
- `refetchOnReconnect: boolean | "always" | ((query: Query) => boolean | "always")`
129130
- Optional
130131
- Defaults to `true`
131132
- If set to `true`, the query will refetch on reconnect if the data is stale.
132133
- If set to `false`, the query will not refetch on reconnect.
133-
- If set to `"always"`, the query will always refetch on reconnect.
134+
- If set to `"always"`, the query will always refetch on reconnect (except when `staleTime: 'static'` is used).
134135
- If set to a function, the function will be executed with the query to compute the value
135136
- `notifyOnChangeProps: string[] | "all" | (() => string[] | "all" | undefined)`
136137
- Optional

0 commit comments

Comments
 (0)