This document compiles tips and conventions used in our codebase for using vue-query
consistently and
effectively.
vue-query
unifies data fetching in apps.
It helps to avoid writing repetitive code by implementing most what is usually needed internally.
Those things include:
- Handling data fetching state (loading, loaded, failed states).
- Caching data.
- Automatic retries.
- Simplified pagination and infinite loading.
- Support for optimistic updates.
- And more.
There are some assumptions in vue-query
that you need to be aware of to use it effectively and
to avoid surprises.
- Cache in
vue-query
is global. Be careful naming yourqueryKey
s - if you use the same key for different data you might end up with results that you are not expecting. vue-query
usesstale-while-reinvalidate
caching strategy. Once you fetched data once and then try to use it somewhere else in the app later (assuming it wasn't garbage collected) it'll be made available right away for you, and (assuming it's stale) it will be refetched and updated in the background. This allows you to avoid loading states while keeping data fresh.vue-query
usesstaleTime
of0
by default. That means that once data has been fetched it'll be marked as stale right away - so next time it's accessed it will be refetched. You can customize this value depending on the case.
Most often you will use useQuery
or useInfiniteQuery
to create your queries.
You can use those directly in components, other composables or create composable just for that query. Creating composable is generally good idea if you'd like to use it in multiple places.
When creating composables for queries put them in src/queries
directory.
In simplest case (no pagination) all you need for your query is queryKey
and queryFn
.
Make sure that your queryKey
can react to Ref
s or getters. If you pass non-reactive value (INCLUDING props)
query will not update as it will only know about the initial value.
import { MaybeRefOrGetter } from "vue";
function useProposalsQuery({
spaceId,
searchQuery,
}: {
spaceId: MaybeRefOrGetter<string>;
searchQuery: MaybeRefOrGetter<string | null>;
}) {
return useQuery({
queryKey: ["proposals", spaceId, "list"],
queryFn: () => {
return getProposals(toValue(spaceId), toValue(searchQuery));
},
});
}
In this case when spaceId
or searchQuery
changes so will queryKey and entire query will update.
Then it can be used like this in your component:
const props = defineProps({
spaceId: string,
});
const searchQuery = ref("");
const { data, isPending, isError } = useProposalsQuery({
// NOTE: spaceId: props.spaceId passes plain value so it won't work as expected! Convert it to Ref!
// When digging for deeper value `toRef(() => props.proposal.space.id)` can be used.
spaceId: toRef(props, "spaceId"),
searchQuery,
});
Queries return many variables that can be used in the UI, but out of those it's recommend to use at least those:
data
- data that you are aiming to fetch. It can contain data from cache.isPending
- will be true if there is no data available to show (no cached data and nothing fetched yet).isError
- will be true if there is no data available to show and request to fetch it has failed (after configured retries).
It's heavily recommended to use all 3 to make sure UI handles all cases:
- If
isPending
is true show loading state. - Else, if
isError
is true show error message. - Else display
data
.
Most of those should apply to all composables, but are especially important here.
- Use
MaybeRefOrGetter
for composable inputs per official recommendation. - Unless your composable has only few input arguments use object for arguments (
useProposals({ spaceId, filters, query })
vsuseProposals(spaceId, filters, query)
). In second case the order is not obvious. - Return all properties returned by
useQuery
oruseInfiniteQuery
from composable not to limit its capabilities.
- Creating helpers to create queryKeys.
- Invalidating data.
- Optimistic updates.