T
TanStack6d ago
inland-turquoise

Refactoring queries

I'm working on a big enterprise project that grew out of a startup. Just like any other startup, it has all the same problems—fast-growing, constantly changing requirements, tons of code, different architectural approaches, etc. With a growing codebase, I started to observe query key collisions, and I want to clean this mess up, as it's becoming harder to maintain and catch bugs. I was looking into 2 possible solutions: 1. query-key-factory 2. query options To give you an example of the code, everywhere in the project we use custom hooks like this one:
export function useShiftPickupApprovals(params: GetShiftPickupApprovalsDto) {
return useQuery({
queryKey: ['schedules', 'shift-pickups', params],
queryFn: () =>
api.get<ShiftPickupApprovalsResponse>(
`/schedules/tasks/${params.facilityId}/shift-requests`,
{ params }
),
enabled: !!params.facilityId,
placeholderData: keepPreviousData,
});
}

export function useUpdateShiftAssignment() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (dto: UpdateScheduleAssignmentsDTO) =>
api.post('/schedules/records', dto),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['schedules'],
});
},
});
}
export function useShiftPickupApprovals(params: GetShiftPickupApprovalsDto) {
return useQuery({
queryKey: ['schedules', 'shift-pickups', params],
queryFn: () =>
api.get<ShiftPickupApprovalsResponse>(
`/schedules/tasks/${params.facilityId}/shift-requests`,
{ params }
),
enabled: !!params.facilityId,
placeholderData: keepPreviousData,
});
}

export function useUpdateShiftAssignment() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (dto: UpdateScheduleAssignmentsDTO) =>
api.post('/schedules/records', dto),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['schedules'],
});
},
});
}
Nowhere in the view layer do we use RQ directly, only these custom hooks. I wonder how to approach refactoring, not to f up things down the road. I tend to use query-key-factory over the query options approach, as I don't really see the benefits of using the latter one. @TkDodo 🔮 Any valuable insights? Any piece of advice on how to approach this? And what are the pros/cons of the 2 approaches I've mentioned? And yes, I've read the docs and your Blog thoroughly, but still don't understand the query options approach and its benefits when using custom hooks.
3 Replies
fair-rose
fair-rose6d ago
but still don't understand the query options approach and its benefits when using custom hooks
my take is that you'd make an abstraction with queryOptions so that you don't have to do custom hooks. If I take your custom useShiftPickupApprovals and make that into queryOptions, I'd get:
export function shiftPickupApprovalsQueryOptions(params: GetShiftPickupApprovalsDto) {
return queryOptions({
queryKey: ['schedules', 'shift-pickups', params],
queryFn: () =>
api.get<ShiftPickupApprovalsResponse>(
`/schedules/tasks/${params.facilityId}/shift-requests`,
{ params }
),
enabled: !!params.facilityId,
placeholderData: keepPreviousData,
});
}
export function shiftPickupApprovalsQueryOptions(params: GetShiftPickupApprovalsDto) {
return queryOptions({
queryKey: ['schedules', 'shift-pickups', params],
queryFn: () =>
api.get<ShiftPickupApprovalsResponse>(
`/schedules/tasks/${params.facilityId}/shift-requests`,
{ params }
),
enabled: !!params.facilityId,
placeholderData: keepPreviousData,
});
}
and then I can do:
useQuery(shiftPickupApprovalsQueryOptions(params))
useSuspenseQuery(shiftPickupApprovalsQueryOptions(params))
usePrefetchQuery(shiftPickupApprovalsQueryOptions(params))
queryClient.ensureQueryData(shiftPickupApprovalsQueryOptions(params))

queryClient.setQueryData(
shiftPickupApprovalsQueryOptions(params).queryKey,
data // ⬅️ this is type-safe!!
)
useQuery(shiftPickupApprovalsQueryOptions(params))
useSuspenseQuery(shiftPickupApprovalsQueryOptions(params))
usePrefetchQuery(shiftPickupApprovalsQueryOptions(params))
queryClient.ensureQueryData(shiftPickupApprovalsQueryOptions(params))

queryClient.setQueryData(
shiftPickupApprovalsQueryOptions(params).queryKey,
data // ⬅️ this is type-safe!!
)
and so on. So it's a much more flexible abstraction it doesn't help with key collisions though I usually resort to having the key be what the url is, so useShiftPickupApprovals would be:
queryKey: ['schedules', 'tasks', params.facilityId, 'shift-requests']
queryKey: ['schedules', 'tasks', params.facilityId, 'shift-requests']
maybe you can even generate the url out of that, or generate the key out of the url. at sentry we have a union type of known urls generated from the backend and we use that and the keys are generated from there; no conflicts; anything more - please hire me for consulting 😂
inland-turquoise
inland-turquoiseOP6d ago
I love your style. If it was up to me, I would hire you, no questions asked ☺️ Anyway, thx for the detailed explanation. Now it clicks why do you need a query options 🙂
fair-rose
fair-rose5d ago
Well I'm available and it helps keep the lights on 🙂

Did you find this page helpful?