T
TanStack3y ago
optimistic-gold

Some best practices guidelines and advice with Vue

I've only recently started using this package (not sure if I should call it vue-query or react-query) but I just wanted to know best practices in terms of query keys and data... 1. Should we try to re-use queries from composables? Or should queries be more specific to the component itself? 2. When typing the keys for the query, should the query key expect multiple Ref<> values from the component when the query is created or should my composable return these refs to the component? 3. Is it better to use ref or reactive for query key values? 4. Is there a nice way to store pagination data outside of the query cached data? So that when replacing data in queries, I know everything is just a basic array of data or is there a better way of managing query structure? I haven't gotten to this yet but we use a lot of broadcasting and need to be able to replace individual values within data.
26 Replies
optimistic-gold
optimistic-goldOP3y ago
5. Can I pass reactive/ref values to a query and not have to worry about unwrapping them?
flat-fuchsia
flat-fuchsia3y ago
yoyo almost positive its impossible to pass a ref to useQuery in any form
optimistic-gold
optimistic-goldOP3y ago
export const taskQueries = {
allTasksBetween: (user: string, start: Ref<Date>, end: Ref<Date>) => {
return useQuery(['tasks', user, start, end], () => fetchTasks(user, start, end), {
placeholderData: [],
});
},
};
export const taskQueries = {
allTasksBetween: (user: string, start: Ref<Date>, end: Ref<Date>) => {
return useQuery(['tasks', user, start, end], () => fetchTasks(user, start, end), {
placeholderData: [],
});
},
};
I tried this and I think it works but my UI broke for other reasons haha but starting to think that should just be in a reactive object
flat-fuchsia
flat-fuchsia3y ago
2) i'd check out this link (All About React Query (with Tanner Linsley) — Learn With Jason), but the answer is useQuery should likely be defined in a composable and reused the useQuery function internally calls the useBaseQuery fn, the signature looks like this arg1: | TQueryKey | UseQueryOptionsGeneric<TQueryFnData, TError, TData, TQueryKey>, arg2: | QueryFunction<TQueryFnData, UnwrapRef<TQueryKey>> | UseQueryOptionsGeneric<TQueryFnData, TError, TData, TQueryKey> = {}, arg3: UseQueryOptionsGeneric<TQueryFnData, TError, TData, TQueryKey> = {}, so looks like can't pass ref as queryKey but could be wrong at first glance
optimistic-gold
optimistic-goldOP3y ago
So it should always be a reactive, POJO, array or string etc?
flat-fuchsia
flat-fuchsia3y ago
i generally just dont think it will work hahah but haven't actually tried yet for reactive
optimistic-gold
optimistic-goldOP3y ago
I don't see what else you would pass?
flat-fuchsia
flat-fuchsia3y ago
but primitives work for sure
optimistic-gold
optimistic-goldOP3y ago
If you have things that are in the template like a search text-field, that's going to be v-model with a ref or reactive So it would make sense to pass that to the query as the key or part of the key?
flat-fuchsia
flat-fuchsia3y ago
yes, you're right but there were type issues from what I understand that prevent watch sources from being passed
optimistic-gold
optimistic-goldOP3y ago
I'm going to test it now ha I guess my main question was #4 How to best store pagination data so I know the structure of my query data is consistent
const search = ref('');
const { data, isLoading } = userQueries.allUsers(search);

export const userQueries = {
allUsers: (search: Ref<string>) => {
return useQuery(['users', search], fetchUsers, {
placeholderData: [],
});
},
};
const search = ref('');
const { data, isLoading } = userQueries.allUsers(search);

export const userQueries = {
allUsers: (search: Ref<string>) => {
return useQuery(['users', search], fetchUsers, {
placeholderData: [],
});
},
};
@mattyice78987 can confirm this works just fine Looks like it's unwrapped somewhere
optimistic-gold
optimistic-goldOP3y ago
No description
stormy-gold
stormy-gold3y ago
All hooks in vue-query accept refs reactive or plain values and they are unwrapped like in this function https://github.com/TanStack/query/blob/52764b0d461cbfe3acd7c0840c0477b834a9a0ac/packages/vue-query/src/useBaseQuery.ts#L104
GitHub
query/useBaseQuery.ts at 52764b0d461cbfe3acd7c0840c0477b834a9a0ac ·...
🤖 Powerful asynchronous state management, server-state utilities and data fetching for TS/JS, React, Solid, Svelte and Vue. - query/useBaseQuery.ts at 52764b0d461cbfe3acd7c0840c0477b834a9a0ac · Tan...
optimistic-gold
optimistic-goldOP3y ago
Amazing! So just type it appropriately and good to go!
flat-fuchsia
flat-fuchsia3y ago
sorry for the misdirection!
optimistic-gold
optimistic-goldOP3y ago
It's fine. I literally started using this yesterday and it's amazing Don't know why I haven't seen this package before
stormy-gold
stormy-gold3y ago
useQuery and useInfiniteQuery should have proper types already. There might be a problem with useMutation and useQueries. But this is type only problem. Functionality wise it should still work.
optimistic-gold
optimistic-goldOP3y ago
Probably just because I am using a composable I have to type my composable args And yeah useQuery accepts it just fine with nothing further required
stormy-gold
stormy-gold3y ago
Ad1. You can and probably should wrap your queries with your own compostables to encapsulate queryKey and queryFn, exposing only some necessary args. For tiny apps it might be overkill, but for bigger apps it will make reuse of specific resources much easier.
optimistic-gold
optimistic-goldOP3y ago
Yeah our app is huge... Hence trying to make it faster
stormy-gold
stormy-gold3y ago
Ad2. queryKey is an array which can contain primitives or nested objects and arrays. It should contain all arguments that you are passing to your queryFn. This way by changing args you will create new cache entry. And if you will try to hit old params, results can be served from cache first. How you will structure your queryKey is totally up to you. You can check this page for more details: https://vue-query-next.vercel.app/#/guides/query-keys
Vue Query
Description
optimistic-gold
optimistic-goldOP3y ago
The last piece for me before I go for a full in migration to this package is to work out the best way to handle paginated data and also nice, type safe setQueriesData so I can update multiple queries that may or may not be paginated. For example UserList (paginated) and UserSelect (select field, not paginated). We use a lot of broadcasting.
stormy-gold
stormy-gold3y ago
IMHO. For paginated user list i would go with something like ['users', page] or ['users', {page}]. For select field, since you have a lot of data (pagination) i would usually send denounced/throttled query to the server with user input as filter and include that in the query key, with a bit of staleTime. This way when you type the same thing the second time, you will skip trip to the server, and could serve it from cache. If it would not be used for some time, it will be GCed. The only question is if you need to display something when you did not type anything, but that could be the first page of users. I doubt anyone would scroll through thousands of users to pick one.
optimistic-gold
optimistic-goldOP3y ago
That was probably a contrived example and it's not that straightforward in our app. So I have the queries working fine Maybe another example would be a Task List - we have a table that lists them, paginated. Then another view that only shows for a specific date range (not paginated) So if a Task is updated somewhere else (like created and then broadcasted in), I want to update both of the above queries One is paginated, the other is not So for one, the Tasks are likely at data.items (if paginated) and the other they're just at data (if not paginated) But if the created/updated Tasks falls in the current week, well I want to put it in both queries without re-fetching Not sure if I'm explaining it poorly.
type TaskTable = {
data: TaskData[];
meta: {
total: number;
};
};
const taskTable = (page: number) =>
useQuery(['tasks', page], async (): Promise<TaskTable> => {
const response = await axios.get('/tasks', { params: { page } });

return response.data;
});

type TaskBoard = TaskData[];
const taskBoard = (start: Date, end: Date) =>
useQuery(['tasks', start, end], async (): Promise<TaskBoard> => {
const response = await axios.get('/tasks', { params: { start, end } });

return response.data.data;
});
type TaskTable = {
data: TaskData[];
meta: {
total: number;
};
};
const taskTable = (page: number) =>
useQuery(['tasks', page], async (): Promise<TaskTable> => {
const response = await axios.get('/tasks', { params: { page } });

return response.data;
});

type TaskBoard = TaskData[];
const taskBoard = (start: Date, end: Date) =>
useQuery(['tasks', start, end], async (): Promise<TaskBoard> => {
const response = await axios.get('/tasks', { params: { start, end } });

return response.data.data;
});
Being able to update both of the above queries with type safety when using setQueriesData is what I'm trying to do If that helps explain my goal 🙂 I suppose the other option is to enforce API responses so that I should expect a meta key on every response - even if not paginated
stormy-gold
stormy-gold3y ago
So there are two concepts you could use: https://vue-query-next.vercel.app/#/guides/query-invalidation https://vue-query-next.vercel.app/#/guides/optimistic-updates From what I understand you are trying to achieve optimistic update. I think in this case you need to figure out what works best for your data model. As every use case has it own quirks.
Vue Query
Description
optimistic-gold
optimistic-goldOP3y ago
Yeah but updating two queries with differing structures is what I'm trying to solve Not as simple as just applying the same updater function to both https://github.com/TanStack/query/discussions/1780 This discussion is similar to what I want to do I think I either have to have the same structure Or somehow track the structure of the query Like being able to tag if it's paginated or not

Did you find this page helpful?