React useEffect dependencies for subscribe/unsubscribe with eslint exhaustive-deps

Issue

I have a useEffect hook that should subscribe to geolocation updates when the component appears, and then unsubscribe when the component disappears. So I pass [] as the effect dependencies since I only want this to run on mount/unmount.

import { useWatchPosition } from "@ionic/react-hooks/geolocation"
import React, { useEffect } from "react"

function SiteMap() {
  const { currentPosition, startWatch, clearWatch }  useWatchPosition()

  // Subscribe/Unsubscribe to geo location on component mount/unmount.
  useEffect(() > {
    startWatch()
    return clearWatch
  }, [])

  return <svg>{/* ... */}</svg>
}

Which causes eslint to warn:

React Hook useEffect has missing dependencies: clearWatch and startWatch. Either include them or remove the dependency array.eslint(react-hooks/exhaustive-deps)

So I changed it to this:

  useEffect(() > {
    startWatch()
    return clearWatch
  }, [startWatch, clearWatch])

Which causes an infinite render loop.

I’m guessing the infinite loop is caused by the @ionic/react-hooks/geolocation library which is creating new functions every time useWatchPosition() is called, making the dependencies look stale.

So should I just disable the check for this line via:

// eslint-disable-next-line react-hooks/exhaustive-deps

Or is there some way I’m missing to do the right thing here?

Solution

Reading the useWatchPosition source code, you can see that the functions are not created with useCallback, this means that they are regenerated whenever the hook is called.

You can store a reference to the functions in a ref, and use the ref.current to call the function:

function SiteMap() {
  const { currentPosition, startWatch, clearWatch }  useWatchPosition()

  const startWatchRef  useRef(startWatch)
  const clearWatchRef  useRef()

  useEffect(() > {
    clearWatch.current  clearWatch // the updated clearWatch
  })

  // Subscribe/Unsubscribe to geo location on component mount/unmount.
  useEffect(() > {
    startWatchRef.current()

    return () > clearWatchRef.current()
  }, [])

  return <svg>{/* ... */}</svg>
}

Answered By – Ori Drori

Leave a Comment