Monday, April 21, 2025
HomeRuby On RailsMigrating to TanStack Question v5

Migrating to TanStack Question v5


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:

  1. Preliminary Render: The information is undefined and usersCount is 0 whereas the question
    is fetching, which is the right preliminary state.
  2. After Question Decision: As soon as the question resolves and onSuccess runs, information
    can be an array of three customers. Nevertheless, since setUsersCount is asynchronous,
    usersCount will stay 0 till the state replace completes. That is fallacious
    as a result of values will not be in-sync.
  3. 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 and usersCount 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.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments