'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?
Solution 1:[1]
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)
Solution 2:[2]
@TKDo, Your solution only works with react-query versions till "4.0.0-alpha.1", the issue will crop up again, if we use any latest version of react-query ("4.0.0-alpha.2" and above). To fix that, we need to use an object and assign the list of queries as the value to queries key like below example.
const results = useQueries({queries: [
{
queryKey: ["barQuery", "BAR_TEST_SINGLE"],
queryFn: () => fetchData("BAR_TEST_SINGLE", "models")
},
{
queryKey: ["lineQuery", "LINE_TEST"],
queryFn: () => fetchData("LINE_TEST", "models")
}]});
Solution 3:[3]
I couldn't add a comment as my reputation is currently too low, but I had the same problem as vaibhav-deep, so thought I would put this here even if it's a bit off-topic.
It was fixed by returning whatever my data is in the async/await fetch function.
const queries = useQueries(
queryList.map((query) => {
return {
queryKey: [query],
queryFn: () => fetchData(query),
};
})
);
async function fetchData(query) {
const response = await axios.get(apiCall)...
...
return response.data;
}
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 | Arjun KayalMoni |
Solution 3 | Alex |