query icon indicating copy to clipboard operation
query copied to clipboard

make TError default to Error instead of unknown

Open TkDodo opened this issue 2 years ago • 7 comments

context

In JavaScript, you can throw anything, even literals like 5 or Promises (?? suspense). That is why the generic for the type TError defaults to unknown. This is in line with how TypeScript itself handles errors in catch clauses since v4.4.

This is not very practical. Unless you explicitly throw a non-error, error will be at least of type Error. If you use axios, it might be of type AxiosError, but that is also not guaranteed. For example, if you have a runtime error in select, that will be caught, and it puts your query into error state. The error will be of type Error then.

Practically, it means that you either fall back to runtime checks:

const { error } = useQuery(...)
if (error instanceof Error) {
  // do something with error.message
}

or, you pass a generic to useQuery:

const { error, isError } = useQuery<Todos, Error>(...)
if (isError) {
  // do something with error.message
}

The second approach is bad for two reasons:

  1. TError is the second generic, so you also have to annotate the first generic, which is the return type of the queryFn. This could actually be inferred.
  2. useQuery has 4 generics, and if you only provide two of them, the other two will fall back to their default value instead of being inferred from their usage. That means select will not work as expected:
const { error, isError } = useQuery<Todos, Error>({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  select: (data) => data[0],
})

if (isError) {
  // do something with error.message
}

You'll get a weird error about no overload matching, see this TypeScript Playground.

proposal

  • on type level, make the TError generic default to Error instead of unknown
  • no runtime changes. If you throw something else, your types might be wrong.
    • we should document this, and @tannerlinsley mentioned he knows a way to overwrite the types for Error globally.

TkDodo avatar Dec 23 '22 07:12 TkDodo

Isn't any a better fit than Error ?

incepter avatar Dec 23 '22 20:12 incepter

That would be pretty unsafe to work with. unknown is "correct" but also inconvenient. Trying to find the middle ground here

TkDodo avatar Dec 23 '22 20:12 TkDodo

Agree that any would be more unsafe than unknown IMO Error is better here, if it doesn't fit the Developer's need we can't still infer other types, but most of the time we use Error. I can work on this one if you want @TkDodo ?

judicaelandria avatar Dec 24 '22 03:12 judicaelandria

Gotta remember that this just a default generic, too. Easily overridden and even inferred through usage in most places.

tannerlinsley avatar Dec 24 '22 03:12 tannerlinsley

If I understand this correctly, we need to change all the TError generic types for core and for react-query, vue-query, etc to Error cc @tannerlinsley

judicaelandria avatar Dec 24 '22 07:12 judicaelandria

Correct

tannerlinsley avatar Dec 24 '22 07:12 tannerlinsley

got it! can you assign this to me?

judicaelandria avatar Dec 24 '22 07:12 judicaelandria