Type-narrowing doesn't work
Bug report
Description / Observed Behavior
const { data, error, isLoading } = useSWR<number>('/api/user/123', fetcher);
if (!isLoading && !error) {
data // number | undefined
}
Expected Behavior
It should be of type number.
I don't think useSWR returns isSuccess. Are you mistyping something @lcswillems ?
@Ram4GB My bad. Fixed the example!
I assume you request the user who is not found in your database or has been deleted by other actions. In this case, the API must throw the error with the status 400, so you won't access your data, and it has to return undefined. @lcswillems
Not sure to understand. I'm just saying if "isLoading" is false, then "data" should become of type "number".
It is a basic functionality of TanStack query: https://tanstack.com/query/latest/docs/framework/react/typescript#type-narrowing
But if fetching fails/errors, there won't be data, and it won't be loading either.
@icyJoseph Updated my example. Makes sense? Basically I'm asking for the same thing than TanStack Query.
Yes @lcswillems, your example is not clear to me. In your case, I believe we should get the type number instead of number | undefined.
Yes we should get the type number, it is what I say in the "expected behavior". Currently, the type is number | undefined (Observed behavior).
@lcswillems
I get your point.
Basically there are three ways to achieve your goal
1.
const { data, error, isLoading } = useSWR<number>('/api/user/123', fetcher, { suspense: true });
2.
const { data, error, isLoading } = useSWR<number>('/api/user/123', fetcher, { fallbackData: 0 });
3.
const { data, error, isLoading } = useSWR<number>('/api/user/123', fetcher);
if (!isLoading && !error && data !== undefined) {
data // number
}
Thank you for sharing!
-
Uses suspense. Don't want to go in that direction.
-
datawould then be always anumber. I still want it to be potentiallyundefined. -
Yeah but the whole point of this issue is to say I should not have to add
data !== undefined.
The 4th and best option would be to improve the typing of the library.
@lcswillems Improve the typing of the library seems to be the best solution, but there is a bit difference between your example and TanStack query.
Your example
const { data, error, isLoading } = useSWR<number>('/api/user/123', fetcher);
if (!isLoading && !error) {
data // number | undefined
}
TanStack query
const { data, isSuccess } = useQuery({
queryKey: ['test'],
queryFn: () => Promise.resolve(5),
})
if (isSuccess) {
data
// ^? const data: number
}
TanStack query creates a new field isSuccess to determine whether type of the data is Data | undefined or Data
@samuel871211 In TanStack query, this also narrows down:
const { data, isLoading, isError } = useQuery({ ... });
if (!isLoading && !isError) {
data
// ^? const data: number
}
And I'm wondering what prevents SWR to narrow down here:
const { data, error, isLoading } = useSWR<number>('/api/user/123', fetcher);
if (!isLoading && !error) {
data // number
}
?
@lcswillems
- I'm using
"@tanstack/react-query": "^5.64.2"
Your code example doesn't work for me.
const { data, isLoading, isError } = useQuery({ ... });
if (!isLoading && !isError) {
data
// ^? const data: number
}
- I believe SWR currently doesn't have this feature, so I have made a feature PR.
Original Codebase before PR
export interface SWRResponse<Data = any, Error = any, Config = any> {
/**
* The returned data of the fetcher function.
*/
data: BlockingData<Data, Config> extends true ? Data : Data | undefined
......
}
export type BlockingData<
Data = any,
Options = SWROptions<Data>
> = Options extends undefined
? false
: Options extends { suspense: true }
? true
: Options extends { fallbackData: Data | Promise<Data> }
? true
: false
The type of data is Data only when the user provides options.suspense or options.fallbackData.
@samuel871211 Sorry it is "isPending" that needs to be used:
https://stackblitz.com/edit/github-j5aexizp?file=src%2Findex.tsx