Loading...
Loading...
Loading...
) : status === 'error' ? (Error: {error.message}
@@ -103,86 +103,29 @@ function Projects() { When an infinite query becomes `stale` and needs to be refetched, each group is fetched `sequentially`, starting from the first one. This ensures that even if the underlying data is mutated, we're not using stale cursors and potentially getting duplicates or skipping records. If an infinite query's results are ever removed from the queryCache, the pagination restarts at the initial state with only the initial group being requested. -### refetchPage - -If you only want to actively refetch a subset of all pages, you can pass the `refetchPage` function to `refetch` returned from `useInfiniteQuery`. - -[//]: # 'Example2' - -```tsx -const { refetch } = useInfiniteQuery({ - queryKey: ['projects'], - queryFn: fetchProjects, - getNextPageParam: (lastPage, pages) => lastPage.nextCursor, -}) - -// only refetch the first page -refetch({ refetchPage: (page, index) => index === 0 }) -``` - -[//]: # 'Example2' - -You can also pass this function as part of the 2nd argument (`queryFilters`) to [queryClient.refetchQueries](../reference/QueryClient#queryclientrefetchqueries), [queryClient.invalidateQueries](../reference/QueryClient#queryclientinvalidatequeries) or [queryClient.resetQueries](../reference/QueryClient#queryclientresetqueries). - -**Signature** - -- `refetchPage: (page: TData, index: number, allPages: TData[]) => boolean` - -The function is executed for each page, and only pages where this function returns `true` will be refetched. - -## What if I need to pass custom information to my query function? - -By default, the variable returned from `getNextPageParam` will be supplied to the query function, but in some cases, you may want to override this. You can pass custom variables to the `fetchNextPage` function which will override the default variable like so: - -[//]: # 'Example3' - -```tsx -function Projects() { - const fetchProjects = ({ pageParam = 0 }) => - fetch('/api/projects?cursor=' + pageParam) - - const { - status, - data, - isFetching, - isFetchingNextPage, - fetchNextPage, - hasNextPage, - } = useInfiniteQuery({ - queryKey: ['projects'], - queryFn: fetchProjects, - getNextPageParam: (lastPage, pages) => lastPage.nextCursor, - }) - - // Pass your own page param - const skipToCursor50 = () => fetchNextPage({ pageParam: 50 }) -} -``` - -[//]: # 'Example3' - ## What if I want to implement a bi-directional infinite list? Bi-directional lists can be implemented by using the `getPreviousPageParam`, `fetchPreviousPage`, `hasPreviousPage` and `isFetchingPreviousPage` properties and functions. -[//]: # 'Example4' +[//]: # 'Example3' ```tsx useInfiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, + defaultPageParam: 0, getNextPageParam: (lastPage, pages) => lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor, }) ``` -[//]: # 'Example4' +[//]: # 'Example3' ## What if I want to show the pages in reversed order? Sometimes you may want to show the pages in reversed order. If this is case, you can use the `select` option: -[//]: # 'Example5' +[//]: # 'Example4' ```tsx useInfiniteQuery({ @@ -195,13 +138,13 @@ useInfiniteQuery({ }) ``` -[//]: # 'Example5' +[//]: # 'Example4' ## What if I want to manually update the infinite query? ### Manually removing first page: -[//]: # 'Example6' +[//]: # 'Example5' ```tsx queryClient.setQueryData(['projects'], (data) => ({ @@ -210,11 +153,11 @@ queryClient.setQueryData(['projects'], (data) => ({ })) ``` -[//]: # 'Example6' +[//]: # 'Example5' ### Manually removing a single value from an individual page: -[//]: # 'Example7' +[//]: # 'Example6' ```tsx const newPagesArray = @@ -228,11 +171,11 @@ queryClient.setQueryData(['projects'], (data) => ({ })) ``` -[//]: # 'Example7' +[//]: # 'Example6' ### Keep only the first page: -[//]: # 'Example8' +[//]: # 'Example7' ```tsx queryClient.setQueryData(['projects'], (data) => ({ @@ -241,6 +184,61 @@ queryClient.setQueryData(['projects'], (data) => ({ })) ``` -[//]: # 'Example8' +[//]: # 'Example7' Make sure to always keep the same data structure of pages and pageParams! + + +[//]: # 'Example8' + +## What if I want to limit the number of pages? + +In some use cases you may want to limit the number of pages stored in the query data to improve the performance and UX: + +- when the user can load a large number of pages (memory usage) +- when you have to refetch an infinite query that contains dozens of pages (network usage: all the pages are sequentially fetched) + +The solution is to use a "Limited Infinite Query". This is made possible by using the `maxPages` option in conjunction with `getNextPageParam` and `getPreviousPageParam` to allow fetching pages when needed in both directions. + +In the following example only 3 pages are kept in the query data pages array. If a refetch is needed, only 3 pages will be refetched sequentially. + +[//]: # 'Example8' + +```tsx +useInfiniteQuery({ + queryKey: ['projects'], + queryFn: fetchProjects, + defaultPageParam: 0, + getNextPageParam: (lastPage, pages) => lastPage.nextCursor, + getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor, + maxPages: 3, +}) +``` + +## What if my API doesn't return a cursor? + +If your API doesn't return a cursor, you can use the `pageParam` as a cursor. Because `getNextPageParam` and `getPreviousPageParam` also get the `pageParam`of the current page, you can use it to calculate the next / previous page param. + +[//]: # 'Example9' + +```tsx +return useInfiniteQuery({ + queryKey: ['projects'], + queryFn: fetchProjects, + defaultPageParam: 0, + getNextPageParam: (lastPage, allPages, lastPageParam) => { + if (lastPage.length === 0) { + return undefined + } + return lastPageParam + 1 + }, + getPreviousPageParam: (firstPage, allPages, firstPageParam) => { + if (firstPageParam <= 1) { + return undefined + } + return firstPageParam - 1 + }, +}) +``` + +[//]: # 'Example9' diff --git a/docs/react/guides/migrating-to-v5.md b/docs/react/guides/migrating-to-v5.md new file mode 100644 index 0000000000..b19440e22e --- /dev/null +++ b/docs/react/guides/migrating-to-v5.md @@ -0,0 +1,462 @@ +--- +id: migrating-to-tanstack-query-5 +title: Migrating to TanStack Query v5 +--- + +## Breaking Changes + +v5 is a major version, so there are some breaking changes to be aware of: + +### Supports a single signature, one object + +useQuery and friends used to have many overloads in TypeScript - different ways how the function can be invoked. Not only this was tough to maintain, type wise, it also required a runtime check to see which type the first and the second parameter, to correctly create options. + +now we only support the object format. + +```diff +- useQuery(key, fn, options) ++ useQuery({ queryKey, queryFn, ...options }) + +- useInfiniteQuery(key, fn, options) ++ useInfiniteQuery({ queryKey, queryFn, ...options }) + +- useMutation(fn, options) ++ useMutation({ mutationFn, ...options }) + +- useIsFetching(key, filters) ++ useIsFetching({ queryKey, ...filters }) + +- useIsMutating(key, filters) ++ useIsMutating({ mutationKey, ...filters }) +``` + +```diff +- queryClient.isFetching(key, filters) ++ queryClient.isFetching({ queryKey, ...filters }) + +- queryClient.ensureQueryData(key, filters) ++ queryClient.ensureQueryData({ queryKey, ...filters }) + +- queryClient.getQueriesData(key, filters) ++ queryClient.getQueriesData({ queryKey, ...filters }) + +- queryClient.setQueriesData(key, updater, filters, options) ++ queryClient.setQueriesData({ queryKey, ...filters }, updater, options) + +- queryClient.removeQueries(key, filters) ++ queryClient.removeQueries({ queryKey, ...filters }) + +- queryClient.resetQueries(key, filters, options) ++ queryClient.resetQueries({ queryKey, ...filters }, options) + +- queryClient.cancelQueries(key, filters, options) ++ queryClient.cancelQueries({ queryKey, ...filters }, options) + +- queryClient.invalidateQueries(key, filters, options) ++ queryClient.invalidateQueries({ queryKey, ...filters }, options) + +- queryClient.refetchQueries(key, filters, options) ++ queryClient.refetchQueries({ queryKey, ...filters }, options) + +- queryClient.fetchQuery(key, fn, options) ++ queryClient.fetchQuery({ queryKey, queryFn, ...options }) + +- queryClient.prefetchQuery(key, fn, options) ++ queryClient.prefetchQuery({ queryKey, queryFn, ...options }) + +- queryClient.fetchInfiniteQuery(key, fn, options) ++ queryClient.fetchInfiniteQuery({ queryKey, queryFn, ...options }) + +- queryClient.prefetchInfiniteQuery(key, fn, options) ++ queryClient.prefetchInfiniteQuery({ queryKey, queryFn, ...options }) +``` + +```diff +- queryCache.find(key, filters) ++ queryCache.find({ queryKey, ...filters }) + +- queryCache.findAll(key, filters) ++ queryCache.findAll({ queryKey, ...filters }) +``` + +### `queryClient.getQueryData` now accepts queryKey only as an Argument + +`queryClient.getQueryData` argument is changed to accept only a `queryKey` + +```diff +- queryClient.getQueryData(queryKey, filters) ++ queryClient.getQueryData(queryKey) +``` + +### `queryClient.getQueryState` now accepts queryKey only as an Argument + +`queryClient.getQueryState` argument is changed to accept only a `queryKey` + +```diff +- queryClient.getQueryState(queryKey, filters) ++ queryClient.getQueryState(queryKey) +``` + +#### Codemod + +To make the remove overloads migration easier, v5 comes with a codemod. + +> The codemod is a best efforts attempt to help you migrate the breaking change. Please review the generated code thoroughly! Also, there are edge cases that cannot be found by the code mod, so please keep an eye on the log output. + +If you want to run it against `.js` or `.jsx` files, please use the command below: + +``` +npx jscodeshift ./path/to/src/ \ + --extensions=js,jsx \ + --transform=./node_modules/@tanstack/react-query/build/codemods/v5/remove-overloads/remove-overloads.js +``` + +If you want to run it against `.ts` or `.tsx` files, please use the command below: + +``` +npx jscodeshift ./path/to/src/ \ + --extensions=ts,tsx \ + --parser=tsx \ + --transform=./node_modules/@tanstack/react-query/build/codemods/v5/remove-overloads/remove-overloads.js +``` + +Please note in the case of `TypeScript` you need to use `tsx` as the parser; otherwise, the codemod won't be applied properly! + +**Note:** Applying the codemod might break your code formatting, so please don't forget to run `prettier` and/or `eslint` after you've applied the codemod! + +A few notes about how codemod works: + +- Generally, we're looking for the lucky case, when the first parameter is an object expression and contains the "queryKey" or "mutationKey" property (depending on which hook/method call is being transformed). If this is the case, your code already matches the new signature, so the codemod won't touch it. 🎉 +- If the condition above is not fulfilled, then the codemod will check whether the first parameter is an array expression or an identifier that references an array expression. If this is the case, the codemod will put it into an object expression, then it will be the first parameter. +- If object parameters can be inferred, the codemod will attempt to copy the already existing properties to the newly created one. +- If the codemod cannot infer the usage, then it will leave a message on the console. The message contains the file name and the line number of the usage. In this case, you need to do the migration manually. +- If the transformation results in an error, you will also see a message on the console. This message will notify you something unexpected happened, please do the migration manually. + +### Callbacks on useQuery (and QueryObserver) have been removed + +`onSuccess`, `onError` and `onSettled` have been removed from Queries. They haven't been touched for Mutations. Please see [this RFC](https://github.com/TanStack/query/discussions/5279) for motivations behind this change and what to do instead. + +### The `remove` method has been removed from useQuery + +Previously, remove method used to remove the query from the queryCache without informing observers about it. It was best used to remove data imperatively that is no longer needed, e.g. when logging a user out. + +But It doesn't make much sense to do this while a query is still active, because it will just trigger a hard loading state with the next re-render. + +if you still need to remove a query, you can use `queryClient.removeQueries({queryKey: key})` + +```diff + const queryClient = useQueryClient(); + const query = useQuery({ queryKey, queryFn }); + +- query.remove() ++ queryClient.removeQueries({ queryKey }) +``` + +### The minimum required TypeScript version is now 4.7 + +Mainly because an important fix was shipped around type inference. Please see this [TypeScript issue](https://github.com/microsoft/TypeScript/issues/43371) for more information. + +### The `isDataEqual` option has been removed from useQuery + +Previously, This function was used to indicate whether to use previous `data` (`true`) or new data (`false`) as a resolved data for the query. + +You can achieve the same functionality by passing a function to `structuralSharing` instead: + +```diff + import { replaceEqualDeep } from '@tanstack/react-query' + +- isDataEqual: (oldData, newData) => customCheck(oldData, newData) ++ structuralSharing: (oldData, newData) => customCheck(oldData, newData) ? oldData : replaceEqualDeep(oldData, newData) +``` + +### The deprecated custom logger has been removed + +Custom loggers were already deprecated in 4 and have been removed in this version. Logging only had an effect in development mode, where passing a custom logger is not necessary. + +### Supported Browsers + +We have updated our browserslist to produce a more modern, performant and smaller bundle. You can read about the requirements [here](../installation#requirements). + +### Private class fields and methods + +TanStack Query has always had private fields and methods on classes, but they weren't really private - they were just private in `TypeScript`. We now use [ECMAScript Private class features](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields), which means those fields are now truly private and can't be accessed from the outside at runtime. + +### Rename `cacheTime` to `gcTime` + +Almost everyone gets `cacheTime` wrong. It sounds like "the amount of time that data is cached for", but that is not correct. + +`cacheTime` does nothing as long as a query is still in used. It only kicks in as soon as the query becomes unused. After the time has passed, data will be "garbage collected" to avoid the cache from growing. + +`gc` is referring to "garbage collect" time. It's a bit more technical, but also a quite [well known abbreviation](https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)) in computer science. + +```diff +const MINUTE = 1000 * 60; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { +- cacheTime: 10 * MINUTE, ++ gcTime: 10 * MINUTE, + }, + }, +}) +``` + +### The `useErrorBoundary` option has been renamed to `throwOnError` + +To make the `useErrorBoundary` option more framework-agnostic and avoid confusion with the established React function prefix "`use`" for hooks and the "ErrorBoundary" component name, it has been renamed to `throwOnError` to more accurately reflect its functionality. + +### TypeScript: `Error` is now the default type for errors instead of `unknown` + +Even though in JavaScript, you can `throw` anything (which makes `unknown` the most correct type), almost always, `Errors` (or subclasses of `Error`) are thrown. This change makes it easier to work with the `error` field in TypeScript for most cases. + +If you want to throw something that isn't an Error, you'll now have to set the generic for yourself: + +```ts +useQueryLoading...
Error: {query.error.message}
{todo.title}
} -{todo.title}
}Loading...
{todo.title}
} + {(todo) => ( + + )}Current Page: {{ page }} | Previous data: {{ isPreviousData }}
-Loading...
+ ) : status === 'error' ? ( + Error: {error.message} + ) : ( + <> ++ {project.name} +
+ ))} +Loading...
) : status === 'error' ? ( Error: {error.message} @@ -110,9 +109,7 @@ function Example() { > )}