Hello
I am trying to use the useQuery
hook with react-select
: AsyncSelect
, and I need a promise to load options asynchronously.
Is it possible to get a promise with this hook ?
The only way I found is to trigger a refetch and use the promise, but it's not really good because it doesn't use the cache...
If by options, you mean an input similar to the react-select: AsyncSelect documentation.. perhaps useLazyQuery would fit your use case perfect.
useLazyQuery doesn't execute when the component is first rendered as it does with useQuery. useLazyQuery provides a function to execute the query a later time.
Input with Search
const LOAD_OPTIONS = gql`
query ($searchTerm: String!) {
options (title: $searchTerm) {
id
title
}
}
`
function OptionsInput() {
const [ loadOptions, { loading, error, data } ] = useLazyQuery(LOAD_OPTIONS)
return (
<input
placeholder='Search for option'
onChange={e => loadOptions({ variables: { searchTerm: e.target.value } }) }
results={data} // find a way to render results
/>
)
}
Thanks for this, it seems a lot better. I'll try it :)
I just made a try and it doesn't help for my case actually.
The AsyncSelect
component is waiting for a promise in the loadOptions
prop.
When I make a change in the input, this function is triggered and it takes the query data at that time. But it's not accurate because it's the previous data, as request didn't finished running.
It seems I can't wait for the request to end before returning the promise to the AsyncSelect
component.
My current solution is to use the default Select
component and handle the async manually.
I through together an example on CodeSandbox, take a look and let me know if it fits your use case:
Yes, that's what I was saying in my comment, I did this finally.
But it doesn't use the AsyncSelect
component but the default Select
component.
Actually, the AsyncSelect
has the loading part built-in, and avoid to write some asynchronous related logic.
But it's using a Promise, and Apollo useQuery
and useLazyQuery
do not send back a Promise.
So I can't wait data from the query, before passing it to AsyncSelect
For now, I made it with the classic Select
component, and it's fine. But can be improved :)
You could probably make this work using the onCompleted
option callback that you can provide to useQuery
, which fires when the query is complete.
@caderitter onCompleetd
is available in useLazyQuery
?
@caderitter
onCompleetd
is available inuseLazyQuery
?
Yes, it works. Here is an example.
function getProductData(data: any) {
return get(data?.viewer?.user, 'products.edges')
}
export default function BuildYourOwnPage() {
const [ products, setProducts] = useState([]);
const [
getProducts,
{ called, data, loading, error },
] = useLazyQuery(PRODUCT_CATALOG_QUERY, {
variables: {
category: 'wine',
},
fetchPolicy: 'network-only',
onCompleted: (d) => setProducts(getProductData(d))
});
useEffect(() => {
getProducts();
}, []);
return (
<Container>
<BreadCrumbs />
<BuildYourOwnTopNavContainer >
<BuildYourOwnHeaderTextContainer>
<BuildYourOwnHeaderText>Build your own 6-Pack</BuildYourOwnHeaderText>
<ProductResultsCount>{products.length} Results</ProductResultsCount>
</BuildYourOwnHeaderTextContainer>
<CategoryTabs getProducts={getProducts}/>
<Spacer/>
</BuildYourOwnTopNavContainer>
{called && loading && <Loading />}
{!loading && get(data?.viewer?.user, 'products') &&
<ProductCatalog products={products}/>}
</Container>
);
}
@hwillson adding the same API interface that useMutation
has to useLazyQuery
might be a nice win for 3.0
Just came across that issue, and just wanted to add my 2莽. it would be really handy to get a promise from the lazyquery call.
You know it just feels right to be able to handle the response in the context of the query
loadData().then(data => console.log())
onCompleted
can not solve different scenarios if you have for examples two functions, which are calling the query
const callOne = () => loadData().then(// do this)
const callTwo = () => loadData().then(// do that)
It makes sense to use the same query, but you probably want to execute different tasks in every scenario.
For folks coming across this later who need async/await on useLazyQuery
you can just use the apollo client directly with the useApolloClient
hook. The client's query
method returns a promise per the docs. You can then save whatever is needed from the query via react's useState
hooks.
import { useApolloClient } from "@apollo/client";
import react, {useState} from "react";
const LOAD_OPTIONS = gql`
query($searchTerm: String!) {
options(title: $searchTerm) {
id
title
}
}
`;
function OptionsInput () {
const client = useApolloClient();
const [results, setResults] = useState([]);
return (
<input
placeholder="Search for option"
onChange={async (e) => {
const { data } = await client.query({
query: LOAD_OPTIONS,
variables: { searchTerm: e.target.value },
});
setResults(data.options);
}}
results={results} // find a way to render results
/>
);
}
@Jakobo
Your solution is good for me!!
Thanks for the sharing your knowledge :)
But I don't know why useLazyQuery
does not support async await.
@hwillson
Is there plan for supporting async await at useLazyQuery
?
Thank you all for these great solutions !
For those using apollo-boost, I had to import useApolloClient
from @apollo/react-hooks
for Jakobo's solution to work. Otherwise, great work!
Any news on that? The solution from @Jakobo can be useful but break a little the code base cleaning and to use the correct function provided by Apollo is more correct in my opinion! Why not provide a returned promise? This is also perfectly compatible with the actual functionality and all scenarios are covered without workaround, more of this Mutation lazy query already provide this feature.
You can follow this
For some reason, refetch function return a promise, so the thing is to use const query = useQuery({ skip: true })
(enable skip
to not call it immediately) and after you can call const result = await query.refetch()
Why refetch returns a promise and lazy query doesn't ? Mystery
in case anyone is looking, here is what is looks like using the useApolloClient
hook with AsyncSelect
:
import React from 'react';
import AsyncSelect from 'react-select/AsyncSelect';
const AsyncSelectInput = () => {
const fetchOptions = async () => {
const { data } = await client.query({
query: QUERY
});
return data ? data.map(d => ({value: d, label: d})) : [];
}
return (
<AsyncSelect
cacheOptions
defaultOptions
loadOptions={fetchOptions}
/>
)
};
Most helpful comment
For folks coming across this later who need async/await on
useLazyQuery
you can just use the apollo client directly with theuseApolloClient
hook. The client'squery
method returns a promise per the docs. You can then save whatever is needed from the query via react'suseState
hooks.