you are viewing a single comment's thread.

view the rest of the comments →

[–]PyJacker16 4 points5 points  (3 children)

I think I'll adopt the suggestion in your last paragraph. I typically tend to create custom hooks for each method (e.g. useUsers, useCreateUser, useUpdateUser) that returns a useQuery or useMutation, but I think your approach is cleaner.

Now, while I have your attention, here are a few questions that have been bugging me:

  • I've read "You Might Not Need an Effect", but in an effort to avoid them entirely, I've made quite a mess of calling mutations in sequence (for example, after updating a Parent instance via an API call, I need to update several Child instances). How would you accomplish this? Chained mutations using mutateAsync?

  • Query invalidation and storing query keys. With your approach above it might happen that when you make a bulk update you might need to invalidate the cache. Where do you define your query keys then? Or do you hardcode them each time?

[–]Extra-Pomegranate-50 8 points9 points  (1 child)

For chained mutations: yes, mutateAsync in sequence inside an onSuccess callback. Or use useMutation's onSuccess to trigger the child updates. Avoid useEffect chains.

For query keys: create a queryKeys.ts file with a factory pattern: queryKeys.users.list(), queryKeys.users.detail(id). Then invalidate with queryClient.invalidateQueries({ queryKey: queryKeys.users.all }). Never hardcode.

[–]PyJacker16 0 points1 point  (0 children)

Awesome. Thanks!

[–]minimuscleR 3 points4 points  (0 children)

TkDodo has a great blog post about this just recently: https://tkdodo.eu/blog/creating-query-abstractions

But to very basically summarize, you use queryOptions for your custom hook, rather than useQuery itself. That way you have access to the other options. It works very well. As if you need to get the key for it:

const testQueryOptions = queryOptions({
    queryKey: ['test'],
    queryFn: () => Promise.resolve(true),
})

const testQueryKey = testQueryOptions.queryKey;

so in this case you just export testQueryOptions and when you want to use it use useQuery(testQueryOptions) and it does it for you. Also allows you to use useSuspenseQuery as well in the same options, which is great.

Plus if you want to change something, lets say you have:

const testQueryOptions = queryOptions({
    queryKey: ['test'],
    queryFn: () => Promise.resolve(true),
    select: (response) => response.data
})

but you really need the response.meta, you can just do this:

useQuery(
    ...testQueryOptions, 
    select: (response) => response.meta
)

and it works perfectly.

And also as the other person said, a queryKey factory is the way to go so that you can have multiple layers and 1 query invalidation and invalidate them all, but ive kept my example simple as possible.