query: Type definitions for `error` value are `unknown`

According to the docs, the error value returned by all of the hooks (useQuery, usePaginatedQuery, useInfiniteQuery, useMutation etc…) can either be null or Error.

The type definitions mostly define it as unknown or unknown | null

This should probably be considered with issue #475 since that issue suggests that the return value in the docs may be incorrect.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 7
  • Comments: 15 (4 by maintainers)

Commits related to this issue

Most upvoted comments

Do you think it might be handy to default TError = Error? I think that covers most use cases.

Problem is that TError comes after TResult which was automatically inferred, you are forcing people to define both when in an earlier version it just worked in the majority of use cases.

You can fill out the types in side the <> after useQuery, to specify the type of your result and your errors. In this case, your errors are simply Error.

  const { status, data, error, fetchMore, canFetchMore } = useInfiniteQuery<ResultModel, Error>(
    "questions",
    getStackQuestions,
    {
      getFetchMore: (lastGroup, allGroups) => lastGroup.nextPageNumber,
      staleTime: 1000 * 60 * 60 * 24 * 365,
    }
  );
  if (status === "error") {
    return <span>Error: {error.message}</span>;
  }

@vincerubinetti we’ll change the type to be Error per default for v5 😃

@norbertsongin it is not guaranteed that any errors you are getting are of a certain type. In JavaScript, anything can be thrown, so unknown is the most safe type. You can alway do an instanceof runtime check, and TypeScript will narrow the type for you.

A real life scenario for queries would be that you have a runtime error in select, which will transform your query to error state with a “normal” Error - not the type you are asserting.

This is in line with TypeScripts recent change in 4.4 to default to unknown in catch clauses.

relevant read: https://tkdodo.eu/blog/react-query-and-type-script#what-about-error

I agree with this:

Problem is that TError comes after TResult which was automatically inferred, you are forcing people to define both when in an earlier version it just worked in the majority of use cases

I agree as well.

It would be nice if the appropriate practice to manage this was listed in the examples.

@thebuilder Thank you for replying. I am not that fluent with Generics can you tell me how would that work over here? I am unable to find any good resources for it.

I see that the source file is typed like below but I am not sure how I would go about using this in my code.

// Parameter syntax with optional config
export function useInfiniteQuery<TResult = unknown, TError = unknown>(
  queryKey: QueryKey,
  queryConfig?: InfiniteQueryConfig<TResult, TError>
): InfiniteQueryResult<TResult, TError>

@TkDodo I agree with https://github.com/TanStack/query/issues/483#issuecomment-1047987028 that unknown is the best option here - why did you decide to go against this?

Balancing type safety with ergonomics, mostly. Defaulting to Error seems like it’s what you’d have 99.5% of the time, and it’s in-line with JS best practices to only throw Errors and nothing else. It will allow us to access error.message without having to check for it first.

Also, the new Register interface in v5 allows you to easily go back to unknown if you prefer that:

https://tanstack.com/query/v5/docs/react/typescript#registering-a-global-error

declare module '@tanstack/react-query' {
  interface Register {
    defaultError: unknown
  }
}

the onXXX callbacks are callbacks to execute side-effects, not to transform data. You also can’t transform data in onSuccess with it. What you can do from those callbacks is return a Promise, which will be awaited internally:

onError: () => {
  return queryClient.invalidateQueries()
}

this makes sure your mutation will stay in pending state while the invalidation is ongoing. So I don’t think your proposal is doable

I can kinda understand why the type should be unknown thanks to JavaScript’s weirdness. Though I wonder if it could be some kind of RQ global setting? Maybe provide the type to RQ once, and assume it everywhere else? (Not sure if that’s even possible in TypeScript).

Related question, if I want to manually specify Error as the error type, how would I do that while being able to keep the Data type inference from the queryFn and not have to redefine it.

useMutation<X, Error>({
    queryFn: someApiFunc
})
// where X still gets inferred automatically from someApiFunc, so I don't have to duplicate ReturnType all over my code base

Is this possible somehow? I can’t find anything on Google about it.