Reputation: 171
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;
Upvotes: 3
Views: 2296
Reputation: 3488
To make it easy to understand I will point out the reason first then come to solution.
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.
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 loopThere are some solutions:
// 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.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
Upvotes: 0
Reputation: 49671
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.
Upvotes: 0
Reputation: 171
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;
Upvotes: 0
Reputation: 84
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:
add a ignore comment and leave it as it is
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]);
Upvotes: 0