netrolite
netrolite

Reputation: 2723

react-query: onSuccess, onError, onSettled are deprecated. What should I use instead?

In this code snippet, when I hover over the onSuccess callback, it says that it's deprecated and will be removed in the next major version. The same happens with onError and onSettled callbacks. What do I use instead?

  const { isError, error, data, refetch, isFetching } = useQuery(
    ["posts"],
    fetchPosts,
    { enabled: false, onSuccess: () => {} }
  );

Upvotes: 42

Views: 61787

Answers (1)

netrolite
netrolite

Reputation: 2723

onError

You can use the onError callback inside the queryCache option of QueryClient

import {
  QueryCache,
  QueryClient,
} from "@tanstack/react-query";
import { toast } from "react-toastify";

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error) => {
      toast(`Something went wrong: ${error.message}`),
    }
  }),
})

And then wrap your application with it like you normally would:

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Posts />
    </QueryClientProvider>
  )
}

If you need more customizable onError side effects, then use the meta option of the useQuery hook. You can pass it any data you want, which you can then use to determine which side effect to trigger.

I came up with this solution:

Define an enum containing the error codes for triggering the side effects:

export const enum TErrCodes {
  POSTS_FETCH_FAILED,
  // other error codes...
}

Specify the error code in the meta option of the useQuery hook:

const query = useQuery(["posts"], fetchPosts, {
  meta: { errCode: TErrCodes.POSTS_FETCH_FAILED },
});

Detect the error code in the queryCache onError callback and perform the side effect you need. I'm showing notifications using the react-toastify library as an example:

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: queryCacheOnError,
  }),
});

function queryCacheOnError(err: unknown, query: Query) {
  switch (query.meta?.errCode) {
    case TErrCodes.POSTS_FETCH_FAILED:
      return toast.error("Could not fetch posts");
    default:
      return toast.error("Something went wrong");
  }
}

Alternatively, you can trigger onError side effects from the query function, like so:

async function fetchPosts() {
  try {
    const resp = await axios.get<TPost[]>("/posts");
    return resp.data;
  } catch (err) {
    if (!isKnownErr(err)) {
      return toast("Something went wrong");
    }

    switch (err.response.data.errCode) {
      case "INVALID_CREDENTIALS":
        toast("Invalid sign-in credentials");
        break;
      // more cases...
      default:
        toast("Something went wrong");
    }

    throw new Error(); // prevent query from being successful
  }
}

const query = useQuery(["posts"], fetchPosts);

onSuccess

You can trigger the onSuccess side-effects from inside the query function as well, like this:

async function fetchPosts() {
  const resp = await axios.get<TPost[]>("/postss");
  yourSideEffect();
  return resp.data;
}
const query = useQuery(["posts"], fetchPosts);

Why were they removed in the first place?

This is mostly taken from this article

  1. The callbacks have inconsistent behavior. They can be called multiple times for the same query, leading to duplication of side effects or state updates. For example, this can lead to showing 2 duplicate error notifications for the same query.

  2. The callbacks have been misused by some developers to perform state syncing operations, which often leads to really hard-to-spot bugs. You should instead derive state by using something like const postsCount = posts.length;. The postsCount variable will be updated whenever the component re-renders.

  3. Using callbacks sometimes leads to unexpected behavior with redux and other state managers.

Here's another article on the topic

Hope you found this helpful!

Upvotes: 62

Related Questions