TanStack Question is a robust data-fetching
and state administration library. Because the launch of TanStack Question v5, many
builders upgrading to the brand new model have confronted challenges in migrating their
present performance. Whereas the official documentation covers all the small print,
it may be overwhelming, making it straightforward to overlook necessary updates.
On this weblog, we’ll clarify the primary updates in TanStack Question v5 and present how
to make the change easily.
For an entire checklist of adjustments, take a look at the
TanStack Question v5 Migration Information.
In earlier variations of React Question, capabilities like useQuery
and useMutation
had a number of sort overloads. This not solely made sort upkeep extra
difficult but in addition led to the necessity for runtime checks to validate the forms of
parameters.
To streamline the API, TanStack Question v5 introduces a simplified method: a
single parameter as an object containing the primary parameters for every perform.
- queryKey / mutationKey
- queryFn / mutationFn
- …choices
Under are some examples of how generally used hooks and queryClient
strategies
have been restructured.
// earlier than (A number of overloads)
useQuery(key, fn, choices);
useInfiniteQuery(key, fn, choices);
useMutation(fn, choices);
useIsFetching(key, filters);
useIsMutating(key, filters);
// after (Single object parameter)
useQuery({ queryKey, queryFn, ...choices });
useInfiniteQuery({ queryKey, queryFn, ...choices });
useMutation({ mutationFn, ...choices });
useIsFetching({ queryKey, ...filters });
useIsMutating({ mutationKey, ...filters });
// earlier than (A number of overloads)
queryClient.isFetching(key, filters);
queryClient.getQueriesData(key, filters);
queryClient.setQueriesData(key, updater, filters, choices);
queryClient.removeQueries(key, filters);
queryClient.cancelQueries(key, filters, choices);
queryClient.invalidateQueries(key, filters, choices);
// after (Single object parameter)
queryClient.isFetching({ queryKey, ...filters });
queryClient.getQueriesData({ queryKey, ...filters });
queryClient.setQueriesData({ queryKey, ...filters }, updater, choices);
queryClient.removeQueries({ queryKey, ...filters });
queryClient.cancelQueries({ queryKey, ...filters }, choices);
queryClient.invalidateQueries({ queryKey, ...filters }, choices);
This method ensures builders can handle and go parameters extra cleanly,
whereas sustaining a extra manageable codebase with fewer sort points.
A big change in TanStack Question v5 is the removing of callbacks similar to
onError
, onSuccess
, and onSettled
from useQuery
and QueryObserver
.
This variation was made to keep away from potential misconceptions about their habits and
to make sure extra predictable and constant unintended effects.
Beforehand, we may outline onError
instantly throughout the useQuery
hook to
deal with unintended effects, similar to exhibiting error messages. This eradicated the necessity
for a separate useEffect
.
const useUsers = () => {
return useQuery({
queryKey: ["users", "list"],
queryFn: fetchUsers,
onError: error => {
toast.error(error.message);
},
});
};
With the removing of the onError
callback, we now have to deal with unintended effects
utilizing React’s useEffect
.
const useUsers = () => {
const question = useQuery({
queryKey: ["users", "list"],
queryFn: fetchUsers,
});
React.useEffect(() => {
if (question.error) {
toast.error(question.error.message);
}
}, [query.error]);
return question;
};
Through the use of useEffect
, the problem with this method turns into way more obvious.
As an example, if useUsers()
is known as twice throughout the software, it is going to
set off two separate error notifications. That is clear when inspecting the
useEffect
implementation, as every element calling the customized hook registers
an unbiased impact. In distinction, with the onError
callback, the habits
is probably not as clear. We would anticipate errors to be mixed, however they aren’t.
For some of these situations, we will use the worldwide callbacks on the
queryCache
. These international callbacks will run solely as soon as for every question and
can’t be overwritten, making them precisely what we’d like for extra predictable
aspect impact dealing with.
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: error => toast.error(`One thing went fallacious: ${error.message}`),
}),
});
One other frequent use case for callbacks was updating native state based mostly on question
information. Whereas utilizing callbacks for state updates may be simple, it could
result in pointless re-renders and intermediate render cycles with incorrect
values.
For instance, think about the situation the place a question fetches a listing of three customers and
updates the native state with the fetched information.
export const useUsers = () => {
const [usersCount, setUsersCount] = React.useState(0);
const { information } = useQuery({
queryKey: ["users", "list"],
queryFn: fetchUsers,
onSuccess: information => {
setUsersCount(information.size);
},
});
return { information, usersCount };
};
This instance includes three render cycles:
- Preliminary Render: The
information
is undefined andusersCount
is 0 whereas the question
is fetching, which is the right preliminary state. - After Question Decision: As soon as the question resolves and
onSuccess
runs, information
can be an array of three customers. Nevertheless, sincesetUsersCount
is asynchronous,
usersCount
will stay 0 till the state replace completes. That is fallacious
as a result of values will not be in-sync. - Last Render: After the state replace completes,
usersCount
is up to date to
replicate the variety of customers (3), triggering a re-render. At this level, each
information
andusersCount
are in sync and show the right values.
The refetchInterval
callback now solely receives the question
object as its
argument, as a substitute of each information
and question
because it did earlier than. This variation
simplifies how callbacks are invoked and it resolves some typing points that
arose when callbacks have been receiving information remodeled by the choose
possibility.
To entry the information throughout the question object, we will now use question.state.information
.
Nevertheless, remember the fact that this is not going to embrace any transformations utilized by
the choose possibility. If we have to entry the remodeled information, we’ll have to
manually reapply the transformation.
For instance, think about the next code snippet:
const useUsers = () => {
return useQuery({
queryKey: ["users", "list"],
queryFn: fetchUsers,
choose: information => information.customers,
refetchInterval: (information, question) => {
if (information?.size > 0) {
return 1000 * 60; // Refetch each minute if there's information
}
return false; // Do not refetch if there is no such thing as a information
},
});
};
This may now be refactored as follows:
const useUsers = () => {
return useQuery({
queryKey: ["users", "list"],
queryFn: fetchUsers,
choose: information => information.customers,
refetchInterval: question => {
if (question.state.information?.customers?.size > 0) {
return 1000 * 60; // Refetch each minute if there's information
}
return false; // Do not refetch if there is no such thing as a information
},
});
};
Equally, the refetchOnWindowFocus
, refetchOnMount
, and
refetchOnReconnect
callbacks now solely obtain the question
as an argument.
Under are the adjustments to the sort signature for the refetchInterval
callback
perform:
// earlier than
refetchInterval: quantity | false | ((information: TData | undefined, question: Question)
=> quantity | false | undefined)
// after
refetchInterval: quantity | false | ((question: Question) => quantity | false | undefined)
The time period cacheTime
is usually misunderstood because the length for which information is
cached. Nevertheless, it truly defines how lengthy information stays within the cache after a
question turns into unused. Throughout this era, the information stays lively and
accessible. As soon as the question is not in use and the desired cacheTime
elapses, the information is taken into account for “rubbish assortment” to stop the cache
from rising excessively. Due to this fact, the time period gcTime
extra precisely describes
this habits.
const MINUTE = 1000 * 60;
const queryClient = new QueryClient({
defaultOptions: {
queries: {
- // cacheTime: 10 * MINUTE, // earlier than
+ gcTime: 10 * MINUTE, // after
},
},
})
The keepPreviousData
possibility and the isPreviousData
flag have been eliminated in
TanStack Question v5, as their performance was largely redundant with the
placeholderData
and isPlaceholderData
choices.
To duplicate the habits of keepPreviousData
, the earlier question information is now
handed as a parameter to the placeholderData
possibility. This selection can settle for an
identification perform to return the earlier information, successfully mimicking the identical
habits. Moreover, TanStack Question supplies a built-in utility perform,
keepPreviousData
, which can be utilized instantly with placeholderData
to realize
the identical impact as in earlier variations.
Right here’s how we will use placeholderData
to duplicate the performance of
keepPreviousData
:
import {
useQuery,
+ keepPreviousData // Constructed-in utility perform
} from "@tanstack/react-query";
const {
information,
- // isPreviousData,
+ isPlaceholderData, // New
} = useQuery({
queryKey,
queryFn,
- // keepPreviousData: true,
+ placeholderData: keepPreviousData // New
});
In earlier variations of TanStack Question, undefined
was handed because the
default web page parameter to the question perform in infinite queries. This led to
potential points with non-serializable undefined
information being saved within the
question cache.
To resolve this, TanStack Question v5 introduces an express initialPageParam
parameter within the infinite question choices. This ensures that the web page parameter is
all the time outlined, stopping caching points and making the question state extra
predictable.
useInfiniteQuery({
queryKey,
- // queryFn: ({ pageParam = 0 }) => fetchSomething(pageParam),
queryFn: ({ pageParam }) => fetchSomething(pageParam),
+ initialPageParam: 0, // New
getNextPageParam: (lastPage) => lastPage.subsequent,
})
The loading
standing is now referred to as pending
, and the isLoading
flag has been
renamed to isPending
. This variation additionally applies to mutations.
Moreover, a brand new isLoading
flag has been added for queries. It’s now
outlined because the logical AND of isPending
and
isFetching
(isPending && isFetching
). Which means that isLoading
behaves the
similar because the earlier isInitialLoading
. Nevertheless, since isInitialLoading
is
being phased out, it will likely be eliminated within the subsequent main model.