diff --git a/.all-contributorsrc b/.all-contributorsrc index 9ed31f428a..c3efeeeacb 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -3,9 +3,7 @@ "projectOwner": "TanStack", "repoType": "github", "repoHost": "https://github.com", - "files": [ - "README.md" - ], + "files": ["README.md"], "imageSize": 100, "commit": false, "commitConvention": "none", @@ -15,306 +13,224 @@ "name": "Tanner Linsley", "avatar_url": "https://avatars0.githubusercontent.com/u/5580297?v=4", "profile": "https://tannerlinsley.com", - "contributions": [ - "code", - "ideas", - "example", - "maintenance", - "review" - ] + "contributions": ["code", "ideas", "example", "maintenance", "review"] }, { "login": "cherniavskii", "name": "Andrew Cherniavskii", "avatar_url": "https://avatars2.githubusercontent.com/u/13808724?v=4", "profile": "http://cherniavskii.com", - "contributions": [ - "code", - "bug" - ] + "contributions": ["code", "bug"] }, { "login": "tibotiber", "name": "Thibaut Tiberghien", "avatar_url": "https://avatars3.githubusercontent.com/u/5635553?v=4", "profile": "http://twitter.com/tibotiber", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "gargroh", "name": "Rohit Garg", "avatar_url": "https://avatars3.githubusercontent.com/u/42495927?v=4", "profile": "https://github.com/gargroh", - "contributions": [ - "tool" - ] + "contributions": ["tool"] }, { "login": "Avi98", "name": "Avinash", "avatar_url": "https://avatars1.githubusercontent.com/u/26133749?v=4", "profile": "https://github.com/Avi98", - "contributions": [ - "code", - "bug" - ] + "contributions": ["code", "bug"] }, { "login": "CreativeTechGuy", "name": "Jason O'Neill", "avatar_url": "https://avatars1.githubusercontent.com/u/12002072?v=4", "profile": "https://github.com/CreativeTechGuy", - "contributions": [ - "maintenance", - "test" - ] + "contributions": ["maintenance", "test"] }, { "login": "bugzpodder", "name": "Jack Zhao", "avatar_url": "https://avatars3.githubusercontent.com/u/14841421?v=4", "profile": "http://fb.me/yz", - "contributions": [ - "code", - "bug" - ] + "contributions": ["code", "bug"] }, { "login": "dpyzo0o", "name": "dpyzo0o", "avatar_url": "https://avatars1.githubusercontent.com/u/24768249?v=4", "profile": "https://github.com/dpyzo0o", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "jelteliekens", "name": "Jelte Liekens", "avatar_url": "https://avatars1.githubusercontent.com/u/3418474?v=4", "profile": "https://github.com/jelteliekens", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "jgettings", "name": "Jen Gettings", "avatar_url": "https://avatars0.githubusercontent.com/u/4183742?v=4", "profile": "https://github.com/jgettings", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "justincy", "name": "Justin", "avatar_url": "https://avatars2.githubusercontent.com/u/1037458?v=4", "profile": "https://github.com/justincy", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "MarceloAlves", "name": "Marcelo Alves", "avatar_url": "https://avatars1.githubusercontent.com/u/216782?v=4", "profile": "http://www.marceloalves.com", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "zorzysty", "name": "Zorza", "avatar_url": "https://avatars0.githubusercontent.com/u/5398733?v=4", "profile": "https://github.com/zorzysty", - "contributions": [ - "bug", - "code", - "doc" - ] + "contributions": ["bug", "code", "doc"] }, { "login": "tkdodo", "name": "Dominik Dorfmeister", "avatar_url": "https://avatars0.githubusercontent.com/u/1021430?v=4", "profile": "https://tkdodo.eu", - "contributions": [ - "code", - "doc", - "maintenance", - "question", - "review" - ] + "contributions": ["code", "doc", "maintenance", "question", "review"] }, { "login": "KATT", "name": "Alex Johansson", "avatar_url": "https://avatars.githubusercontent.com/u/459267?v=4", "profile": "https://katt.dev", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "EddyVinck", "name": "Eddy", "avatar_url": "https://avatars.githubusercontent.com/u/23434753?v=4", "profile": "http://www.eddyvinck.nl", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "prateek3255", "name": "Prateek Surana", "avatar_url": "https://avatars.githubusercontent.com/u/21277179?v=4", "profile": "http://prateeksurana.me", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "babycourageous", "name": "Rene Dellefont", "avatar_url": "https://avatars.githubusercontent.com/u/14936212?v=4", "profile": "https://github.com/babycourageous", - "contributions": [ - "code", - "doc" - ] + "contributions": ["code", "doc"] }, { "login": "jvuoti", "name": "Janne Vuoti", "avatar_url": "https://avatars.githubusercontent.com/u/3702781?v=4", "profile": "https://github.com/jvuoti", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "arnaudbzn", "name": "Arnaud", "avatar_url": "https://avatars.githubusercontent.com/u/20332397?v=4", "profile": "http://seaviewlab.com", - "contributions": [ - "code", - "doc" - ] + "contributions": ["code", "doc"] }, { "login": "zrwsk", "name": "Jakub Żurawski", "avatar_url": "https://avatars.githubusercontent.com/u/9089600?v=4", "profile": "https://github.com/zrwsk", - "contributions": [ - "doc" - ] + "contributions": ["doc"] }, { "login": "ardeora", "name": "Aryan Deora", "avatar_url": "https://avatars.githubusercontent.com/u/45807386?v=4", "profile": "http://www.aryandeora.com", - "contributions": [ - "code", - "maintenance" - ] + "contributions": ["code", "maintenance"] }, { "login": "jennyckaplan", "name": "Jen Kaplan", "avatar_url": "https://avatars.githubusercontent.com/u/25395806?v=4", "profile": "https://github.com/jennyckaplan", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "lukesmurray", "name": "Luke Murray", "avatar_url": "https://avatars.githubusercontent.com/u/34020210?v=4", "profile": "https://lukesmurray.com/", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "oscartbeaumont", "name": "Oscar Beaumont", "avatar_url": "https://avatars.githubusercontent.com/u/21004798?v=4", "profile": "https://otbeaumont.me", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "DamianOsipiuk", "name": "Damian Osipiuk", "avatar_url": "https://avatars.githubusercontent.com/u/28151934?v=4", "profile": "https://github.com/DamianOsipiuk", - "contributions": [ - "code", - "maintenance" - ] + "contributions": ["code", "maintenance"] }, { "login": "matthewhausman", "name": "Matthew Hausman", "avatar_url": "https://avatars.githubusercontent.com/u/25216513?v=4", "profile": "https://www.linkedin.com/in/matthew-hausman/", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "Newbie012", "name": "Eliya Cohen", "avatar_url": "https://avatars.githubusercontent.com/u/10504365?v=4", "profile": "https://github.com/Newbie012", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "KubaJastrz", "name": "Jakub Jastrzębski", "avatar_url": "https://avatars.githubusercontent.com/u/6443113?v=4", "profile": "https://kubajastrz.com", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "balazsmatepetro", "name": "Balázs Máté Petró", "avatar_url": "https://avatars.githubusercontent.com/u/1608725?v=4", "profile": "https://github.com/balazsmatepetro", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "Mamoanwar97", "name": "Mahmoud M. Anwar", "avatar_url": "https://avatars.githubusercontent.com/u/36894846?v=4", "profile": "http://linkedin.com/in/mamoanwar97/", - "contributions": [ - "code" - ] + "contributions": ["code"] }, { "login": "Moshyfawn", "name": "moshyfawn", "avatar_url": "https://avatars.githubusercontent.com/u/16290753?v=4", "profile": "http://moshyfawn.dev", - "contributions": [ - "code" - ] + "contributions": ["code"] } ], "contributorsPerLine": 7, diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e995e1aafc..82619e2a15 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -34,7 +34,10 @@ const config = { }, }, rules: { - '@typescript-eslint/array-type': ['error', { default: 'generic', readonly: 'generic' }], + '@typescript-eslint/array-type': [ + 'error', + { default: 'generic', readonly: 'generic' }, + ], '@typescript-eslint/ban-types': 'off', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/consistent-type-imports': [ diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e36ac20ce0..b740afe1bc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -3,9 +3,9 @@ name: pr on: pull_request: paths-ignore: - - "docs/**" - - "media/**" - - "**/*.md" + - 'docs/**' + - 'media/**' + - '**/*.md' concurrency: group: ${{ github.workflow }}-${{ github.event.number || github.ref }} diff --git a/.prettierignore b/.prettierignore index af4a55b2cb..4596205e79 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,3 +5,4 @@ **/coverage **/dist **/codemods/**/__testfixtures__ +pnpm-lock.yaml diff --git a/docs/react/adapters/react-query.md b/docs/react/adapters/react-query.md index 31a67e9c5f..b928726e34 100644 --- a/docs/react/adapters/react-query.md +++ b/docs/react/adapters/react-query.md @@ -7,7 +7,11 @@ The `react-query` package offers a 1st-class API for using TanStack Query via Re ## Example ```tsx -import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query' +import { + QueryClient, + QueryClientProvider, + useQuery, +} from '@tanstack/react-query' const queryClient = new QueryClient() @@ -19,15 +23,15 @@ function Example() { {query.isPending ? 'Loading...' : query.isError - ? 'Error!' - : query.data - ? query.data.map((todo) =>
{todo.title}
) - : null} + ? 'Error!' + : query.data + ? query.data.map((todo) =>
{todo.title}
) + : null} ) } -function App () { +function App() { return ( diff --git a/docs/react/adapters/solid-query.md b/docs/react/adapters/solid-query.md index b42bd624f4..616d4512c5 100644 --- a/docs/react/adapters/solid-query.md +++ b/docs/react/adapters/solid-query.md @@ -1,21 +1,25 @@ --- -title: Solid Query +title: Solid Query --- -The `@tanstack/solid-query` package provides a 1st-class API for using TanStack Query with SolidJS. +The `@tanstack/solid-query` package provides a 1st-class API for using TanStack Query with SolidJS. ## Example ```tsx -import { QueryClient, QueryClientProvider, createQuery } from '@tanstack/solid-query' +import { + QueryClient, + QueryClientProvider, + createQuery, +} from '@tanstack/solid-query' import { Switch, Match, For } from 'solid-js' const queryClient = new QueryClient() function Example() { const query = createQuery({ - queryKey: () => ['todos'], - queryFn: fetchTodos + queryKey: () => ['todos'], + queryFn: fetchTodos, }) return ( @@ -28,9 +32,7 @@ function Example() {

Error: {query.error.message}

- - {(todo) =>

{todo.title}

} -
+ {(todo) =>

{todo.title}

}
@@ -44,7 +46,6 @@ function App() {
) } - ``` ## Available Functions @@ -61,26 +62,23 @@ Solid Query offers useful primitives and functions that will make managing serve - `QueryClient` - `QueryClientProvider` - - - ## Important Differences between Solid Query & React Query -Solid Query offers an API similar to React Query, but there are some key differences to be mindful of. +Solid Query offers an API similar to React Query, but there are some key differences to be mindful of. - To maintain their reactivity, Query keys need to be wrapped inside a function while using `createQuery`, `createQueries`, `createInfiniteQuery` and `useIsFetching`. ```tsx // ❌ react version useQuery({ - queryKey: ["todos", todo], - queryFn: fetchTodos + queryKey: ['todos', todo], + queryFn: fetchTodos, }) // ✅ solid version createQuery({ - queryKey: () => ["todos", todo()], - queryFn: fetchTodos + queryKey: () => ['todos', todo()], + queryFn: fetchTodos, }) ``` @@ -91,14 +89,14 @@ import { For, Suspense } from 'solid-js' function Example() { const query = createQuery({ - queryKey: () => ['todos'], - queryFn: fetchTodos + queryKey: () => ['todos'], + queryFn: fetchTodos, }) return (
{/* ✅ Will trigger loading fallback, data accessed in a suspense context. */} - + {(todo) =>
{todo.title}
}
{/* ❌ Will not trigger loading fallback, data not accessed in a suspense context. */} @@ -111,7 +109,11 @@ function Example() { - Solid Query primitives (`createX`) do not support destructuring. The return value from these functions is a store, and their properties are only tracked in a reactive context. ```tsx -import { QueryClient, QueryClientProvider, createQuery } from '@tanstack/solid-query' +import { + QueryClient, + QueryClientProvider, + createQuery, +} from '@tanstack/solid-query' import { Match, Switch } from 'solid-js' const queryClient = new QueryClient() @@ -175,7 +177,7 @@ const queryClient = new QueryClient() function Example() { const [enabled, setEnabled] = createSignal(false) - const query = createQuery({ + const query = createQuery({ queryKey: () => ['todos'], queryFn: fetchTodos, // ❌ passing a signal directly is not reactive @@ -197,9 +199,7 @@ function Example() {

Error: {query.error.message}

- - {(todo) =>

{todo.title}

} -
+ {(todo) =>

{todo.title}

}
@@ -218,4 +218,4 @@ function App() { - Errors can be caught and reset using SolidJS' native `ErrorBoundary` component. `QueryErrorResetBoundary` is not needed with Solid Query -- Since Property tracking is handled through Solid's fine grained reactivity, options like `notifyOnChangeProps` are not needed \ No newline at end of file +- Since Property tracking is handled through Solid's fine grained reactivity, options like `notifyOnChangeProps` are not needed diff --git a/docs/react/adapters/vue-query.md b/docs/react/adapters/vue-query.md index 9c5d7e64d1..f8a55f6dd0 100644 --- a/docs/react/adapters/vue-query.md +++ b/docs/react/adapters/vue-query.md @@ -14,28 +14,31 @@ This example very briefly illustrates the 3 core concepts of Vue Query: ```vue diff --git a/docs/react/comparison.md b/docs/react/comparison.md index f41463ddc2..78e8d2512c 100644 --- a/docs/react/comparison.md +++ b/docs/react/comparison.md @@ -12,54 +12,54 @@ Feature/Capability Key: - 🔶 Supported and documented, but requires extra user-code to implement - 🛑 Not officially supported or documented. -| | React Query | SWR [_(Website)_][swr] | Apollo Client [_(Website)_][apollo] | RTK-Query [_(Website)_][rtk-query] | React Router [_(Website)_][react-router] | -|----------------------------------------------------| -------------------------- | --------------------------- | ------------------------------------------ | ------------------------------------ | ------------------------------------------------------------------------- | -| Github Repo / Stars | [![][stars-react-query]][gh-react-query] | [![][stars-swr]][gh-swr] | [![][stars-apollo]][gh-apollo] | [![][stars-rtk-query]][gh-rtk-query] | [![][stars-react-router]][gh-react-router] | -| Platform Requirements | React | React | React, GraphQL | Redux | React | -| Their Comparison | | (none) | (none) | [Comparison][rtk-query-comparison] | (none) | -| Supported Query Syntax | Promise, REST, GraphQL | Promise, REST, GraphQL | GraphQL, Any (Reactive Variables) | Promise, REST, GraphQL | Promise, REST, GraphQL | -| Supported Frameworks | React | React | React + Others | Any | React | -| Caching Strategy | Hierarchical Key -> Value | Unique Key -> Value | Normalized Schema | Unique Key -> Value | Nested Route -> value | -| Cache Key Strategy | JSON | JSON | GraphQL Query | JSON | Route Path | +| | React Query | SWR [_(Website)_][swr] | Apollo Client [_(Website)_][apollo] | RTK-Query [_(Website)_][rtk-query] | React Router [_(Website)_][react-router] | +| -------------------------------------------------- | ---------------------------------------- | ---------------------------------------- | ------------------------------------------ | ------------------------------------ | ------------------------------------------------------------------------- | +| Github Repo / Stars | [![][stars-react-query]][gh-react-query] | [![][stars-swr]][gh-swr] | [![][stars-apollo]][gh-apollo] | [![][stars-rtk-query]][gh-rtk-query] | [![][stars-react-router]][gh-react-router] | +| Platform Requirements | React | React | React, GraphQL | Redux | React | +| Their Comparison | | (none) | (none) | [Comparison][rtk-query-comparison] | (none) | +| Supported Query Syntax | Promise, REST, GraphQL | Promise, REST, GraphQL | GraphQL, Any (Reactive Variables) | Promise, REST, GraphQL | Promise, REST, GraphQL | +| Supported Frameworks | React | React | React + Others | Any | React | +| Caching Strategy | Hierarchical Key -> Value | Unique Key -> Value | Normalized Schema | Unique Key -> Value | Nested Route -> value | +| Cache Key Strategy | JSON | JSON | GraphQL Query | JSON | Route Path | | Cache Change Detection | Deep Compare Keys (Stable Serialization) | Deep Compare Keys (Stable Serialization) | Deep Compare Keys (Unstable Serialization) | Key Referential Equality (===) | Route Change | -| Data Change Detection | Deep Comparison + Structural Sharing | Deep Compare (via `stable-hash`) | Deep Compare (Unstable Serialization) | Key Referential Equality (===) | Loader Run | -| Data Memoization | Full Structural Sharing | Identity (===) | Normalized Identity | Identity (===) | Identity (===) | -| Bundle Size | [![][bp-react-query]][bpl-react-query] | [![][bp-swr]][bpl-swr] | [![][bp-apollo]][bpl-apollo] | [![][bp-rtk-query]][bpl-rtk-query] | [![][bp-react-router]][bpl-react-router] + [![][bp-history]][bpl-history] | -| API Definition Location | Component, External Config | Component | GraphQL Schema | External Config | Route Tree Configuration | -| Queries | ✅ | ✅ | ✅ | ✅ | ✅ | -| Cache Persistence | ✅ | ✅ | ✅ | ✅ | 🛑 Active Routes Only 8 | -| Devtools | ✅ | ✅ | ✅ | ✅ | 🛑 | -| Polling/Intervals | ✅ | ✅ | ✅ | ✅ | 🛑 | -| Parallel Queries | ✅ | ✅ | ✅ | ✅ | ✅ | -| Dependent Queries | ✅ | ✅ | ✅ | ✅ | ✅ | -| Paginated Queries | ✅ | ✅ | ✅ | ✅ | ✅ | -| Infinite Queries | ✅ | ✅ | ✅ | 🛑 | 🛑 | -| Bi-directional Infinite Queries | ✅ | 🔶 | 🔶 | 🛑 | 🛑 | -| Infinite Query Refetching | ✅ | ✅ | 🛑 | 🛑 | 🛑 | -| Lagged Query Data1 | ✅ | ✅ | 🛑 | ✅ | ✅ | -| Selectors | ✅ | 🛑 | ✅ | ✅ | N/A | -| Initial Data | ✅ | ✅ | ✅ | ✅ | ✅ | -| Scroll Recovery | ✅ | ✅ | ✅ | ✅ | ✅ | -| Cache Manipulation | ✅ | ✅ | ✅ | ✅ | 🛑 | -| Outdated Query Dismissal | ✅ | ✅ | ✅ | ✅ | ✅ | -| Render Batching & Optimization2 | ✅ | ✅ | 🛑 | ✅ | ✅ | -| Auto Garbage Collection | ✅ | 🛑 | 🛑 | ✅ | N/A | -| Mutation Hooks | ✅ | ✅ | ✅ | ✅ | ✅ | -| Offline Mutation Support | ✅ | 🛑 | 🟡 | 🛑 | 🛑 | -| Prefetching APIs | ✅ | ✅ | ✅ | ✅ | ✅ | -| Query Cancellation | ✅ | 🛑 | 🛑 | 🛑 | ✅ | -| Partial Query Matching3 | ✅ | 🔶 | 🛑 | ✅ | N/A | -| Stale While Revalidate | ✅ | ✅ | ✅ | ✅ | 🛑 | -| Stale Time Configuration | ✅ | 🛑7 | 🛑 | ✅ | 🛑 | -| Pre-usage Query/Mutation Configuration4 | ✅ | 🛑 | 🛑 | ✅ | ✅ | -| Window Focus Refetching | ✅ | ✅ | 🛑 | ✅ | 🛑 | -| Network Status Refetching | ✅ | ✅ | ✅ | ✅ | 🛑 | -| General Cache Dehydration/Rehydration | ✅ | 🛑 | ✅ | ✅ | ✅ | -| Offline Caching | ✅ | 🛑 | ✅ | 🔶 | 🛑 | -| React Suspense | ✅ | ✅ | 🛑 | 🛑 | ✅ | -| Abstracted/Agnostic Core | ✅ | 🛑 | ✅ | ✅ | 🛑 | -| Automatic Refetch after Mutation5 | 🔶 | 🔶 | ✅ | ✅ | ✅ | -| Normalized Caching6 | 🛑 | 🛑 | ✅ | 🛑 | 🛑 | +| Data Change Detection | Deep Comparison + Structural Sharing | Deep Compare (via `stable-hash`) | Deep Compare (Unstable Serialization) | Key Referential Equality (===) | Loader Run | +| Data Memoization | Full Structural Sharing | Identity (===) | Normalized Identity | Identity (===) | Identity (===) | +| Bundle Size | [![][bp-react-query]][bpl-react-query] | [![][bp-swr]][bpl-swr] | [![][bp-apollo]][bpl-apollo] | [![][bp-rtk-query]][bpl-rtk-query] | [![][bp-react-router]][bpl-react-router] + [![][bp-history]][bpl-history] | +| API Definition Location | Component, External Config | Component | GraphQL Schema | External Config | Route Tree Configuration | +| Queries | ✅ | ✅ | ✅ | ✅ | ✅ | +| Cache Persistence | ✅ | ✅ | ✅ | ✅ | 🛑 Active Routes Only 8 | +| Devtools | ✅ | ✅ | ✅ | ✅ | 🛑 | +| Polling/Intervals | ✅ | ✅ | ✅ | ✅ | 🛑 | +| Parallel Queries | ✅ | ✅ | ✅ | ✅ | ✅ | +| Dependent Queries | ✅ | ✅ | ✅ | ✅ | ✅ | +| Paginated Queries | ✅ | ✅ | ✅ | ✅ | ✅ | +| Infinite Queries | ✅ | ✅ | ✅ | 🛑 | 🛑 | +| Bi-directional Infinite Queries | ✅ | 🔶 | 🔶 | 🛑 | 🛑 | +| Infinite Query Refetching | ✅ | ✅ | 🛑 | 🛑 | 🛑 | +| Lagged Query Data1 | ✅ | ✅ | 🛑 | ✅ | ✅ | +| Selectors | ✅ | 🛑 | ✅ | ✅ | N/A | +| Initial Data | ✅ | ✅ | ✅ | ✅ | ✅ | +| Scroll Recovery | ✅ | ✅ | ✅ | ✅ | ✅ | +| Cache Manipulation | ✅ | ✅ | ✅ | ✅ | 🛑 | +| Outdated Query Dismissal | ✅ | ✅ | ✅ | ✅ | ✅ | +| Render Batching & Optimization2 | ✅ | ✅ | 🛑 | ✅ | ✅ | +| Auto Garbage Collection | ✅ | 🛑 | 🛑 | ✅ | N/A | +| Mutation Hooks | ✅ | ✅ | ✅ | ✅ | ✅ | +| Offline Mutation Support | ✅ | 🛑 | 🟡 | 🛑 | 🛑 | +| Prefetching APIs | ✅ | ✅ | ✅ | ✅ | ✅ | +| Query Cancellation | ✅ | 🛑 | 🛑 | 🛑 | ✅ | +| Partial Query Matching3 | ✅ | 🔶 | 🛑 | ✅ | N/A | +| Stale While Revalidate | ✅ | ✅ | ✅ | ✅ | 🛑 | +| Stale Time Configuration | ✅ | 🛑7 | 🛑 | ✅ | 🛑 | +| Pre-usage Query/Mutation Configuration4 | ✅ | 🛑 | 🛑 | ✅ | ✅ | +| Window Focus Refetching | ✅ | ✅ | 🛑 | ✅ | 🛑 | +| Network Status Refetching | ✅ | ✅ | ✅ | ✅ | 🛑 | +| General Cache Dehydration/Rehydration | ✅ | 🛑 | ✅ | ✅ | ✅ | +| Offline Caching | ✅ | 🛑 | ✅ | 🔶 | 🛑 | +| React Suspense | ✅ | ✅ | 🛑 | 🛑 | ✅ | +| Abstracted/Agnostic Core | ✅ | 🛑 | ✅ | ✅ | 🛑 | +| Automatic Refetch after Mutation5 | 🔶 | 🔶 | ✅ | ✅ | ✅ | +| Normalized Caching6 | 🛑 | 🛑 | ✅ | 🛑 | 🛑 | ### Notes @@ -83,19 +83,16 @@ Feature/Capability Key: [bp-react-query]: https://badgen.net/bundlephobia/minzip/react-query?label=💾 [gh-react-query]: https://github.com/tannerlinsley/react-query [stars-react-query]: https://img.shields.io/github/stars/tannerlinsley/react-query?label=%F0%9F%8C%9F - [swr]: https://github.com/vercel/swr [bp-swr]: https://badgen.net/bundlephobia/minzip/swr?label=💾 [gh-swr]: https://github.com/vercel/swr [stars-swr]: https://img.shields.io/github/stars/vercel/swr?label=%F0%9F%8C%9F [bpl-swr]: https://bundlephobia.com/result?p=swr - [apollo]: https://github.com/apollographql/apollo-client [bp-apollo]: https://badgen.net/bundlephobia/minzip/@apollo/client?label=💾 [gh-apollo]: https://github.com/apollographql/apollo-client [stars-apollo]: https://img.shields.io/github/stars/apollographql/apollo-client?label=%F0%9F%8C%9F [bpl-apollo]: https://bundlephobia.com/result?p=@apollo/client - [rtk-query]: https://redux-toolkit.js.org/rtk-query/overview [rtk-query-comparison]: https://redux-toolkit.js.org/rtk-query/comparison [rtk-query-bundle-size]: https://redux-toolkit.js.org/rtk-query/comparison#bundle-size @@ -105,7 +102,6 @@ Feature/Capability Key: [stars-rtk-query]: https://img.shields.io/github/stars/reduxjs/redux-toolkit?label=🌟 [bpl-rtk]: https://bundlephobia.com/result?p=@reduxjs/toolkit [bpl-rtk-query]: https://bundlephobia.com/package/@reduxjs/toolkit - [react-router]: https://github.com/remix-run/react-router [bp-react-router]: https://badgen.net/bundlephobia/minzip/react-router-dom?label=💾 [gh-react-router]: https://github.com/remix-run/react-router diff --git a/docs/react/eslint/eslint-plugin-query.md b/docs/react/eslint/eslint-plugin-query.md index 5b45abfc31..397ca7ad15 100644 --- a/docs/react/eslint/eslint-plugin-query.md +++ b/docs/react/eslint/eslint-plugin-query.md @@ -27,7 +27,6 @@ To enable all of the recommended rules for our plugin, add `plugin:@tanstack/esl } ``` - ### Alternative config Alternatively, add `@tanstack/eslint-plugin-query` to the plugins section of your `.eslintrc` configuration file: diff --git a/docs/react/eslint/exhaustive-deps.md b/docs/react/eslint/exhaustive-deps.md index c56f398008..a4b1bd1214 100644 --- a/docs/react/eslint/exhaustive-deps.md +++ b/docs/react/eslint/exhaustive-deps.md @@ -14,26 +14,25 @@ Examples of **incorrect** code for this rule: /* eslint "@tanstack/query/exhaustive-deps": "error" */ useQuery({ - queryKey: ["todo"], - queryFn: () => api.getTodo(todoId) + queryKey: ['todo'], + queryFn: () => api.getTodo(todoId), }) const todoQueries = { - detail: (id) => ({ queryKey: ["todo"], queryFn: () => api.getTodo(id) }) + detail: (id) => ({ queryKey: ['todo'], queryFn: () => api.getTodo(id) }), } ``` - Examples of **correct** code for this rule: ```tsx useQuery({ - queryKey: ["todo", todoId], - queryFn: () => api.getTodo(todoId) + queryKey: ['todo', todoId], + queryFn: () => api.getTodo(todoId), }) const todoQueries = { - detail: (id) => ({ queryKey: ["todo", id], queryFn: () => api.getTodo(id) }) + detail: (id) => ({ queryKey: ['todo', id], queryFn: () => api.getTodo(id) }), } ``` diff --git a/docs/react/eslint/stable-query-client.md b/docs/react/eslint/stable-query-client.md index 8e7df6a8aa..9af0414c0f 100644 --- a/docs/react/eslint/stable-query-client.md +++ b/docs/react/eslint/stable-query-client.md @@ -24,7 +24,6 @@ function App() { } ``` - Examples of **correct** code for this rule: ```tsx diff --git a/docs/react/guides/disabling-queries.md b/docs/react/guides/disabling-queries.md index 81a0c2ef6d..9de977e0d0 100644 --- a/docs/react/guides/disabling-queries.md +++ b/docs/react/guides/disabling-queries.md @@ -18,12 +18,11 @@ When `enabled` is `false`: ```tsx function Todos() { - const { isLoading, isError, data, error, refetch, isFetching } = - useQuery({ - queryKey: ['todos'], - queryFn: fetchTodoList, - enabled: false, - }) + const { isLoading, isError, data, error, refetch, isFetching } = useQuery({ + queryKey: ['todos'], + queryFn: fetchTodoList, + enabled: false, + }) return (
diff --git a/docs/react/guides/filters.md b/docs/react/guides/filters.md index c55d8f9301..daca12f69f 100644 --- a/docs/react/guides/filters.md +++ b/docs/react/guides/filters.md @@ -52,7 +52,7 @@ A mutation filter is an object with certain conditions to match a mutation with: await queryClient.isMutating() // Filter mutations by mutationKey -await queryClient.isMutating({ mutationKey: ["post"] }) +await queryClient.isMutating({ mutationKey: ['post'] }) // Filter mutations using a predicate function await queryClient.isMutating({ diff --git a/docs/react/guides/infinite-queries.md b/docs/react/guides/infinite-queries.md index c9950f5896..d842293536 100644 --- a/docs/react/guides/infinite-queries.md +++ b/docs/react/guides/infinite-queries.md @@ -87,8 +87,8 @@ function Projects() { {isFetchingNextPage ? 'Loading more...' : hasNextPage - ? 'Load More' - : 'Nothing more to load'} + ? 'Load More' + : 'Nothing more to load'}
{isFetching && !isFetchingNextPage ? 'Fetching...' : null}
@@ -110,9 +110,7 @@ To ensure a seamless querying process without conflicts, it's highly recommended [//]: # 'Example1' ```jsx - !isFetching && fetchNextPage()} -/> + !isFetching && fetchNextPage()} /> ``` [//]: # 'Example1' @@ -197,8 +195,8 @@ queryClient.setQueryData(['projects'], (data) => ({ ```tsx queryClient.setQueryData(['projects'], (data) => ({ - pages: data.pages.slice(0,1), - pageParams: data.pageParams.slice(0,1), + pages: data.pages.slice(0, 1), + pageParams: data.pageParams.slice(0, 1), })) ``` @@ -206,7 +204,6 @@ queryClient.setQueryData(['projects'], (data) => ({ Make sure to always keep the same data structure of pages and pageParams! - [//]: # 'Example8' ## What if I want to limit the number of pages? @@ -235,7 +232,7 @@ useInfiniteQuery({ ## 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. +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' @@ -246,13 +243,13 @@ return useInfiniteQuery({ initialPageParam: 0, getNextPageParam: (lastPage, allPages, lastPageParam) => { if (lastPage.length === 0) { - return undefined + return undefined } return lastPageParam + 1 }, getPreviousPageParam: (firstPage, allPages, firstPageParam) => { if (firstPageParam <= 1) { - return undefined + return undefined } return firstPageParam - 1 }, diff --git a/docs/react/guides/migrating-to-react-query-3.md b/docs/react/guides/migrating-to-react-query-3.md index e251f38c63..aed7f6aa06 100644 --- a/docs/react/guides/migrating-to-react-query-3.md +++ b/docs/react/guides/migrating-to-react-query-3.md @@ -137,7 +137,7 @@ useQuery(['post', id], () => fetchPost(id)) If you still insist on not using inline functions, you can use the newly passed `QueryFunctionContext`: ```tsx -useQuery(['post', id], context => fetchPost(context.queryKey[1])) +useQuery(['post', id], (context) => fetchPost(context.queryKey[1])) ``` ### Infinite Query Page params are now passed via `QueryFunctionContext.pageParam` @@ -183,18 +183,14 @@ The `useInfiniteQuery()` interface has changed to fully support bi-directional i One direction: ```tsx -const { - data, - fetchNextPage, - hasNextPage, - isFetchingNextPage, -} = useInfiniteQuery( - 'projects', - ({ pageParam = 0 }) => fetchProjects(pageParam), - { - getNextPageParam: (lastPage, pages) => lastPage.nextCursor, - } -) +const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + useInfiniteQuery( + 'projects', + ({ pageParam = 0 }) => fetchProjects(pageParam), + { + getNextPageParam: (lastPage, pages) => lastPage.nextCursor, + }, + ) ``` Both directions: @@ -214,29 +210,25 @@ const { { getNextPageParam: (lastPage, pages) => lastPage.nextCursor, getPreviousPageParam: (firstPage, pages) => firstPage.prevCursor, - } + }, ) ``` One direction reversed: ```tsx -const { - data, - fetchNextPage, - hasNextPage, - isFetchingNextPage, -} = useInfiniteQuery( - 'projects', - ({ pageParam = 0 }) => fetchProjects(pageParam), - { - select: data => ({ - pages: [...data.pages].reverse(), - pageParams: [...data.pageParams].reverse(), - }), - getNextPageParam: (lastPage, pages) => lastPage.nextCursor, - } -) +const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + useInfiniteQuery( + 'projects', + ({ pageParam = 0 }) => fetchProjects(pageParam), + { + select: (data) => ({ + pages: [...data.pages].reverse(), + pageParams: [...data.pageParams].reverse(), + }), + getNextPageParam: (lastPage, pages) => lastPage.nextCursor, + }, + ) ``` ### Infinite Query data now contains the array of pages and pageParams used to fetch those pages. @@ -244,7 +236,7 @@ const { This allows for easier manipulation of the data and the page params, like, for example, removing the first page of data along with it's params: ```tsx -queryClient.setQueryData(['projects'], data => ({ +queryClient.setQueryData(['projects'], (data) => ({ pages: data.pages.slice(1), pageParams: data.pageParams.slice(1), })) @@ -277,10 +269,10 @@ The `mutate` function can be used when using callbacks: const { mutate } = useMutation({ mutationFn: addTodo }) mutate('todo', { - onSuccess: data => { + onSuccess: (data) => { console.log(data) }, - onError: error => { + onError: (error) => { console.error(error) }, onSettled: () => { @@ -393,7 +385,7 @@ import { setLogger } from 'react-query' // Log with Sentry setLogger({ - error: error => { + error: (error) => { Sentry.captureException(error) }, }) @@ -418,13 +410,14 @@ setConsole({ In version 3 **this is done automatically when React Query is used in React Native**. - ### Typescript + #### `QueryStatus` has been changed from an [enum](https://www.typescriptlang.org/docs/handbook/enums.html#string-enums) to a [union type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) So, if you were checking the status property of a query or mutation against a QueryStatus enum property you will have to check it now against the string literal the enum previously held for each property. Therefore you have to change the enum properties to their equivalent string literal, like this: + - `QueryStatus.Idle` -> `'idle'` - `QueryStatus.Loading` -> `'loading'` - `QueryStatus.Error` -> `'error'` @@ -460,7 +453,7 @@ import { useQuery } from 'react-query' function User() { const { data } = useQuery(['user'], fetchUser, { - select: user => user.username, + select: (user) => user.username, }) return
Username: {data}
} @@ -512,7 +505,7 @@ A `QueryObserver` can be used to create and/or watch a query: ```tsx const observer = new QueryObserver(queryClient, { queryKey: 'posts' }) -const unsubscribe = observer.subscribe(result => { +const unsubscribe = observer.subscribe((result) => { console.log(result) unsubscribe() }) @@ -530,7 +523,7 @@ const observer = new InfiniteQueryObserver(queryClient, { getPreviousPageParam: (firstPage, allPages) => firstPage.prevCursor, }) -const unsubscribe = observer.subscribe(result => { +const unsubscribe = observer.subscribe((result) => { console.log(result) unsubscribe() }) @@ -546,7 +539,7 @@ const observer = new QueriesObserver(queryClient, [ { queryKey: ['post', 2], queryFn: fetchPost }, ]) -const unsubscribe = observer.subscribe(result => { +const unsubscribe = observer.subscribe((result) => { console.log(result) unsubscribe() }) diff --git a/docs/react/guides/migrating-to-react-query-4.md b/docs/react/guides/migrating-to-react-query-4.md index 467ebd4712..38779170af 100644 --- a/docs/react/guides/migrating-to-react-query-4.md +++ b/docs/react/guides/migrating-to-react-query-4.md @@ -139,7 +139,9 @@ In order to make bailing out of updates possible by returning `undefined`, we ha Further, it is an easy bug to produce `Promise` by adding logging in the queryFn: ```tsx -useQuery(['key'], () => axios.get(url).then(result => console.log(result.data))) +useQuery(['key'], () => + axios.get(url).then((result) => console.log(result.data)), +) ``` This is now disallowed on type level; at runtime, `undefined` will be transformed to a _failed Promise_, which means you will get an `error`, which will also be logged to the console in development mode. @@ -420,8 +422,8 @@ React Query defaults to "tracking" query properties, which should give you a nic When using the [functional updater form of setQueryData](../reference/QueryClient#queryclientsetquerydata), you can now bail out of the update by returning `undefined`. This is helpful if `undefined` is given to you as `previousValue`, which means that currently, no cached entry exists and you don't want to / cannot create one, like in the example of toggling a todo: ```tsx -queryClient.setQueryData(['todo', id], previousTodo => - previousTodo ? { ...previousTodo, done: true } : undefined +queryClient.setQueryData(['todo', id], (previousTodo) => + previousTodo ? { ...previousTodo, done: true } : undefined, ) ``` diff --git a/docs/react/guides/mutations.md b/docs/react/guides/mutations.md index 2f6526464e..16b5e64253 100644 --- a/docs/react/guides/mutations.md +++ b/docs/react/guides/mutations.md @@ -231,7 +231,8 @@ useMutation({ }, }) -[('Todo 1', 'Todo 2', 'Todo 3')].forEach((todo) => { +const todos = ['Todo 1', 'Todo 2', 'Todo 3'] +todos.forEach((todo) => { mutate(todo, { onSuccess: (data, error, variables, context) => { // Will execute only once, for the last mutation (Todo 3), diff --git a/docs/react/guides/optimistic-updates.md b/docs/react/guides/optimistic-updates.md index 45eb734236..f733d305b1 100644 --- a/docs/react/guides/optimistic-updates.md +++ b/docs/react/guides/optimistic-updates.md @@ -10,33 +10,33 @@ React Query provides two ways to optimistically update your UI before a mutation This is the simpler variant, as it doesn't interact with the cache directly. [//]: # 'ExampleUI1' + ```tsx - const { isPending, submittedAt, variables, mutate, isError } = 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 } = 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'] }) + }, +}) ``` + [//]: # '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`: [//]: # 'ExampleUI2' + ```tsx
    {todoQuery.items.map((todo) => (
  • {todo.text}
  • ))} - {isPending && ( -
  • - {variables} -
  • - )} + {isPending &&
  • {variables}
  • }
``` + [//]: # 'ExampleUI2' We're rendering a temporary item with a different `opacity` as long as the mutation is pending. Once it completes, the item will automatically no longer be rendered. Given that the refetch succeeded, we should see the item as a "normal item" in our list. @@ -44,18 +44,18 @@ We're rendering a temporary item with a different `opacity` as long as the mutat If the mutation errors, the item will also disappear. But we could continue to show it, if we want, by checking for the `isError` state of the mutation. `variables` are _not_ cleared when the mutation errors, so we can still access them, maybe even show a retry button: [//]: # 'ExampleUI3' + ```tsx -{isError && ( -
  • - {variables} - -
  • -)} +{ + isError && ( +
  • + {variables} + +
  • + ) +} ``` + [//]: # 'ExampleUI3' ### If the mutation and the query don't live in the same component @@ -63,20 +63,22 @@ If the mutation errors, the item will also disappear. But we could continue to s This approach works very well if the mutation and the query live in the same component, However, you also get access to all mutations in other components via the dedicated `useMutationState` hook. It is best combined with a `mutationKey`: [//]: # 'ExampleUI4' + ```tsx // somewhere in your app const { mutate } = useMutation({ mutationFn: (newTodo: string) => axios.post('/api/data', { text: newTodo }), onSettled: () => queryClient.invalidateQueries({ queryKey: ['todos'] }), - mutationKey: ['addTodo'] + mutationKey: ['addTodo'], }) // access variables somewhere else const variables = useMutationState({ filters: { mutationKey: ['addTodo'], status: 'pending' }, - select: (mutation) => mutation.state.variables + select: (mutation) => mutation.state.variables, }) ``` + [//]: # 'ExampleUI4' `variables` will be an `Array`, because there might be multiple mutations running at the same time. If we need a unique key for the items, we can also select `mutation.state.submittedAt`. This will even make displaying concurrent optimistic updates a breeze. @@ -122,11 +124,13 @@ useMutation({ }, }) ``` + [//]: # 'Example' ### Updating a single todo [//]: # 'Example2' + ```tsx useMutation({ mutationFn: updateTodo, @@ -158,11 +162,13 @@ useMutation({ }, }) ``` + [//]: # 'Example2' You can also use the `onSettled` function in place of the separate `onError` and `onSuccess` handlers if you wish: [//]: # 'Example3' + ```tsx useMutation({ mutationFn: updateTodo, @@ -174,8 +180,8 @@ useMutation({ }, }) ``` -[//]: # 'Example3' +[//]: # 'Example3' ## When to use what diff --git a/docs/react/guides/paginated-queries.md b/docs/react/guides/paginated-queries.md index c0138ad141..7ddb8b348a 100644 --- a/docs/react/guides/paginated-queries.md +++ b/docs/react/guides/paginated-queries.md @@ -6,12 +6,14 @@ title: Paginated / Lagged Queries Rendering paginated data is a very common UI pattern and in TanStack Query, it "just works" by including the page information in the query key: [//]: # 'Example' + ```tsx const result = useQuery({ queryKey: ['projects', page], - queryFn: fetchProjects + queryFn: fetchProjects, }) ``` + [//]: # 'Example' However, if you run this simple example, you might notice something strange: @@ -29,27 +31,23 @@ Consider the following example where we would ideally want to increment a pageIn - `isPlaceholderData` is made available to know what data the query is currently providing you [//]: # 'Example2' + ```tsx -import { keepPreviousData, useQuery } from "@tanstack/react-query"; -import React from "react"; +import { keepPreviousData, useQuery } from '@tanstack/react-query' +import React from 'react' function Todos() { const [page, setPage] = React.useState(0) - const fetchProjects = (page = 0) => fetch('/api/projects?page=' + page).then((res) => res.json()) - - const { - isPending, - isError, - error, - data, - isFetching, - isPlaceholderData, - } = useQuery({ - queryKey: ['projects', page], - queryFn: () => fetchProjects(page), - placeholderData: keepPreviousData, - }) + const fetchProjects = (page = 0) => + fetch('/api/projects?page=' + page).then((res) => res.json()) + + const { isPending, isError, error, data, isFetching, isPlaceholderData } = + useQuery({ + queryKey: ['projects', page], + queryFn: () => fetchProjects(page), + placeholderData: keepPreviousData, + }) return (
    @@ -59,14 +57,14 @@ function Todos() {
    Error: {error.message}
    ) : (
    - {data.projects.map(project => ( + {data.projects.map((project) => (

    {project.name}

    ))}
    )} Current Page: {page + 1}