T
TanStack•3w ago
sensitive-blue

Calling dependent queries declaratively and imperatively

Hi, is there any good way to be able to call dependent queries both via hooks and imperatively? For independent queries, it's great that you can do this
const projectsOpts = (userId: string) => ({
queryKey: ['user', userId, 'projects'],
queryFn: () => fetchProjects(userId),
})

useQuery(projectsOpts('123'))
useSuspenseQuery(projectsOpts('123'))
queryClient.fetchQuery(projectsOpts('123'))
const projectsOpts = (userId: string) => ({
queryKey: ['user', userId, 'projects'],
queryFn: () => fetchProjects(userId),
})

useQuery(projectsOpts('123'))
useSuspenseQuery(projectsOpts('123'))
queryClient.fetchQuery(projectsOpts('123'))
but for dependent queries, I end up having to write it three times
const collaboratorsOpts = (projectId: string) => ({
queryKey: ['project', projectId, 'collaborators'],
queryFn: () => fetchCollaborators(projectId),
})

// Hook version
function useAllCollaborators(userId: string) {
const {data: projects} = useQuery(projectsOpts(userId))
return useQueries({
queries: projects?.map(p => collabsOpts(p.id)),
})
}

// Suspense hook
function useAllCollaboratorsSuspense(userId: string) {
const {data: projects} = useSuspenseQuery(projectsOpts(userId))
return useSuspenseQueries({
queries: projects.map(p => collaboratorsOpts(p.id)),
})
}

// Imperative version
async function fetchAllCollaborators(
qc: QueryClient,
userId: string,
) {
const projects = await qc.fetchQuery(projectsOpts(userId))
return await Promise.all(
projects.map(p =>
qc.fetchQuery(collaboratorsOpts(p.id))
)
)
}
const collaboratorsOpts = (projectId: string) => ({
queryKey: ['project', projectId, 'collaborators'],
queryFn: () => fetchCollaborators(projectId),
})

// Hook version
function useAllCollaborators(userId: string) {
const {data: projects} = useQuery(projectsOpts(userId))
return useQueries({
queries: projects?.map(p => collabsOpts(p.id)),
})
}

// Suspense hook
function useAllCollaboratorsSuspense(userId: string) {
const {data: projects} = useSuspenseQuery(projectsOpts(userId))
return useSuspenseQueries({
queries: projects.map(p => collaboratorsOpts(p.id)),
})
}

// Imperative version
async function fetchAllCollaborators(
qc: QueryClient,
userId: string,
) {
const projects = await qc.fetchQuery(projectsOpts(userId))
return await Promise.all(
projects.map(p =>
qc.fetchQuery(collaboratorsOpts(p.id))
)
)
}
3 Replies
sensitive-blue
sensitive-blueOP•6d ago
@TkDodo 🔮 any insight?
foreign-sapphire
foreign-sapphire•6d ago
yeah I don't see a way around that, except for combining the queries and caching them together
sensitive-blue
sensitive-blueOP•3d ago
Thanks. Caching them together breaks invalidation if i need the individual queries too. Do you perceive this to be a missing feature of Query or out of scope? Something like new Query() from DB would be awesome. Or just any way for a useQuery hook to be able to inspect what observers it needs to set up for an imperatively composed queryFn. Otherwise using route loaders is painful

Did you find this page helpful?