apollo-client: `stopPolling` doesn't work when using React StrictMode

Intended outcome:

According to documentation, stopPolling function should stop the query polling.

Actual outcome:

After calling stopPolling(), polling continues.

How to reproduce the issue:

export default function MyComponent() {

  const {
    data: carsData,
    loading: carsLoading,
    error: carsError,
    fetchMore,
    stopPolling,
    startPolling
  } = useQuery<getCars, getCarsVariables>(GET_CARS, {
    fetchPolicy: 'network-only',
    pollInterval: 10000
  })

useEffect(() => {
    return () => {
      stopPolling() // Doesn't stop polling on unmount
    }
  }, [])

  return (
    <>
      <Button
        onClick={() => {
          stopPolling() // Doesn't stop polling on click
        }}
      >
        STOP
      </Button>
      
     {/* MyComponent Render Stuff  */}

    </>
  )
}

Versions

System: OS: macOS 10.15.6 Binaries: Node: 14.12.0 - /usr/local/bin/node Yarn: 1.22.5 - /usr/local/bin/yarn npm: 6.14.8 - /usr/local/bin/npm Browsers: Chrome: 86.0.4240.111 Firefox: 81.0.2 Safari: 14.0 npmPackages: @apollo/client: ^3.2.5 => 3.2.5

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 12
  • Comments: 20 (4 by maintainers)

Most upvoted comments

I’m still seeing this in v3.4.10.

I’m using this as a workaround:

const { data, loading, client } = useQuery(GetData, {
   pollingInterval: 5000
})

useEffect(() => {
  return () => {
     client.stop()
  }
}, [client]);

Stilll experiencing this in 2021 (late october), but the above doesn’t work for me because using client stops all the polling, while I just want to stop one specifically.

I think there is an info missing in the documentation about the fact that pollInterval and start/stop cannot be used together, hence I’ve found this working solution:

const { data, loading, startPolling, stopPolling } = useQuery(GetData);

useEffect(() => {
   startPolling(desiredTimeoutInMilliseconds);
}, [startPolling]);

useEffect(() => {
   return () => stopPolling();
}, [stopPolling]);

Summarized, when the component is mounted the polling will start. Once the component is destroyed, the polling will be stopped.

This seems to consistently work, hence I’m keeping this solution, hope this helps someone.

This is probably fixed in the 3.5, if you want to try it (it’s currently in a release candidate stage).

I think there is an info missing in the documentation about the fact that pollInterval and start/stop cannot be used together, hence I’ve found this working solution:

Yeah I’ve found it weird that you essentially pass a new pollInterval via the function, whereas I expected the pollInterval() passed to the original hook to be the source of truth, and the functions to take no arguments.

Either way, I think the cleverest would be to have the hook to automatically stop the autopolling when the component is destroyed, since the polling is probably intended to be linked to the mounting component.

I will give a try to the RC when I happen to use that specific scenario again, right now I’m pretty good with this solution 😃

Also running into this issue as well !

This apollo issue with leaked polling caused a major problem for us this week. Thanks @briosheje, your answer saved us a lot of trouble.

I work with @branaust - this is the code in question (simplified a bit)

  const { data, loading, startPolling, stopPolling } = useQuery(
    GQL_QUERY,
    {
      variables: { id },
    }
  );

  const isItemPending = data?.items.some(
    (item) => item.status !== "READY"
  );

  useEffect(() => {
    isItemPending ?  startPolling(60 * 1000) : stopPolling();
  }, [isVideoPending]);

  useEffect(() => stopPolling, []);

Start works as expected but stop never works either on unmount or in the event that there are no pending items.

I’m only able to reproduce this bug when using React.StrictMode, so I updated the title. If anyone can come up with a reproduction that doesn’t use strict mode let me know.

I noticed that stopPolling works only if the polling is started using startPolling (so without passing pollIntervall options in query declaration).

Is this the desired behaviour?

export default function MyComponent() {

  const {
    data: carsData,
    loading: carsLoading,
    error: carsError,
    fetchMore,
    stopPolling,
    startPolling
  } = useQuery<getCars, getCarsVariables>(GET_CARS, {
    fetchPolicy: 'network-only'
  })

   useEffect(() => {
    startPolling(10000)
    return () => {
      stopPolling() // Works
    }
  }, [startPolling, stopPolling])

  return (
    <>
      <Button
        onClick={() => {
          stopPolling() // Works
        }}
      >
        STOP
      </Button>
      
     {/* MyComponent Render Stuff  */}

    </>
  )
}