apollo-client icon indicating copy to clipboard operation
apollo-client copied to clipboard

useLazyQuery refetch will never resolve the promise even network request done

Open jackykwandesign opened this issue 3 years ago • 11 comments

Query hook

	const [getTopicById] = useLazyQuery<
		{ topic: Topic },
		{ topicId: string }
	>(GET_TOPIC_DETAIL_BY_ID_QUERY,{
		fetchPolicy:'cache-and-network',
// or network-only
	})

Function to call the query

		try {
			console.log("start handleGetTopicDetail")
			const result = await getTopicById({
				variables:{
					topicId
				}
			})
			console.log("result handleGetTopicDetail", result)
			console.log("end handleGetTopicDetail");
			
		} catch (error) {
			console.log("error", error);
		}

Intended outcome: I expect it will run the query, then resolve the promise, if error then throw it and print to the console

Actual outcome: When the query run the first time, it is good whatever the fetch policy is. image

When run 2nd time or more: The query network request is done, but never resolve Promise to pass the await. You can see the result never print image image

How to reproduce the issue:

Versions

  System:
    OS: Linux 5.4 Ubuntu 20.04.2 LTS (Focal Fossa)
  Binaries:
    Node: 16.13.1 - ~/.nvm/versions/node/v16.13.1/bin/node
    Yarn: 1.22.15 - ~/.yarn/bin/yarn
    npm: 8.1.2 - ~/.nvm/versions/node/v16.13.1/bin/npm
  npmPackages:
    @apollo/client: ^3.5.6 => 3.5.6 

jackykwandesign avatar Jan 09 '22 06:01 jackykwandesign

This behavior also happen in "network-only" fetch policy And interesting is, if i send the 2nd or more request with same function, the 2nd will work while 1st one is jammed

I do further test and add a Dummy button for re-fetch. The useEffect is first load, the Dummy button is for re-fetch

	useEffect(() => {
		param.topicId ? handleGetTopicDetail(param.topicId) : setTopic(null)
	}, [param.topicId])

	return (
		<Fragment>
			<button
				onClick={async () => {
					console.log("ress start")
					const res = await getTopicById({
						variables: {
							topicId: param.topicId,
						},
					})
					console.log("ress end", res)
				}}
			>
				Dummy
			</button>
			{topic && (
				<Fragment>
					<TopicDetailContainer
						topic={topic}
						onRefresh={async () =>
							await handleGetTopicDetail(topic.id, true)
						}
					/>
				</Fragment>
			)}
		</Fragment>
	)

The 1st request is OK, and load data i need The 2nd re-fetch is jammed image

If i send 3rd re-fetch, it can pass again image

jackykwandesign avatar Jan 09 '22 06:01 jackykwandesign

We are having this same issue.

acrtz avatar Jan 18 '22 01:01 acrtz

Experiencing the same on 3.5.7

StringVariable avatar Jan 20 '22 00:01 StringVariable

don't know if this bug solved or not, below is a customLazyQuery work perfectly for async, credit to someone in other threads (sorry i forget who and where but it is useful, thank you !

import { DocumentNode, OperationVariables, useApolloClient } from '@apollo/client';
import { useCallback } from 'react';

  
export function useCustomLazyQuery<TData = any, TVariables = OperationVariables>(_query: DocumentNode) {
    const client = useApolloClient();
    return useCallback(
        (variables: TVariables) =>
            client.query<TData, TVariables>({
                query: _query,
                variables: variables,
                fetchPolicy:'network-only',
            }),
        [client, _query]
    );
}

jackykwandesign avatar Feb 25 '22 07:02 jackykwandesign

We are having the same issue on 3.3.20

ntofigzade avatar Oct 06 '22 15:10 ntofigzade

Same issue - although ours is also hanging on the first request. Returned updated data is still available from the hook such as data, error, loading etc.

leepowellnbs avatar Oct 28 '22 12:10 leepowellnbs

Investigated this a little over the weekend, I think our issue is down to the Apollo client changing after our component is rendered (from an unauthed client to an authed one). Could be worth checking that your client is memoized or similar?

leepowellnbs avatar Oct 31 '22 11:10 leepowellnbs

having the similar issue. useLazyQuery returns a tuple with a LazyQueryExecFunction, which returns a promise that is never resolved. Apollo client v3.7.3

RobinTail avatar Jan 03 '23 10:01 RobinTail

The solution offered by @jackykwandesign works as a good bypass. Thank you.

RobinTail avatar Jan 03 '23 12:01 RobinTail

I managed to workaround this issue - TL;DR: I used a combination of notifyOnNetworkStatusChange: true and onCompleted options instead of awaiting the lazy query to resolve.

I'm implementing a debounceLazyQuery where I need to track of my own loading flag from the beginning of debounce until the call is actually made. This got tricky when the lazy query call never resolves - even though the API correctly responds - I don't know when the loading is finished.

Turns out, setting notifyOnNetworkStatusChange to true makes the lazy query trigger onCompleted when the API response arrives, passing the same data fetch would have been resolved with (?seemingly) as a parameter.

Here's some code for the reference:

const [data, setData] = useState<Q>();
const [isLoading, setLoading] = useState(false);
const [fetch, queryResult] = useLazyQuery<Q, V>(query, queryOptions);
const debounceTimeout = useRef<any>();

...

const debouncedFetch: QueryTuple<Q, V>[0] = useCallback(
    (options?: QueryLazyOptions<V>) =>
      new Promise(resolve => {
        const opts = {
          notifyOnNetworkStatusChange: true, // <-- add this to make onCompleted called when API responds
          ...options,
          onCompleted: (nextData: Q) => { // <-- use onComplete to handle the response
            setLoading(false);
            setData(nextData);
          },
        };
        setLoading(true);
        clearTimeout(debounceTimeout.current);
        debounceTimeout.current = setTimeout(async () => {
          resolve(fetch(opts)); // <-- this fetch may never resolve - the issue
        }, debounceMs);
      }),
    [debounceMs, fetch],
  );

sstano avatar May 30 '23 22:05 sstano

Hey @jackykwandesign 👋

I know its been some time since you've opened this issue so appreciate the patience here! Without a reproduction its difficult to tell if this has already been fixed, but I'd like to point you to a couple fixes that dealt with unresolved promises in useLazyQuery. These fixes may or may not be related to the issue you were seeing.

3.7.4 fixed an issue via https://github.com/apollographql/apollo-client/pull/10427 that rejects promises when the component unmounts. Given what I see in your code sample, I don't think this has to do with unmounting, but there is a chance the same underlying mechanism was at play.

Unfortunately my original approach in 3.7.4 was met with a bit of pushback as aborting the request and rejecting the promise proved to be unpopular for various reasons. 3.7.11 tweaked this behavior via https://github.com/apollographql/apollo-client/pull/10698 so that it allows the promise to resolve naturally, regardless of whether the component had been unmounted or not.

I'd love if you'd be able to try 3.7.11 or later and see if your issue has been resolved. Thanks!

jerelmiller avatar Feb 15 '24 00:02 jerelmiller

We're closing this issue now but feel free to ping the maintainers or open a new issue if you still need support. Thank you!

github-actions[bot] avatar Mar 16 '24 05:03 github-actions[bot]

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. For general questions, we recommend using StackOverflow or our discord server.

github-actions[bot] avatar Apr 16 '24 00:04 github-actions[bot]