'Let TypeScript compiler know that the data is present in React-Query

React-Query generally returns some query states such as isLoading, isError. The library guarantees that these booleans are stable. This means we can be sure that the data is present if e. g. check for the onSuccess boolean. However, this - of course - isn't enough for the TS compiler. Even after the check, it assumes that the data can be undefined. What's the best way to let the TS compiler know that the data is present?

Here's an example from the docs:

function Todos() {
   const { isLoading, isError, data, error } = useQuery('todos', fetchTodoList)
 
   if (isLoading) {
     return <span>Loading...</span>
   }
 
   if (isError) {
     return <span>Error: {error.message}</span>
   }
 
   // --> The data is now sure to exist

   return (
     <ul>
       {data.map(todo => ( // --> this will not work with the TS compiler.
         <li key={todo.id}>{todo.title}</li>
       ))}
     </ul>
   )
 }


Solution 1:[1]

react-query added discriminated unions in v3, so this generally works. You can discriminate by the status field as well as the derived boolean flags. However, there are two things that you need to keep in mind:

  1. discriminated unions do not work if you destruct. This has nothing to do with react-query, but with TypeScript. If you take a field "out" of an object with destructuring, it can no longer discriminate that object.

  2. react-query has an additional state: isIdle (or status === 'idle'), which will be true if the query is disabled via enabled: false.

Keeping these two things in mind, this should work:

function Todos() {
   const queryInfo = useQuery('todos', fetchTodoList)
 
   if (queryInfo.isLoading || queryInfo.isIdle) {
     return <span>Loading...</span>
   }
 
   if (queryInfo.isError) {
     return <span>Error: {error.message}</span>
   }
 
   // --> The data is now sure to exist

   return (
     <ul>
       {queryInfo.data.map(todo => ( // --> TS is happy now
         <li key={todo.id}>{todo.title}</li>
       ))}
     </ul>
   )
 }

of course, you can also just check for isSuccess or status === 'success' to narrow the type.

Solution 2:[2]

When you need to load data once e.g. on the application start, I personally like to use "Gate" component. The role of the gate is to stop futher rendering as long as data is not loaded yet. When data is finally loaded I use context&customHook to read my initial data. At this point Im sure that my data will not be undefined. You can check a PoC here

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 TkDodo
Solution 2 IdeFFiX