Exhaustive Type checking for React Query in Typescript
Using React Query in Typescript has some pitfalls as I noticed. Consider the returned status of the useQueryResult hook. Status can take 4 different states:
1const status: "idle" | "error" | "loading" | "success"
Consider a blogpost component. If you are checking the status with an if-statement like the documentation of react query suggests like so:
1 const {status, data, error} = useQuery("blogpost", () => getPost()); 2 3 if (status === 'loading' || status === 'idle') { 4 return <span>Loading...</span> 5 } 6 if (status === 'error') { 7 if (error instanceof Error) 8 return <span>Error: {error.message}</span> 9 } 10 if (status === 'success') { 11 return data != undefined ? 12 <div>{data.content!}</div> : <div>No Content</div> 13 } 14} 15
Typescript compiler will give an error like this:
1'Blogpost' cannot be used as a JSX component. 2 Its return type 'Element | undefined' is not a valid JSX element. 3 Type 'undefined' is not assignable to type 'Element | null'
One possible solution which is suggested by the IDE for example is to just add an else-clause:
1 const {status, data, error} = useQuery("blogpost", () => getPost()); 2 3 if (status === 'loading' || status === 'idle') { 4 return <span>Loading...</span> 5 } 6 if (status === 'error') { 7 if (error instanceof Error) 8 return <span>Error: {error.message}</span> 9 } 10 if (status === 'success') { 11 return data != undefined ? 12 <div>{data.content!}</div> : <div>No Content</div> 13 } 14 else return <div>Why do I have to be here ??</div> 15} 16
which does not look very clean.
- The else-block is never reached.
- We have unnecessary code overhead
The reason is that typescript is not able to perform exhaustive type checking in if-statements. Instead you can use a switch-statement:
1 const {status, data, error} = useQuery("blogpost", () => getPost()); 2 3 switch (status) { 4 case "idle": 5 return <div>idle</div>; 6 case 'loading': return <span>Loading...</span> 7 case 'error': 8 return <span>Error: {error.message}</span> 9 10 case "success": {return data != undefined ? 11 <div>{data.content!}</div> : <div>No Content</div>
This avoids any errors and useless extra return cases.