TanStack Query (formerly React Query) is a library for managing asynchronous server state.
Main solutions:
- Server data acquisition.
- Request state management.
- Data caching and reuse.
- Automatically re-request.
- Request deduplication.
- Server data modification.
- Cache updates and invalidations.
- Pagination and infinite scrolling.
For Django + DRF + Orval + Axios + React projects:
Django / DRF
↑
Axios
↑
Orval Generated API Function
↑
TanStack Query
↑
Orval Generated Hooks
↑
React Components
Need to distinguish:
| Tools | Core responsibilities |
|---|---|
| Axios | Send HTTP request |
| Orval | Generate API functions and Hooks from OpenAPI |
| TanStack Query | Manage server state and cache |
| React | UI rendering and component state |
TanStack Query is not responsible for actually sending the HTTP request, but calls Axios and other request functions through queryFn, mutationFn.
1. QueryClient and QueryClientProvider
QueryClient is the core object of TanStack Query, used to manage Query Cache, Mutation Cache and global default configuration.
Usually created at the application entrance:
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
gcTime: 5 * 60_000,
retry: 2,
refetchOnWindowFocus: true,
},
},
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<MainApp />
</QueryClientProvider>
);
}
Use within the component:
const queryClient = useQueryClient();
You can get the QueryClient currently managed by Provider.
Common methods of QueryClient
| method | function |
|---|---|
invalidateQueries() | Tag cache invalidation |
setQueryData() | Directly modify the specified cache |
getQueryData() | Synchronous read cache |
removeQueries() | Delete matching cache |
resetQueries() | Reset Query |
refetchQueries() | Retrieve matching Query |
cancelQueries() | Cancel matching request |
fetchQuery() | Get data, request if necessary |
prefetchQuery() | Get and cache data in advance |
clear() | Clear Query and Mutation caches |
Generally, keep a stable QueryClient instance during the application life cycle and do not recreate it every time the component is rendered.
2. useQuery: Read server data
useQuery() is mainly used to obtain and cache server data.
const result = useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
enabled: userId != null,
staleTime: 60_000,
gcTime: 300_000,
});
Core parameters:
| parameters | function |
|---|---|
queryKey | Query and cache unique identifiers |
queryFn | Asynchronous function to get data |
enabled | Whether to automatically execute queries |
staleTime | How long data remains fresh |
gcTime | Unused cache retention time |
retry | Retry strategy after failed request |
select | Convert the data returned by Hook |
placeholderData | Temporarily displayed placeholder data |
initialData | Set initial cache data |
Query status
const {
data,
error,
isPending,
isLoading,
isFetching,
isError,
isSuccess,
refetch,
} = useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
});
| Status | meaning |
|---|---|
isPending | No data has been successfully obtained yet |
isLoading | The first data acquisition is in progress |
isFetching | Currently requesting, including background refresh |
isRefetching | Re-fetching, not initial loading |
isError | The current query status is error |
isSuccess | The current query status is successful |
isStale | The data has expired or been actively invalidated |
Special attention:
isLoading = isPending && isFetching;
Therefore, when enabled: false and there is no data, the following may occur:
isPending = true
isLoading = false
isFetching = false
This is not a contradiction because Query has no data yet, but no request is being made.
Typical components
function UserProfile({ userId }: { userId: number }) {
const { data, isPending, isError, error, isFetching } = useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId),
});
if (isPending) return <div>Loading...</div>;
if (isError) return <div>{error.message}</div>;
return (
<div>
<h2>{data.username}</h2>
{isFetching && <span>Refreshing...</span>}
</div>
);
}
In the case where old data is already available, the component can still display the cached content even if the background re-requests it.
3. Query Key: Cache ID
TanStack Query Use the array form queryKey to distinguish between different queries.
["user", 1][("user", 2)][("pois", { city: 1, page: 1 })][
("pois", { city: 1, page: 2 })
];
Different Query Key correspond to different cache entries.
Query Key level matching
For example:
["user"][("user", "me")][("user", "profile")][("user", "profile", 1)];
Execution:
queryClient.invalidateQueries({
queryKey: ["user"],
});
Prefix matching is used by default, so it matches all Query above.
If you only need an exact match:
queryClient.invalidateQueries({
queryKey: ["user"],
exact: true,
});
Query Key Design Principles
Parameters on which query results depend usually need to be included in Query Key.
For example:
useQuery({
queryKey: ["pois", cityId, page],
queryFn: () => getPois(cityId, page),
});
If Query Key does not contain cityId, data from different cities may mistakenly share the same cache.
For Orval generating Hook, the automatically generated Query Key factory is usually preferred.
4. staleTime and gcTime
These are the two most important cache parameters of TanStack Query.
staleTime: fresh time
staleTime: 60_000;
Indicates that the data is considered fresh for typically 60 seconds after a successful acquisition or update.
There are two main states of data:
Fresh
↓ staleTime Expiration
Stale
- Fresh: The data is considered fresh and is usually not re-requested due to default trigger conditions such as component mounting and window focusing.
- Stale: The data has expired and can be automatically re-requested at the appropriate time.
Stale does not mean that the cache has been deleted, nor does it mean that the request must be sent immediately after expiration.
gcTime: Garbage collection time
gcTime: 300_000;
Indicates that the unused cache typically remains for 5 minutes after Query no longer has active subscribers.
Component Mounted
↓
Active Query
↓
Component Unmounted
↓
Inactive Query
↓
gcTime
↓
Cache Removed
Resubscribing to a Query that is still in the cache will re-enter the Active state.
The difference between the two
| Contrast | staleTime | gcTime |
|---|---|---|
| Control content | Is the data fresh? | When is the cache deleted? |
| Main start time | Data successfully obtained or updated | Query becomes Inactive |
| Expiration result | The data becomes Stale | Cache can be reclaimed |
| Whether to request immediately | No | No |
| Whether to delete cache | No | Yes |
Example
Assumptions:
staleTime: 60_000,
gcTime: 300_000,
If the query has expired, but the component is always subscribed to it, the cache will not be deleted just because gcTime expires.
If the component is unmounted and then remounted 2 minutes later:
- The cache still exists.
- The data may have been Stale.
- Old caches can be displayed immediately and refreshed in the background by default.
If more than 5 minutes have passed since uninstalling and there are no other subscribers or retention mechanisms:
- The original Query may be garbage collected.
- Entering the page again usually requires a new request.
Default value
TanStack Query v5 Common default configuration:
staleTime: 0;
gcTime: 5 * 60 * 1000;
means:
- The data defaults to Stale immediately.
- Inactive Query is retained for 5 minutes by default.
This explains why when staleTime is not configured, repeated page entry may continuously trigger requests.
Infinity and static
staleTime: Infinity;
Data does not automatically become stale due to the passage of time, but can still be invalidated by invalidateQueries().
staleTime: "static";
It has stricter static data semantics, and ordinary invalidation operations cannot trigger its invalidation.
Suitable for data that really does not change while the application is running.
5. Active and Inactive Query
Active
There is currently Query Observer being subscribed and enabled.
For example:
function Profile() {
const result = useUserProfileRetrieve();
// ...
}
When Hook subscribes to and enables Query, it is usually in the Active state.
Inactive
There are currently no active Query Observers.
For example, when the user leaves the Profile page, the related component is uninstalled, and no other components subscribe to it.
But Query may still exist in the cache.
Query life cycle
useQuery Mount
↓
Active
↓
Fetch / Cache
↓
Component Unmount
↓
Inactive
↓
gcTime Expires
↓
Garbage Collection
Note: active/inactive describes the subscription status; fresh/stale describes the data freshness. They are two independent dimensions.
6. Automatic re-request (Refetch)
TanStack Query will re-request Stale data at certain times by default.
| Trigger condition | Configuration |
|---|---|
| Component remount | refetchOnMount |
| Browser window refocused | refetchOnWindowFocus |
| network reconnect | refetchOnReconnect |
| Regular polling | refetchInterval |
| Manually re-request | refetch() |
| cache invalidation | invalidateQueries() |
For example:
useQuery({
queryKey: ["user", "profile"],
queryFn: getProfile,
staleTime: 60_000,
refetchOnWindowFocus: false,
refetchOnReconnect: true,
refetchOnMount: true,
});
refetchInterval
refetchInterval: 30_000;
Indicates an attempt to reacquire data every approximately 30 seconds.
It should be noted that scheduled polling is not completely restricted by staleTime and is an independent automatic request mechanism.
enabled
useQuery({
queryKey: ["user", userId],
queryFn: () => getUser(userId!),
enabled: userId != null,
});
It means that the request will be automatically sent only after obtaining a valid userId.
The disabled state also affects auto-invalidation and re-fetching behavior, so don't interpret it as simply delaying a request.
7. useMutation: Modify server status
useMutation() is typically used to create, update, or delete data.
const mutation = useMutation({
mutationFn: updateProfile,
});
Execution:
mutation.mutate({
username: "Tom",
});
Mutation status
| Status | meaning |
|---|---|
isIdle | Not yet executed |
isPending | Executing |
isSuccess | success |
isError | failed |
data | Data returned successfully |
error | error object |
mutate and mutateAsync
mutation.mutate(data);
Trigger Mutation and process the result through callback.
await mutation.mutateAsync(data);
Returns Promise, which can be combined with try/catch to process the result.
try {
await mutation.mutateAsync({
username: "Tom",
});
} catch (error) {
console.error(error);
}
Mutation does not automatically maintain long-term server data cache in Query Key units by default like Query.
8. Mutation Lifecycle
Mutation provides four important lifecycle callbacks.
| callback | Calling time |
|---|---|
onMutate | mutationFn before execution |
onSuccess | Request successful |
onError | Request failed |
onSettled | After the request succeeds or fails |
onSuccess
const mutation = useMutation({
mutationFn: updateProfile,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["user", "profile"],
});
},
});
Suitable for flushing the relevant cache after a successful write.
onError
onError: (error) => {
console.error(error);
};
Used to handle failures, display errors, or rollback optimistic updates.
onSettled
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: ["user", "profile"],
});
};
Regardless of success or failure, it is ultimately executed.
If the callback returns Promise, TanStack Query will wait for it to complete before ending the corresponding Mutation life cycle processing.
Note: If there is no need to refresh the data when it fails, there is no need to always request the server in onSettled.
9. Optimistic update (Optimistic Update)
Optimistic updates refer to:
Update the client interface or cache before the server confirms success.
For example, a user changes their nickname:
User Changes Name
↓
Update UI Immediately
↓
Send PATCH Request
│
├── Success → Keep / Synchronize
│
└── Error → Rollback
Complete example
const queryClient = useQueryClient();
const profileKey = ["user", "profile"] as const;
const mutation = useMutation({
mutationFn: (newName: string) => updateProfile({ username: newName }),
onMutate: async (newName) => {
await queryClient.cancelQueries({
queryKey: profileKey,
});
const oldProfile = queryClient.getQueryData<{
username: string;
}>(profileKey);
queryClient.setQueryData(profileKey, (old: typeof oldProfile) =>
old ? { ...old, username: newName } : old,
);
return { oldProfile };
},
onError: (_error, _variables, onMutateResult) => {
if (onMutateResult?.oldProfile !== undefined) {
queryClient.setQueryData(profileKey, onMutateResult.oldProfile);
}
},
onSettled: () => {
return queryClient.invalidateQueries({
queryKey: profileKey,
});
},
});
Three key steps:
cancelQueries(): Avoid overwriting optimistic updates after old requests return.setQueryData(): Immediately modify the client cache.onError(): Restore previous data when request fails.
The return value of onMutate() can be passed to subsequent callbacks to save old cached data.
Note: Complex concurrency Mutation may cause update order conflicts, so optimistic updates may not be suitable for all businesses.
In addition, in TanStack Query v5, onMutate needs to be configured in useMutation(); the call-level callbacks of mutate() mainly include onSuccess, onError and onSettled.
10. invalidateQueries: cache invalidation
queryClient.invalidateQueries({
queryKey: ["user", "profile"],
});
Function:
- Matches the specified Query.
- Label them Stale.
- By default, background reacquisition is triggered for Active Query that meets the conditions.
Server Data Updated
↓
invalidateQueries()
↓
Cache Marked Stale
│
├── Active → Refetch
│
└── Inactive → Wait for Future Usage
Failure scope
queryClient.invalidateQueries({
queryKey: ["user"],
});
Can match multiple Query prefixed with ["user"].
Exact match:
queryClient.invalidateQueries({
queryKey: ["user", "profile"],
exact: true,
});
refetchType
queryClient.invalidateQueries({
queryKey: ["user"],
refetchType: "all",
});
Common options:
active: Default, re-request matching active Query.inactive: Request matching inactive Query.all: Matching active and inactive Query are both re-requested.none: Only marks the failure and does not trigger a re-request.
There are additional restrictions for disabled Query and Query using special static configurations.
11. setQueryData: Directly modify the cache
setQueryData() does not send HTTP request, but directly modifies Query Cache.
queryClient.setQueryData(["user", "profile"], {
username: "Bob",
avatar: "avatar.jpg",
});
If the Query does not exist, a cache entry may be created when valid data is provided.
Mutation is written to the cache after success
const mutation = useMutation({
mutationFn: updateProfile,
onSuccess: (updatedProfile) => {
queryClient.setQueryData(["user", "profile"], updatedProfile);
},
});
This can reduce additional GET requests if the server returns complete, trustworthy, up-to-date data.
Functional update
queryClient.setQueryData(
["user", "profile"],
(old: UserProfile | undefined) => {
if (!old) return old;
return {
...old,
username: "Bob",
};
},
);
Do not directly modify old objects in the cache:
old.username = "Bob";
return old;
Immutable updates should be used to create new objects.
12. getQueryData, removeQueries and resetQueries
getQueryData
Synchronously read an existing cache:
const profile = queryClient.getQueryData<UserProfile>(["user", "profile"]);
HTTP requests will not be actively sent.
removeQueries
queryClient.removeQueries({
queryKey: ["user", "profile"],
});
Delete matching cache records.
It is suitable for cleaning up data that is no longer needed, but for Query that still has component subscriptions, the status of the component after deletion and subsequent requests need to be considered.
resetQueries
queryClient.resetQueries({
queryKey: ["user", "profile"],
});
Restore the match Query to its original state.
If Query is configured with initialData, it is possible to restore this initial data.
Active Query will usually be re-requested as well.
clear
queryClient.clear();
Clean up Query Cache and Mutation Cache.
It is suitable for scenarios where the client server status needs to be cleared as a whole, but indiscriminate clearing of still needed data should be avoided.
Contrast
| method | Delete cache | Request |
|---|---|---|
invalidateQueries | No | By default, Active Query that meets the conditions are refreshed. |
setQueryData | No | No |
removeQueries | Yes | Do not automatically initiate refresh |
resetQueries | reset state | Active Query usually refreshes |
clear | Clear cache | Do not automatically initiate refresh |
13. Automatic caching and request deduplication
If two components request the same data at the same time:
function Header() {
useUserProfileRetrieve();
}
function ProfilePage() {
useUserProfileRetrieve();
}
As long as they use the same QueryClient and Query Key:
Component A ──┐
├── Shared Query Cache
Component B ──┘
↓
Single Query
↓
HTTP Request
TanStack Query usually shares the same in-flight request rather than sending two copies of the same request.
But note:
- Different Query Key will not automatically remove duplicates.
- Whether to request again after the request is completed depends on the freshness and configuration.
- Normal memory caches are not shared between two different QueryClients.
14. Retry: Retry after failure
TanStack Query will retry failed Query in the client environment by default, and the common default number is 3 times.
retry: 3;
Can be turned off:
retry: false;
It can also be determined based on the error type:
retry: (failureCount, error) => {
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
if (status === 400 || status === 401 || status === 403) {
return false;
}
return failureCount < 2;
};
General principles:
- Temporary network failure: you can try again.
- The server is temporarily unavailable: consider trying again.
- Parameter error: usually no need to retry.
- Authentication or permissions error: Retrying is usually not required.
useMutation does not automatically retry by default, so the retry policy of Query should not be used indiscriminately for write operations.
Particular caution is required when automatically retrying non-idempotent write requests.
15. Query of select
select is used to convert the data returned by Hook.
For example, the backend returns:
{
id: 1,
username: "Alice",
email: "alice@example.com"
}
You can extract just the username:
const { data: username } = useQuery({
queryKey: ["user", "profile"],
queryFn: getProfile,
select: (data) => data.username,
});
Note:
selectaffects the data returned by this Query Observer.- The original data in Query Cache will not be directly replaced with the selected value.
Suitable for situations where multiple components read different fields from the same server data.
16. initialData and placeholderData
initialData
useQuery({
queryKey: ["user", 1],
queryFn: getUser,
initialData: {
id: 1,
username: "Alice",
},
});
initialData will put the initial value into Query Cache.
Suitable for situations where trustworthy initial data already exists.
placeholderData
useQuery({
queryKey: ["user", 1],
queryFn: getUser,
placeholderData: {
id: 1,
username: "Loading...",
},
});
placeholderData is only used for temporary display and will not be written to Query Cache as a formal query result.
difference
| Features | initialData | placeholderData |
|---|---|---|
| Write Query Cache | Yes | No |
| as real initial data | Yes | No |
| Suitable for temporary UI placeholder | Generally not a priority | Yes |
| Does it affect freshness judgment? | Yes | Not cached as real data |
17. Paging and keepPreviousData
Paging API example:
GET /api/v1/pois?page=1
GET /api/v1/pois?page=2
If old data is discarded immediately when switching paging, the interface may appear briefly blank.
TanStack Query v5 can be used with placeholderData with keepPreviousData:
import { keepPreviousData, useQuery } from "@tanstack/react-query";
const result = useQuery({
queryKey: ["pois", page],
queryFn: () => getPois(page),
placeholderData: keepPreviousData,
});
Function:
Page 1 Data
↓
User Switches to Page 2
↓
Keep Showing Page 1 Temporarily
↓
Fetch Page 2
↓
Display Page 2
You can use isPlaceholderData to determine whether the currently displayed data is placeholder data.
This is great for normal list pagination.
18. useInfiniteQuery: infinite scroll
For scenarios such as infinite scrolling, loading more, etc., you can use useInfiniteQuery.
const query = useInfiniteQuery({
queryKey: ["pois", "infinite"],
queryFn: ({ pageParam }) => getPois({ cursor: pageParam }),
initialPageParam: null as string | null,
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
});
Core concepts:
| Properties | function |
|---|---|
pages | List of loaded pages |
pageParams | Pagination parameters used for each page |
fetchNextPage() | Get next page |
hasNextPage | Is there a next page? |
isFetchingNextPage | Are you getting the next page? |
getNextPageParam | Calculate next page parameters |
getNextPageParam returns null or undefined usually means there is no next page.
For Map POI large data lists or search results, infinite scrolling may be considered, but is not necessarily required for all interfaces.
19. AbortSignal: Request to cancel
TanStack Query will provide AbortSignal to Query Function.
useQuery({
queryKey: ["user", userId],
queryFn: ({ signal }) =>
axios.get(`/api/v1/users/${userId}`, {
signal,
}),
});
If the request is canceled, Axios can consume the Signal to terminate the client HTTP request.
Common scenarios:
- Users can quickly switch query conditions.
- The page leaves.
- Cancel manually.
- Old requests are no longer needed.
Note:
- The presence of
signaldoes not mean that every inactive request will be automatically aborted. - The request function needs to actually use Signal.
- Aborting a client request does not guarantee that the backend has stopped executing.
The API function generated by Orval can usually pass Signal in conjunction with TanStack Query.
20. Prefetch: Get data in advance
prefetchQuery() can fill the cache in advance.
For example, when the user is about to open the details page:
await queryClient.prefetchQuery({
queryKey: ["poi", poiId],
queryFn: () => getPoi(poiId),
staleTime: 60_000,
});
Then the component executes:
useQuery({
queryKey: ["poi", poiId],
queryFn: () => getPoi(poiId),
staleTime: 60_000,
});
If the cache is still fresh, you can usually use the data fetched in advance without having to request it again.
Common triggering methods:
- Mouse Hover.
- The list item enters the visible area.
- Before routing navigation.
- Page preloading.
Note: Preloading also consumes network resources, and data for all pages should not be preloaded indiscriminately.
21. Structural Sharing
TanStack Query defaults to reusing unchanged references in old and new data as much as possible.
For example, some fields returned by the server have not changed:
{
user: {
id: 1,
username: "Alice"
},
total: 10
}
When the new response is the same as the old data, TanStack Query can reuse related object references to reduce unnecessary reference changes.
This is called Structural Sharing.
It mainly works with JSON compatible data structures.
For data containing instances of special classes or types other than JSON, restrictions need to be considered.
Usually just keep the default configuration.
22. Query Cache and Mutation Cache
There are two main cache management structures inside TanStack Query.
Query Cache
Manage data and status for read operations:
QueryCache
├── ["user", "profile"]
├── ["pois", { city: 1 }]
└── ["itinerary", 123]
Responsible for:
- Data reuse.
- Freshness management.
- Query life cycle.
- Background re-request.
- Cache cleaning.
Mutation Cache
Manage the execution status and history of Mutation.
MutationCache
├── Update Profile
├── Delete POI
└── Create Itinerary
It will not automatically synchronize the return result of each Mutation to the related Query Cache.
This is why after Mutation is successful, it is often necessary to actively call:
queryClient.invalidateQueries(...)
or:
queryClient.setQueryData(...)
23. The relationship between global configuration and Orval
In projects that use Orval, there is usually no need to reimplement the generated Query Hook yourself.
For example Orval has been generated:
useUserProfileRetrieve();
You can use it directly:
const { data, isPending } = useUserProfileRetrieve({
query: {
staleTime: 60_000,
enabled: true,
},
});
The specific call signature is subject to the Orval generated code.
If unified configuration is required:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000,
gcTime: 300_000,
retry: 2,
refetchOnWindowFocus: false,
},
mutations: {
retry: 0,
},
},
});
Generally recommended in projects:
- Global settings are suitable for the default behavior of most interfaces.
- Special interfaces are covered by Hook Options.
- Use the Orval Query Key factory for failover operations.
- Do not modify Generated Code directly.
24. FAQ
Q1: After using TanStack Query, do I still need React State?
need.
TanStack Query mainly manages server status.
React useState is still used for:
- Modal is open or not.
- Form input.
- Current Tab.
- Local UI interaction.
- User-edited data that has not yet been submitted.
It is not recommended to put all React State into TanStack Query.
Q2: Will the request be sent immediately after staleTime expires?
Usually not.
It just makes the data Stale. It is then reacquired when the component is mounted, the window is focused, the network is reconnected, or other configured timings occur.
Q3: Will gcTime be re-requested when it expires?
Won't.
It is responsible for garbage collection and not re-fetching.
If the cache has been deleted, subsequent resubscriptions to the Query will usually require a new request.
Q4: If two components call the same Hook, will two requests be sent?
For simultaneous requests for the same QueryClient and the same Query Key, the requests are usually reused.
But if the request has ended and the cache is at Stale, subsequent mounts may still trigger a refetch.
Q5: Will the GET cache be automatically updated upon success of Mutation?
Usually not.
Cache consistency needs to be maintained manually through invalidateQueries() or setQueryData(), unless the project is additionally configured with an automatic invalidation policy.
Q6: When to use invalidateQueries and when to use setQueryData?
- The latest data needs to be reconfirmed from the server:
invalidateQueries(). - Mutation has returned complete latest data:
setQueryData()is available. - Multiple related queries need to be updated simultaneously: a comprehensive caching strategy needs to be designed.
Summary
The core concept of TanStack Query can be divided into four parts:
| Classification | core concepts |
|---|---|
| Data reading | useQuery, Query Key, QueryFn, Query status |
| caching mechanism | staleTime、gcTime、Active/Inactive、Structural Sharing |
| Data modification | useMutation、Lifecycle、Optimistic Update |
| cache control | invalidateQueries、setQueryData、resetQueries、Prefetch |
Core data process:
React Component
↓
useQuery / useMutation
↓
QueryClient
│
├── QueryCache
└── MutationCache
↓
queryFn / mutationFn
↓
Axios
↓
Django API
What you need to master most:
- Query Key How to uniquely identify a cache.
- The difference between
staleTimeandgcTime. - The relationship between Active / Inactive and Fresh / Stale.
- Query How to automatically reacquire data.
- Mutation How to update or invalidate cache after success.
onMutate,onErrorand optimistic updates.- How to reuse QueryClient from Hooks generated by Orval.