Björn Urban

  • About Me
  • Projects
  • Blog

Exhaustive Type checking for React Query in Typescript

Titelbild
28. Oktober 2022
Typescript
React Query
Javascript
Web

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.

  1. The else-block is never reached.
  2. 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.