T
TanStack3y ago
flat-fuchsia

Best way to query "sub-queries"

I'm attempting to query more data for each item in the "parent" query and have something like this:
const { data, status } = useQuery(['foobars', id], async () => {
const foobars = await foobars.query({ id })

await Promise.all(
foobars.map(async foobar => {
// eslint-disable-next-line require-atomic-updates
foobar.messages = await foobarMessages.query({ id: foobar.id })
})
)

return { foobars }
})
const { data, status } = useQuery(['foobars', id], async () => {
const foobars = await foobars.query({ id })

await Promise.all(
foobars.map(async foobar => {
// eslint-disable-next-line require-atomic-updates
foobar.messages = await foobarMessages.query({ id: foobar.id })
})
)

return { foobars }
})
My question is, is there a better way to do this? Perhaps with useQuery and useQueries? While what I have works it feels like I might be misusing react-query, not to mention eslint complains of atomic updates.
11 Replies
genetic-orange
genetic-orange3y ago
yeah I think I ultimately have a related question which I came here to post. My understanding from https://tanstack.com/query/v4/docs/react/guides/dependent-queries is that this is how you are supposed to do things. But it does feel wrong, because I'm triggering extra rerenders. If i have useQuery(A) and useQuery(B(A)) and only actually update the view on the latter, i'm going to rerender when A fetches (but B(A)) hasn't, but not update my view at all.
Dependent Queries | TanStack Query Docs
useQuery dependent Query Dependent (or serial) queries depend on previous ones to finish before they can execute. To achieve this, it's as easy as using the enabled option to tell a query when it is ready to run:
genetic-orange
genetic-orange3y ago
Because I'm building a web3 application, these dependency chains can get extremely long. Like, every single query depends on 1-2 at a minimum. But I may be misunderstanding rerendering
flat-fuchsia
flat-fuchsiaOP3y ago
Yeah that’s what I’ve referenced and having the same dilemma. I need all of the data available before I actually display it in the app which is why I tried sneaking it in one useQuery. It also simplifies my overlay logic because we have a loading spinner covering the interface until the data is available for render. Breaking it apart into multiple useQuery/useQueries ends up complicating that logic with multiple status variables. For reference, the JSX is something along the lines of:
<Overlay status={status}>
{data?.foobars.map(foobar => (
<div key={foobar.id}>
...
{foobar.messages && (
<Well className={clsx('ml-20', styles.well)}>
{foobar.messages.map(message => (
<Message message={message} key={message.id} />
))}
</Well>
)}
</div>
))}
</Overlay>
<Overlay status={status}>
{data?.foobars.map(foobar => (
<div key={foobar.id}>
...
{foobar.messages && (
<Well className={clsx('ml-20', styles.well)}>
{foobar.messages.map(message => (
<Message message={message} key={message.id} />
))}
</Well>
)}
</div>
))}
</Overlay>
genetic-orange
genetic-orange3y ago
re: spinners, I think you can just use a single field from your useQueries, though I'm not confident off the top of my head which one actually, i think that this is false. another consideration towards a different API: I'd like to be able to define the chain of queries once, and then get useFooQuery and fetchFoo both. Instead, if I have something rather complicated, I have to do: useDQuery = (x) => { const a = useAQuery(x).data const b = useBQuery(a).data const c = useCQuery(b).data return useQuery(blahblah(c)) } and THEN fetchD = async (x) => fetchA.then(fetchB).then(fetchC).then(c => queryClient.fetchQuery(blahblah(c)) which, is not the worst thing in the world, but it is kinda annoying and obviously the relationships can be a lot more complex github copilot has been handy here sorry to hijack but i just wanted to give another case, which has been on my mind, where this comes up
flat-fuchsia
flat-fuchsiaOP3y ago
No worries I’m very pro communication with discussions like these. Just ask my coworker, I spam him all day long with tech talk
genetic-orange
genetic-orange3y ago
and yeah the 'isLoading' logic being complicated comes up a lot as well. a lot of the time for me, my app may very frequently want to do useFooQuery and then transform that data in some way. well I can make a handy hook, useBazFromFoo, and it depends on useFooQuery, but now all the information about whether this thing is fetching or not is destroyed. I'd have to return like [baz, fooQuery]. and if baz depends on multiple queries (it usually actually does!), then what now? not saying there's a simple answer but this is the kind of thing I feel like I end up wasting a lot of time on though thats a fairly minor issue
flat-fuchsia
flat-fuchsiaOP3y ago
Yeah sometimes there feels like there’s no right answer because you can solve a problem so many different ways. I guess the reason I originally made this post was to make sure my code was written in the most appropriate way to community standards My gut tells me the right way to write my code is with useQuery and a dependent useQueries with enabled, but that raises more complications like the status flags and data manipulation so I’m trying to decide if the right way is worth the headaches
genetic-orange
genetic-orange3y ago
i think in your case i would just use useQuery within each Message make Message just take an id
flat-fuchsia
flat-fuchsiaOP3y ago
It’s not actually Message in my source code. I just try to “foobar” my pseudo code as much as I can. My hands are also very tied by a database defined API. The data is actually an array of details and each detail has an array of approvals (signatures basically)
genetic-orange
genetic-orange3y ago
is each item of the array a component though? my point is just that any time the query hierarchy and the component hierarchy are isomorphic, you don't need useQueries (ive never actually once used useQueries, though this may be a mistake at least once :0)
conscious-sapphire
conscious-sapphire3y ago
This is an issue I've encountered as well - isLoading doesn't seem to work the way you'd predict. Maybe there is a resource somewhere on what the optimal way to structure things is for different situations. My thinking is that splitting things up is better because it allows you to more efficiently re-use queries. But it introduces complications with dependent queries and getting them to (reliably) fire only when the query they depend on is ready. I've used array.some to some success for this with queries A feeds B feeds C, but no need to update A and B on every fetch if they're reasonably expected to stay the same 95% of the time and only C will change with any frequency. Or say I want to get the overall status of an entire list of items, but also want to list the status of the individual items in another element without having to make a separate query for it? That seems like an ideal case for useQueries where I can re-use the result for both cases. But as you said, is it better to chain them with dependencies, or use suspense so they execute in parallel even if dependent, or just lump everything in one query that hits multiple endpoints (but then dependent aspects of the query get blocked with await which slows things down)? Can queries be nested and are they thenable? Etc.

Did you find this page helpful?