#Refactoring queries

7 messages · Page 1 of 1 (latest)

broken solar
#

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'],
      });
    },
  });
}

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.
@torn hedge 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.

torn hedge
#

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,
  });
}

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!!
)

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']

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 😂

broken solar
#

Anyway, thx for the detailed explanation. Now it clicks why do you need a query options 🙂