apollo-client: Using useQuery with pollInterval triggers onCompleted only once

Intended outcome:

I would expect the onCompleted callback to be fired after every poll.

Actual outcome:

onCompleted is being fired only once. First time the query is made.

How to reproduce the issue:

const { data, stopPolling } = useQuery(QUERY, {
  variables: {...},
  fetchPolicy: 'network-only',
  pollInterval: 1000,
  onCompleted: () => console.log('called')
})

Versions

npmPackages: @apollo/react-common: ^3.0.1 => 3.0.1 @apollo/react-hooks: ^3.0.1 => 3.0.1 apollo-cache-inmemory: ^1.3.5 => 1.3.11 apollo-client: ^2.6.4 => 2.6.4 apollo-link: ^1.2.3 => 1.2.4 apollo-link-context: ^1.0.10 => 1.0.10 apollo-link-error: ^1.1.1 => 1.1.2 apollo-link-http: ^1.5.5 => 1.5.7 apollo-link-logger: ^1.2.3 => 1.2.3 apollo-server-koa: ^2.1.0 => 2.2.4 apollo-utilities: ^1.3.2 => 1.3.2 react-apollo: ^2.2.4 => 2.3.2

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 48
  • Comments: 42 (7 by maintainers)

Commits related to this issue

Most upvoted comments

Setting notifyOnNetworkStatusChange to true solved the issue in my case.

Also, @dominik-myszkowski , setting fetchPolicy: 'network-only' only triggers the onCompleted once. I can only get it working by setting notifyOnNetworkStatusChange: true

This issue persists in 3.5.8.

It’s baffling how issues like this can go multiple years without being addressed in such a widely used library… And this is not the first one I’ve seen.

I suggest reopening this issue.

This should now be resolved in @apollo/client@3.5.0. Let us know if you notice any issues. Thanks!

@ajhool I have just tested it again and I can confirm that the query is being called multiple times. I can see it in the network traffic every second, but the onCompleted is being called only once.

I created a runnable reproduction of this issue (use branch polling-oncompleted-not-called): https://github.com/dylanwulf/react-apollo-error-template/tree/polling-oncompleted-not-called

Setting notifyOnNetworkStatusChange to true solved the issue in my case.

This worked for me. Setting ‘network-only’ did not.

I’m running into this issue as well. Even with fetchPolicy: 'no-cache' the onCompleted handler is only being called once.

Right, absolutely, it should! That’s the bug. I’ve commented merely to point out that setting notifyOnNetworkStatusChange isn’t a workable workaround in many situations and that folks should be aware of that drawback before applying it willy nilly, as it causes the whole tree below the hook to rerender on every interval.

The issue with setting notifyOnNetworkStatusChange is that it will rerender for every poll interval (as documented). This is probably not what you want and you’d want to rerender only when the data changes.

This could be the culprit?

https://github.com/apollographql/apollo-client/blob/6bc9fdcfc064bd60533794f0ef5aeed45f7ad537/src/react/data/QueryData.ts#L433

It seems onCompleted only runs when data has changed, so if you are polling and no data changes occur then it will not fire.

Ideally there could be an extra prop to always call. Or maybe an alternative function prop, onCompletedAlways?

Yup, can confirm. Still happening in 3.3.6. I resolved the issue with notifyOnNetworkStatusChange , though it’s like using a baseball bat to clean the dishes.

The only other alternative I can come up with is using a useEffect, and refetch.

same issue, in addition to onCompleted, my react table is not reflecting changes even though i see the calls in network. notifyOnNetworkStatusChange is making everything re-render on each poll so that’s not really a solution i can use.

A workaround for our case was to add fetchPolicy: 'no-cache', to the query options. Thus:

const { data, stopPolling } = useQuery(QUERY, {
  variables: {...},
  fetchPolicy: 'network-only',
  pollInterval: 1000,
  onCompleted: () => console.log('called'),
  fetchPolicy: 'no-cache',
})

Obviously this means you will bypass the client-side cache but it will ensure the completion hook is triggered every time.

@akikoskine, @brainkim, can we reopen this issue?

The same is true for onError. It’s called for the first error but is not called if later poll attempts have errors. The workarounds do not help me.

Bumping this because I ran into it. I was using pollInterval to check on a job, and set it to zero once the job is done (since there won’t be any updates afterwards). I have fetchPolicy: "no-cache" and the data I fetch definitely changes, but onCompleted only runs once. As other have mentioned, notifyOnNetworkStatusChange: true causes onCompleted to rerun.

The fact that onCompleted runs only once is unintuitive and undesireable. The fact that its fixed by setting a seemingly unrelated option notifyOnNetworkStatusChange changes the behavior is very unintuitive.

@alessbell @hwillson @ajhool Please consider this useCase.

fetchPolicy is cache-and-network. Query goes and data is present in cache. onCompleted is called.

Simultaneously Network call goes (Because fetchPolicy is cache-and-network). In the network call, data has changed which causes cache to update BUT, onCompleted will never be called with this new data. Isn’t this not a valid usecase where onCompleted should have been called?

Additional: onCompleted won’t be called the second time because of this check -> previousResult?.networkStatus !== result.networkStatus. networkStatus doesn’t change in our above useCase when cache is revalidated with network request.

Please check

This has proven to be the most reliable combination for us. note the separate user queries and the manual starting and stoping of polling. this bypasses a second bug in which stop polling does not reliably work with pollinterval

  const [completed,setCompleted] = useState(false);

  const { data: versionData, loading: versionLoading, stopPolling, startPolling } = useQuery(QUERY, {
    variables: {
      id: id,
    },
  })

useEffect(() => {
    if (versionData) {
        ...
    }, [versionData])

useEffect(() => {
    // versionRefetch()
    if (!completed) {
      startPolling(4000)
    } else {
      stopPolling()
    }
    return () => {
      stopPolling()
    }
  }, [stopPolling, startPolling, completed])
  

I don’t think this is fixed @hwillson

Here’s a codesandbox repro. Making notifyOnNetworkStatusChange: true fixes it (as it always did), but when it’s false, the original issue still exists.

Should this be re-opened?

For what it’s worth, I’ve sort of resolved the issue caused by this workaround for the time being by chucking the polling useQuery into a dead end of the tree, so the rerendering isn’t annoying. In there I then use makeVar which is then used in the typePolicies of the InMemoryCache, like this:

  {
    cache: new InMemoryCache({
      typePolicies: {
        Manuscript: {
          fields: {
            _currentRoles: {
              read(existing, { cache, args, readField }) {
                const currentRoles = currentRolesVar()
              },
            },
          },
        },
      },
    }

It’s quite the detour, but it works, so hopefully it’s useful for someone else too.

Have you checked the network traffic? I believe that it does not actually poll, as opposed to executing onComplete only on the first query.