How to resolve "React Hook useEffect has a missing dependency: 'currentPosition'"

2.2k views Asked by At

When I include currentPosition in the useEffect dependency array or when I delete it, the code turns into an infinite loop. Why? I have the same problem with map but when I place map in the dependency array it's ok.

import { useState, useEffect } from "react";

import { useMap } from "react-leaflet";
import L from "leaflet";

import icon from "./../constants/userIcon";

const UserMarker = () => {
  const map = useMap();
  const [currentPosition, setCurrentPosition] = useState([
    48.856614,
    2.3522219,
  ]);

  useEffect(() => {
    if (navigator.geolocation) {
      let latlng = currentPosition;
      const marker = L.marker(latlng, { icon })
        .addTo(map)
        .bindPopup("Vous êtes ici.");
      map.panTo(latlng);

      navigator.geolocation.getCurrentPosition(function (position) {
        const pos = [position.coords.latitude, position.coords.longitude];
        setCurrentPosition(pos);
        marker.setLatLng(pos);
        map.panTo(pos);
      });
    } else {
      alert("Problème lors de la géolocalisation.");
    }
  }, [map]);

  return null;
};

export default UserMarker;
4

There are 4 answers

0
gcbox999 On BEST ANSWER

Thank you, i have resolved the conflict how this:

import { useEffect } from "react";

import { useMap } from "react-leaflet";
import L from "leaflet";

import icon from "./../constants/userIcon";

const UserMarker = () => {
  const map = useMap();

  useEffect(() => {
    const marker = L.marker;
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function (position) {
        const latlng = [position.coords.latitude, position.coords.longitude];
        marker(latlng, { icon })
          .setLatLng(latlng)
          .addTo(map)
          .bindPopup("Vous êtes ici.");
        map.panTo(latlng);
      });
    } else {
      alert("Problème lors de la géolocalisation.");
    }
  }, [map]);

  return null;
};

export default UserMarker;
0
Yilmaz On

the reason why you are getting infinite loop if currentPosition inside dependency array:

const [currentPosition, setCurrentPosition] = useState([
    48.856614,
    2.3522219,
  ]);

you have initially have a value for currentPosition and, then you are changing inside useEffect, that causes your component rerender, and this is happening infinitely. You should not add it to the dependency array.

The reason you are getting "missing-dependency warning" is,if any variable that you are using inside useEffect is defined inside that component or passed to the component as a prop, you have to add it to the dependency array, otherwise react warns you. That's why you should add map to the array and since you are not changing it inside useEffect it does not cause rerendering.

In this case you have to tell es-lint dont show me that warning by adding this://eslint-disable-next-line react-hooks/exhaustive-deps because you know what you are doing:

useEffect(() => {
   if (navigator.geolocation) {
      let latlng = currentPosition;
      const marker = L.marker(latlng, { icon })
        .addTo(map)
        .bindPopup("Vous êtes ici.");
      map.panTo(latlng);

      navigator.geolocation.getCurrentPosition(function (position) {
        const pos = [position.coords.latitude, position.coords.longitude];
        setCurrentPosition(pos);
        marker.setLatLng(pos);
        map.panTo(pos);
      });
    } else {
      alert("Problème lors de la géolocalisation.");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [map]);
 

that comment will turn of the dependency check on that line of code.

0
LeonMueller - OneAndOnly On

The comment from DCTID explains the reason why including the state in the useEffect hook creates an infinite loop.

You need to make sure that this does not happen! You have two options:

  1. add a ignore comment and leave it as it is

  2. create a additional redundant variable to store the current value of the variable currentPosition and only execute the function if the value actually changed

An implementation of the second approach:

let currentPosition_store = [48.856614, 2.3522219];

useEffect(() => {
    if (!hasCurrentPositionChanged()) {
        return;
    }

    currentPosition_store = currentPosition;

    // remaining function

    function hasCurrentPositionChanged() {
        if (currentPosition[0] === currentPosition_store[0] &&
            currentPosition[1] === currentPosition_store[1]
        ) {
            return false;
        }
        
        return true;
    }
}, [map, currentPosition]);
4
Tony Nguyen On

To make it easy to understand I will point out the reason first then come to solution.

  1. Why? I have the same problem with map but when I place map in the dependency array it's ok.

Answer: The reason is useEffect is re-run based on it dependencies. useEffect first run when Component render -> component re-render (cuz it's props change...) -> useEffect will shallow compare and re-run if its dependencies change.

  • In your case, map Leaflet Map I bet react-leaflet will return same Map instance (same reference) if your component simply re-render -> when you component re-render -> map (Leaflet Map instance) don't change -> useEffect not re-run -> infinity loop not happen.
  • currentPosition is your local state and you update it inside your useEffect setCurrentPosition(pos); -> component re-render -> currentPosition in dependencies change (currentPosition is different in shallow compare) -> useEffect re-run -> setCurrentPosition(pos); make component re-render -> infinity loop
  1. Solution:

There are some solutions:

  • Disable the lint rule by add // eslint-disable-next-line exhaustive-deps right above the dependencies line. But this is not recommended at all. By doing this we break how useEffect work.
  • Split up your useEffect:

import { useState, useEffect } from "react";

import { useMap } from "react-leaflet";
import L from "leaflet";

import icon from "./../constants/userIcon";

const UserMarker = () => {
  const map = useMap();
  const [currentPosition, setCurrentPosition] = useState([
    48.856614,
    2.3522219,
  ]);
  
  // They are independent logic so we can split it yo
  useEffect(() => {
    if (navigator.geolocation) {
      let latlng = currentPosition;
      const marker = L.marker(latlng, { icon })
        .addTo(map)
        .bindPopup("Vous êtes ici.");
      map.panTo(latlng);
    } else {
      alert("Problème lors de la géolocalisation.");
    }
  }, [map, currentPosition]);

  useEffect(() => {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function (position) {
        const pos = [position.coords.latitude, position.coords.longitude];
        setCurrentPosition(pos);
        marker.setLatLng(pos);
        map.panTo(pos);
      });
    }
  }, [map]);

  return null;
};

export default UserMarker;

There is a great article about useEffect from Dan, it's worth to check it out: https://overreacted.io/a-complete-guide-to-useeffect/#dont-lie-to-react-about-dependencies