Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

useLazyQuery default options permanently overridden by execute options #11557

Closed
cjskill opened this issue Feb 1, 2024 · 7 comments
Closed
Labels
ℹ needs-more-info Needs more information to determine root cause

Comments

@cjskill
Copy link

cjskill commented Feb 1, 2024

Issue Description

I am using the useLazyQuery hook. I want to run this query once on component mount, then handle those results.

After a user interaction I want to startPolling the same query, but handle those results differently than the results from the component mount query.

Since I can't pass an onCompleted callback as an argument to the startPolling function I am using the default options to declare the onCompleted callback.

However, when I call the execute function, the default onCompleted option is overridden with the initial mount onCompleted option for all subsequent polls.

Example:

const [getSomething, {startPolling, stopPolling }] = useLazyQuery(
    SOME_QUERY,
    {
      onCompleted: () => {
        console.log("onCompleted Default");
      },
    }
  );

  React.useEffect(() => {
    getSomething({
      onCompleted: () => {
        console.log("onCompleted Mount");
      },
    });
  }, []);

  return (
    <button
      onClick={() => {startPolling(1000)}}
    >
      Start polling
    </button>
  );

Console after button click:

onCompleted Mount
onCompleted Mount
onCompleted Mount
...

Expected Console after button click:

onCompleted Mount
onCompleted Default
onCompleted Default
...

Link to Reproduction

https://codesandbox.io/p/sandbox/apollo-uselazyquery-9lkpv2?file=%2Fsrc%2FApp.js%3A20%2C15-26%2C13&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522cls3ivhee00063b6kqvnhpuhm%2522%252C%2522sizes%2522%253A%255B70%252C30%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522cls3ivhed00023b6ktebjao3a%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522cls3ivhed00033b6k2g6qnuq9%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522cls3ivhed00053b6kim02on3k%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522cls3ivhed00023b6ktebjao3a%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522cls3ivhed00013b6k0f0nbpu1%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%252C%257B%2522id%2522%253A%2522cls3kikp800023b6kkctn159o%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A20%252C%2522startColumn%2522%253A15%252C%2522endLineNumber%2522%253A26%252C%2522endColumn%2522%253A13%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.js%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522cls3ivhed00023b6ktebjao3a%2522%252C%2522activeTabId%2522%253A%2522cls3kikp800023b6kkctn159o%2522%257D%252C%2522cls3ivhed00053b6kim02on3k%2522%253A%257B%2522id%2522%253A%2522cls3ivhed00053b6kim02on3k%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522cls3ivhed00043b6kb4rugoti%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522activeTabId%2522%253A%2522cls3ivhed00043b6kb4rugoti%2522%257D%252C%2522cls3ivhed00033b6k2g6qnuq9%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522cls3ivhed00033b6k2g6qnuq9%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D

Reproduction Steps

Open console, click Start polling button.

Observe in the console that the onCompleted default callback is overridden by the execute onCompleted callback for every subsequent query.

@apollo/client version

3.9.2

@cjskill cjskill changed the title useLazyQuery options overridden by execute options useLazyQuery default options overridden by execute options Feb 1, 2024
@cjskill cjskill changed the title useLazyQuery default options overridden by execute options useLazyQuery default options permanently overridden by execute options Feb 1, 2024
@jerelmiller
Copy link
Member

Hey @cjskill 👋

I'd like to ask what your use case is for this particular thing you said:

After a user interaction I want to startPolling the same query, but handle those results differently than the results from the component mount query.

I'm curious what you're doing with those results and why the distinction is necessary. Could you provide some more info here on what you're trying to accomplish?

@jerelmiller jerelmiller added the ℹ needs-more-info Needs more information to determine root cause label Feb 1, 2024
@cjskill
Copy link
Author

cjskill commented Feb 1, 2024

@jerelmiller

My API response contains a status (pending/success/failure).

For the first API call I want to handle each of these statuses respectively:

  • pending: Start polling until success/failure
  • success: Do nothing
  • error: Do nothing

For all subsequent requests (from polling) I want to handle each of these statuses respectively:

  • pending: Continue polling (Do nothing)
  • success: Show success message
  • error: Show error message

I basically have a sidebar where you can open different items (React components being mounted and unmounted), and when an item is opened I want to check for results. Within each of these items you can mutate the data, which is then received via an async API (polling).

But I don't want to handle success/error statuses on mount because every time I switch between items a success message or an error message will show. I only want to show these messages after a mutation has happened -> poll for results -> then display success/error.

@jerelmiller
Copy link
Member

@cjskill I think I have an idea of what you're saying. If I were to restate, that initial request is used to determine the status of the thing you're querying. If its in a certain state (pending), you want to start polling on that item repeatedly until that item has transitioned to a more final state (success or error). That transition to the final state is triggered by some mutation elsewhere in your system and (I would guess) handled async by your backend, so you're using polling to check the item until its been processed.

Does this sound roughly correct?

And for this requirement:

I don't want to handle success/error statuses on mount because every time I switch between items a success message or an error message will show.

I'm guessing you're showing some kind of toast message or something and you want to make sure you only do this the first time it transitions from pending -> success/error correct? Otherwise I could see how this could get very annoying for your users 😆


Assuming yes, I want to challenge your thinking and consider switching around your solution a little bit.

Here you're thinking about it in terms of what triggers the query as a means to orient your code around, but as you're seeing, you're running into some technical issues preventing your desired result. To be quite honest, even if we build a way to "reset" that onCompleted value into the library, you're still likely to end up with some awkward code to make this work.

Instead, I'd have you consider thinking about this from the data itself and writing code to orient around that. I think you'll find this lends to a less awkward solution. With this approach, you're reacting instead to changes in your data, rather than the timing of when/where the query completes.

Given my understanding of the problem and this way of thinking, I'd switch to a useQuery instead since you want to execute that query unconditionally on mount anyways. No need to execute that query from a useEffect. Instead I'd use the useEffect to sync with your data changes to handle when to start/stop polling and when to trigger your success/error UI. This means that you can react to changes in that data however they may happen, whether its from a cache update from a different part of your system, or whether it is a change in that data from the polling itself. No need to use onCompleted for this.

I'd consider using a ref to store the previous status as well since you only want to trigger that UI when you're transitioning from pending to a final state.

Here is roughly that idea in code:

// I'm going to use a hypothetical query for this situation that contains a `status`
// field that you can check against
const GET_THING = gql`
  query GetThing($id: ID!) {
    thing(id: $id) {
      id
      status
    }
  }
`;

function Thing({ id }) {
  const { data, startPolling, stopPolling } = useQuery(GET_THING, { variables: { id } });
  const status = data?.thing?.status;
  // Here we can use a ref to store the previous status.
  // This ensures we only show the success/error toast when we've transitioned 
  // _from_ the right state
  const previousStatusRef = useRef(status);

  useEffect(() => {
    if (status === 'pending') {
      startPolling(1000);
    } else if (status === 'success' && previousStatusRef.current === 'pending') {
      stopPolling();
      showToast('Success!');
    } else if (status === 'error' && previousStatusRef.current === 'pending') {
      stopPolling();
      showToast('Error :(');
    }

    previousStatusRef.current = status;
  }, [status]);

  return // ... whatever you show otherwise
}

We typically do not recommend doing data syncing in onCompleted on prefer you work with the data property returned from these hooks because that value will be kept up-to-date with the cache and avoids you needing to sync that state between Apollo and your component.

Feel free to arrange the code above however you want, but I wanted to show you the general idea that I'm presenting here. Let me know if this kind of approach works for your use case, and if not, I'd be happy to collaborate further on a solution that works for your use case.

@cjskill
Copy link
Author

cjskill commented Feb 1, 2024

@jerelmiller

This is great! Definitely works for my use case. Good to know to rely on the data property for cache/sync purposes.

Thank you for the prompt and detailed responses!

@cjskill cjskill closed this as completed Feb 1, 2024
Copy link
Contributor

github-actions bot commented Feb 1, 2024

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

@jerelmiller
Copy link
Member

@cjskill you're very welcome! Glad that works well for you!

Copy link
Contributor

github-actions bot commented Mar 3, 2024

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 github-actions bot locked as resolved and limited conversation to collaborators Mar 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
ℹ needs-more-info Needs more information to determine root cause
Projects
None yet
Development

No branches or pull requests

2 participants