← ProgrammingBack to top

TanStack Query: React server state management

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:

ToolsCore responsibilities
AxiosSend HTTP request
OrvalGenerate API functions and Hooks from OpenAPI
TanStack QueryManage server state and cache
ReactUI 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

methodfunction
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:

parametersfunction
queryKeyQuery and cache unique identifiers
queryFnAsynchronous function to get data
enabledWhether to automatically execute queries
staleTimeHow long data remains fresh
gcTimeUnused cache retention time
retryRetry strategy after failed request
selectConvert the data returned by Hook
placeholderDataTemporarily displayed placeholder data
initialDataSet initial cache data

Query status

const {
  data,
  error,
  isPending,
  isLoading,
  isFetching,
  isError,
  isSuccess,
  refetch,
} = useQuery({
  queryKey: ["user", userId],
  queryFn: () => getUser(userId),
});
Statusmeaning
isPendingNo data has been successfully obtained yet
isLoadingThe first data acquisition is in progress
isFetchingCurrently requesting, including background refresh
isRefetchingRe-fetching, not initial loading
isErrorThe current query status is error
isSuccessThe current query status is successful
isStaleThe 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

ContraststaleTimegcTime
Control contentIs the data fresh?When is the cache deleted?
Main start timeData successfully obtained or updatedQuery becomes Inactive
Expiration resultThe data becomes StaleCache can be reclaimed
Whether to request immediatelyNoNo
Whether to delete cacheNoYes

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 conditionConfiguration
Component remountrefetchOnMount
Browser window refocusedrefetchOnWindowFocus
network reconnectrefetchOnReconnect
Regular pollingrefetchInterval
Manually re-requestrefetch()
cache invalidationinvalidateQueries()

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

Statusmeaning
isIdleNot yet executed
isPendingExecuting
isSuccesssuccess
isErrorfailed
dataData returned successfully
errorerror 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.

callbackCalling time
onMutatemutationFn before execution
onSuccessRequest successful
onErrorRequest failed
onSettledAfter 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:

  1. cancelQueries(): Avoid overwriting optimistic updates after old requests return.
  2. setQueryData(): Immediately modify the client cache.
  3. 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:

  1. Matches the specified Query.
  2. Label them Stale.
  3. 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

methodDelete cacheRequest
invalidateQueriesNoBy default, Active Query that meets the conditions are refreshed.
setQueryDataNoNo
removeQueriesYesDo not automatically initiate refresh
resetQueriesreset stateActive Query usually refreshes
clearClear cacheDo 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:

  • select affects 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

FeaturesinitialDataplaceholderData
Write Query CacheYesNo
as real initial dataYesNo
Suitable for temporary UI placeholderGenerally not a priorityYes
Does it affect freshness judgment?YesNot 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:

Propertiesfunction
pagesList of loaded pages
pageParamsPagination parameters used for each page
fetchNextPage()Get next page
hasNextPageIs there a next page?
isFetchingNextPageAre you getting the next page?
getNextPageParamCalculate 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 signal does 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:

Classificationcore concepts
Data readinguseQuery, Query Key, QueryFn, Query status
caching mechanismstaleTime、gcTime、Active/Inactive、Structural Sharing
Data modificationuseMutation、Lifecycle、Optimistic Update
cache controlinvalidateQueries、setQueryData、resetQueries、Prefetch

Core data process:

React Component
       ↓
useQuery / useMutation
       ↓
QueryClient
       │
       ├── QueryCache
       └── MutationCache
       ↓
queryFn / mutationFn
       ↓
Axios
       ↓
Django API

What you need to master most:

  1. Query Key How to uniquely identify a cache.
  2. The difference between staleTime and gcTime.
  3. The relationship between Active / Inactive and Fresh / Stale.
  4. Query How to automatically reacquire data.
  5. Mutation How to update or invalidate cache after success.
  6. onMutate, onError and optimistic updates.
  7. How to reuse QueryClient from Hooks generated by Orval.

Reference documentation