How to handle multiple queries with React-Query

Beadle picture Beadle · Mar 1, 2021 · Viewed 7.9k times · Source

I've started playing with React-Query and it works great if I only need to fetch data from a single collection in my database. However, I'm struggling to find a good way to query multiple collections for use in a single component.

One Query (no problem):

const { isLoading, isError, data, error } = useQuery('stuff', fetchStuff)

if (isLoading) {
     return <span>Loading...</span>
   }
 
   if (isError) {
     return <span>Error: {error.message}</span>
   }
 
   return (
     <ul>
       {data.map(stuff => (
         <li key={stuff.id}>{stuff.title}</li>
       ))}
     </ul>
   )
 }

Multiple Queries to Different Collections (😬):

const { isLoading: isLoadingStuff, isError: isErrorStuff, data: stuff, error: errorStuff } = useQuery('stuff', fetchStuff);
const { isLoading: isLoadingThings, isError: isErrorThings, data: Things, error: errorThings } = useQuery('things', fetchThings);
const { isLoading: isLoadingDifferentStuff, isError: isErrorDifferentStuff, data: DifferentStuff, error: errorDifferentStuff } = useQuery('DifferentStuff', fetchDifferentStuff);

const isLoading = isLoadingStuff || isLoadingThings || isLoadingDifferentStuff
const isError = isErrorStuff || isErrorThings || isErrorDifferentStuff
const error = [errorStuff, errorThings, errorDifferentStuff]

if (isLoading) {
     return <span>Loading...</span>
   }
 
if (isError) {
    return (
      <span>
        {error.forEach((e) => (e ? console.log(e) : null))}
        Error: see console!
      </span>
    );
  }
 
   return (
  <>
    <ul>
      {stuff.map((el) => (
        <li key={el.id}>{el.title}</li>
      ))}
    </ul>
    <ul>
      {things.map((el) => (
        <li key={el.id}>{el.title}</li>
      ))}
    </ul>
    <ul>
      {differentStuff.map((el) => (
        <li key={el.id}>{el.title}</li>
      ))}
    </ul>
  </>
);
 }

I'm sure there must be a better way to do this. I'm very interested in React-Query for multiple reasons but one nice benefit is to reduce boilerplate. However, this approach doesn't seem much better than using useEffect and useState to manage my api calls. I did find the useQueries hook but it didn't make this any cleaner really.

Does anyone know if there is a way in React-Query to make multiple queries and only get back one isLoading, isError, and error(array?) response? Or just a better way to handle multiple queries that I'm missing?

Answer

TkDodo picture TkDodo · Mar 2, 2021

I did find the useQueries hook but it didn't make this any cleaner really.

useQueries gives you an Array of results, so you can map over them:

const isLoading = queryResults.some(query => query.isLoading)

If you have a component that fires multiple concurrent requests, there is only so much the library can do to reduce complexity. Each query can have it's own loading state / error state / data. Each query can have its own settings and can behave differently. The recommended approach is still to extract that to a custom hook and return what you want from it.

error handling could be streamlined by using error boundaries with the useErrorBoundary option. To streamline loading experience, you can try suspense (though experimental), it will show the fallback loader for all queries.

this approach doesn't seem much better than using useEffect and useState to manage my api calls.

this is ignoring all the advantages like (amongst others) caching, background refetches, mutations, smart invalidation etc.