apollo-client: await useLazyQuery does not return a Promise on every call

Intended outcome: I’m trying to show an alert in react-native after awaiting a lazyQuery Promise. After every await i would expect to get a response which I can process. Either with data or at least with an error.

const [email, setEmail] = useState('');

const [resetPassword, { loading }] = useLazyQuery<
  ResetPasswordData,
  QueryResetPasswordArgs
>(RESET_PASSWORD_QUERY);

const handleError = useCallback(
  (error: ApolloError) => {
    if (error.message?.indexOf("Cannot find user") !== -1) {
      Alert.alert("unknown user");
      return;
    }
    Alert.alert("default error");
  },
  []
);

const handleCompleted = useCallback(
  (data: ResetPasswordData) => {
    if (data.success) {
      Alert.alert("password has been reset");
    }
    if (data.success === false) {
      Alert.alert("default error");
    }
  },
  []
);

const onResetPassword = useCallback(
  async (mail: string) => {
    try {
      console.log("sending request");
      const response = await resetPassword({
        variables: { email: mail },
      });
      console.log("response.error", response.error);
      console.log("response.data", response.data);
      if (response.error) {
        handleError(response.error);
        return;
      }
      if (response.data) {
        handleCompleted(response.data);
        return;
      }
    } catch (err) {
      console.warn("error while resetting password", err);
      Alert.alert("common.unknownError");
    }
  },
  [resetPassword, handleCompleted, handleError]
);

<View>
  <TextBox
    onChangeText={setEmail}
    value={email}
    placeholder={t('common.email')}
  />
  <Button
    title={"reset password"}
    loading={loading}
    onPress={() => onResetPassword(email)}
  />
</View>

Actual outcome: When I change the textInput for a first time and i click the reset button, everything works as expected. If I click the button again with no textInput change, everything is still working as expected. Now if I change the textInput and click the button again, the console.log(“sending”); appears and i can see the query on my server. So the query is really fired. But the promise does not resolve. I have to click the Button again to get another resolved Promise. So somehow one Promise never returns.

The console output is:

// click button
sending request
response.error [...]
response.data [...]
// click button
sending request
response.error [...]
response.data [...]
// changing text and click button
sending request
// click button
sending request
response.error [...]
response.data [...]
// changing text and click button
sending request
// changing text and click button
sending request
// changing text and click button
sending request
// click button
sending request
response.error [...]
response.data [...]
....

How to reproduce the issue: Create view with a textInput and a Button Fire the lazyQuery by pressing the button and passing the current value await the lazyQuery and process the response promise, which is not resolving every time.

Versions System: OS: macOS 11.6.2 Binaries: Node: 16.13.0 - ~/.nvm/versions/node/v16.13.0/bin/node Yarn: 1.22.17 - /usr/local/bin/yarn npm: 8.1.0 - ~/.nvm/versions/node/v16.13.0/bin/npm Browsers: Chrome: 97.0.4692.99 Firefox: 96.0.2 Safari: 15.2 npmPackages: @apollo/client: ^3.5.7 => 3.5.7 apollo-link-scalars: ^3.0.0 => 3.0.0 apollo-upload-client: ^16.0.0 => 16.0.0

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 6
  • Comments: 24 (5 by maintainers)

Most upvoted comments

More info: this seems to only happen for me when the component using the useLazyQuery hook is run on the server and then rehydrated on the client. When i navigate to the same component client side (as opposed to refreshing the page), the lazy query correctly returns a promise.

A less than ideal workaround I found is that onCompleted and onError are still called, so you can wrap the query in a promise and then it will work as expected.

      const data = await new Promise((resolve, reject) =>
        lazyQuery(QUERY, {
          onCompleted: resolve,
          onError: reject,
        })
      )
      

Was facing this same issues with @apollo/client: "3.7.7" and react: "18.2.0". Turned out to be an issue with the way we were memoizing our ApolloClient instance that was being passed to <ApolloProvider />.

There were a variety of scenarios that caused us to create a brand new ApolloClient (like the user logging in and out). This issue was resolved by ensuring that we only ever create one instance of ApolloClient for the life of the app.

Instead of creating a new ApolloClient using the useMemo hook, we now just instantiate the ApolloClient at the top of the file and modify it as needed in useEffect hooks.

We are experiencing the same issues in 3.6.6: The Promise never returns and code after await lazyQuery(); is never reached although the Network tab clearly shows that the backend returned a valid response. This issue is preventing us from upgrading to React 18, since we did not experience such issues on React 17.

More info: this seems to only happen for me when the component using the useLazyQuery hook is run on the server and then rehydrated on the client. When i navigate to the same component client side (as opposed to refreshing the page), the lazy query correctly returns a promise.

A less than ideal workaround I found is that onCompleted and onError are still called, so you can wrap the query in a promise and then it will work as expected.

      const data = await new Promise((resolve, reject) =>
        lazyQuery(QUERY, {
          onCompleted: resolve,
          onError: reject,
        })
      )
      

Can confirm server side is not the case, we encounter this on client side in a React Native app too.

We are also experiencing the same behaviour @phebing-slashwhy described in React 17 with apollo-client 3.6.9

Is there any update on this?

With 3.5.8 the behaviour seems to be different but still not really as expected.

// changing text and click button
sending request
response.error undefined
response.data {"resetPassword": null}
// click button
sending request
error while resetting password // catch block
// click button
sending request
error while resetting password // catch block
// changing text and click button
sending request
response.error undefined
response.data {"resetPassword": null}
// click button
sending request
error while resetting password // catch block

So with the updated version an error is thrown on the second and subsequent requests when the text didn’t change.

If I change the email input and send a first request neither an error is thrown nor is there a response.error object. Either I would expect the error to be thrown on every request or to have a response.error object on every request.

I can see my backend responding with the same output on every request (“cannot find user with…”), no matter if it was the first or n th request.

Hey all 👋

Thanks for your patience on this issue! Reading through the original reported issue, I believe this may have been resolved in 3.7.4. 3.7.4 included a fix that would ensure promises don’t get stuck in a pending state when the component unmounts. While I’m not sure unmounting is your problem here necessarily, these may be closely related. 3.7.11 did alter this behavior a little bit via https://github.com/apollographql/apollo-client/pull/10698 and made it so that the promise will resolve naturally rather than reject. You’ll probably be happier with the change in 3.7.11, so please upgrade to this version to see if this fixes the issue. Thanks!

Encountered the same issue. Ended up using apollo client imperatively with useApolloClient.