How to share query state using hooks?
depsimon opened this issue · comments
What is the best way to share a query state across multiple components using hooks?
My use case is to have a useProduct
hook that encapsulates the logic and use it in multiple components.
Here's how I'd like it to work.
// In `views/product.vue`
const { data: productQuery, execute: fetchProduct } = useProduct({ id: route.params.productId })
await fetchProduct()
// In `views/product/comments.vue`
const { data: productQuery } = useProduct({ id: route.params.productId })
// productQuery is loaded from the parent view, no need to execute the query again.
// In `views/product/edit.vue`
const { data: productQuery, execute: fetchProduct } = useProduct({ id: route.params.productId })
const onSubmit = () => {
// submit edit form
// re-fetch product, will update state in the hook and thus in `views/product.vue`
fetchProduct()
})
Here's an attempt to such a hook
import { useQuery } from 'villus';
import { useRoute } from 'vue-router';
import {
type ProductFindQuery,
type ProductFindQueryVariables,
ProductFind,
} from '@/graphql/schema';
import { NotFoundHttpError } from '@/utils/errors';
const { data, error, isDone, isFetching, execute } = useQuery<
ProductFindQuery,
ProductFindQueryVariables
>({
query: ProductFind,
cachePolicy: 'network-only',
fetchOnMount: false,
});
export const useServiceProduct = async () => {
const route = useRoute();
if (data.value && !!data.value?.productById) {
throw new NotFoundHttpError('This product does not exist', {
backTo: { name: 'ProductList' },
title: 'Product not found',
});
}
return {
data,
error,
isDone,
isFetching,
execute,
};
};
But this triggers the "Cannot detect villus Client, did you forget to call useClient
?" warning because useQuery is used outside the hook function.
What's the best way to implement this? I'd like to avoid using Pinia
Hello, sorry it took a while to get a look at this.
You can take advantage of provide/inject
API here.
a quick implementation of this would look like this:
const PRODUCT_INJECTION_KEY = `PRODUCT_CONTEXT`;
// or this if you want to inject multiple instances at the same time
// const PRODUCT_INJECTION_KEY = (id) => `product_fetched_${id}`;
function useProduct({ id }) {
const existingCtx = inject(PRODUCT_INJECTION_KEY, null);
if (existingCtx) {
return existingCtx;
}
// initialization logic for `useQuery`
const queryCtx = useQuery(...);
// ...
provide(PRODUCT_INJECTION_KEY, queryCtx);
return queryCtx;
}
I'm using this in production and it works quite well, let me know if there are still difficulties with this. I will try to add a similar example to the docs.
This is exactly what I needed it seems. Thanks for your time :)