How to properly implement useQueries in react-query?

I’m using react-query to make two separate queries in the same React component. I originally tried using two useQuery hooks:

export default function Component() {
  const [barData, setBarData] = useState();
  const [lineData, setLineData] = useState();
  const { error: errorBar, isLoading: loadingBar } = useData(
    "barQuery",
    "BAR_TEST_SINGLE",
    setBarData
  );
  const { error: errorLine, isLoading: loadingLine } = useData(
    "lineQuery",
    "LINE_TEST",
    setLineData
  );

  const isLoading = loadingLine && loadingBar;
  const error = errorLine && errorBar;


  if (isLoading) return <LoadingSpinner title={title} />;
  if (error)
    return <InvalidStateAPI description={error.message} title={title} />;
  return (
    <>
        <Line data={lineData} />
        <Bar data={barData} />
    </>
  );
}

Here’s the content of useData, which is imported from another file:

export function useData(queryId, chartId, setData) {
  return useQuery(
    queryId,
    () =>
      fetchData(chartId, "models").then((resp) => {
        setData(resp.Item.data);
      })
  );
}

fetchData is a function that queries an AWS DDB table. I’m happy to post that function, but I’m currently excluding it for brevity.

Rendering Component results in this error: TypeError: Cannot read properties of undefined (reading 'filter'). I suspect this error is thrown because a component doesn’t get its data in time for rendering. I thought this would be solved by adding const isLoading = loadingLine && loadingBar;, as suggested in this post, but it did not.

Finally, on the recommendation of this SO answer, I decided to use useQueries. The documentation is quite sparse, but it recommends the following format:

const results = useQueries([
   { queryKey: ['post', 1], queryFn: fetchPost },
   { queryKey: ['post', 2], queryFn: fetchPost },
 ])

I modified my original code to the following:

 const results = useQueries([
   {
     queryKey: ["post", 1],
     queryFn: useData2("barQuery", "BAR_TEST_SINGLE", setBarData),
   },
   {
     queryKey: ["post", 2],
     queryFn: useData2("lineQuery", "LINE_TEST", setLineData),
   },
 ]);

but now I’m getting this error: TypeError: _this2.options.queryFn is not a function.

Am I formatting my query incorrectly? How can I fix it? Alternatively, are there other ways to run different queries using react-query in the same component?

Answer

No, this is not the correct syntax for useQueries. You can’t pass a useQuery hook in as queryFn – the queryFn needs the function that fetches the data, in your case, that would be fetchData(chartId, "models").

The root cause of your initial problem however seems to be that your condition only waits until one of the queries has finished loading:

const isLoading = loadingLine && loadingBar;

if loadingLine is false and loadingBar is true, this condition will yield false and you will thus not display a loading spinner anymore. If you want to wait until all queries have finished loading, that would be:

const isLoading = loadingLine || loadingBar;

lastly, I’d like to point out that copying data from react-query to local state is not necessary. react-query will return the result returned from the queryFn as the data field. My implementation would look something like that:

export function useData(queryId, chartId) {
  return useQuery(
    [queryId, chartId],
    () => fetchData(chartId, "models")
  );
}

const { error: errorBar, isLoading: loadingBar, data: barData } = useData(
    "barQuery",
    "BAR_TEST_SINGLE",
  );
  const { error: errorLine, isLoading: loadingLine, data: lineData } = useData(
    "lineQuery",
    "LINE_TEST",
  );

  const isLoading = loadingLine || loadingBar;

or, alternatively, with useQueries:

 const results = useQueries([
   {
     queryKey: ["barQuery", "BAR_TEST_SINGLE"],
     queryFn: () => fetchData("BAR_TEST_SINGLE", "models")
   },
   {
     queryKey: ["lineQuery", "LINE_TEST"],
     queryFn: () => fetchData("LINE_TEST", "models")
   },
 ]);

const isLoading = results.some(result => result.isLoading)