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.
So this was fixed in #514 - But I'm not sure this is the correct approach, since it expects errors to be Error objects.
This will only occur naturally by an unhandled error being thrown, and prevents you from creating custom error objects with more context. Using unknown you are forced to specify the type.
As an example, In my fetch function I'll reject the promise with the status and JSON response:
// See if we can get a valid JSON error message from the response.
const response = await fetch(url, request);
const json: Result = await response.json().catch(() => {
// Failed to parse a JSON response. Return an empty object instead.
return {};
});
if (response.ok) {
return json;
} else {
return Promise.reject({
status: response.status,
...json,
});
}
I'd than handle the error by setting the type of the error:
onError: (error: SchemaProblemDetails) => {
// In case of a 401 Unauthorized call, clear the token since it's invalid
if (error.status === 401 && clearLogin) clearLogin();
},
This will now fail, unless I do a type conversion when reading the error value.
A better way is if we can configure the expected type of error alongside the type of data.
This is how SWR handles it.
This would have the BaseQueryOptions accept the type of error:
export interface BaseQueryOptions<Error = any> {
/**
* Set this to `true` to disable automatic refetching when the query mounts or changes query keys.
* To refetch the query, use the `refetch` method returned from the `useQuery` instance.
*/
manual?: boolean
/**
* If `false`, failed queries will not retry by default.
* If `true`, failed queries will retry infinitely., failureCount: num
* If set to an integer number, e.g. 3, failed queries will retry until the failed query count meets that number.
* If set to a function `(failureCount, error) => boolean` failed queries will retry until the function returns false.
*/
retry?: boolean | number | ((failureCount: number, error: Error) => boolean)
retryDelay?: (retryAttempt: number) => number
staleTime?: number
cacheTime?: number
refetchInterval?: false | number
refetchIntervalInBackground?: boolean
refetchOnWindowFocus?: boolean
refetchOnMount?: boolean
onError?: (err: Error) => void
suspense?: boolean
isDataEqual?: (oldData: unknown, newData: unknown) => boolean
}
And that would than have to be passed down from the hooks, so they accept the type of error in addition to the type of result, key, variables, etc.
useQuery<TResult, TKey, TError>()
@thebuilder I am not sure if I am doing it right but it is showing me the error Object is of type 'unknown' when I try to access error.message. Do I need to add an interface for the results of useQuery?
My code
const { status, data, error, fetchMore, canFetchMore } = useInfiniteQuery(
"questions",
getStackQuestions,
{
getFetchMore: (lastGroup, allGroups) => lastGroup.nextPageNumber,
staleTime: 1000 * 60 * 60 * 24 * 365,
}
);
if (status === "error") {
return <span>Error: {error.message}</span>;
}
You need to specify the type of the error as a generic, when creating the query.
@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>
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>;
}
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.
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 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.