react-mapbox-gl
react-mapbox-gl copied to clipboard
Infinite loop when setting state in handleMoveEnd
I have this component:
const Map = ReactMapboxGl({
accessToken: process.env.MAPBOX_KEY
});
const MapView = () => {
const [readMapCenter, setReadMapCenter] = useState([9.964037, 53.568269])
const handleMoveEnd = (map) => {
const curCenter = [map.getCenter().lng.toFixed(4), map.getCenter().lat.toFixed(4)]
console.log(curCenter)
//setReadMapCenter(curCenter) <== uncommenting this line creates infinite loop
}
return (
<Map
style="mapbox://styles/mapbox/streets-v9"
center={[9.964037, 53.568269]}
zoom={[12]}
movingMethod="easeTo"
containerStyle={{
height: '100%',
width: '100%'
}}
onMoveEnd={(map) => handleMoveEnd(map)}
>
</Map>
)
}
With this code I see the mapCenter logged once to the console when I move the map.
However when I uncomment the state setting in handleMoveEnd
, the react app crashes with "Maxiumum update depth exceeded". Could somebody explain to me why this is happening?
You can try https://github.com/welldone-software/why-did-you-render to debug
I feel like this is something wrong with the lib, the onDragEnd works as expected...
if you set the centre after drag end, it doesn't cause the map to run the onDragEnd event, which means you dont get the infinite loop, however with onMoveEnd or onZoomEnd, setting the centre causes these events to fire, leading to infinite loop.
Are there any suggestions to fix this?
I just encountered this issue. I believe it's due to onMoveEnd being setup in the componentdidmount, so when you call the useState method, it causes an endless loop of state change -> componentdidmount.
Anyway, on to my solution... YMMV. I used useRef.
const MapView = () => {
//const [readMapCenter, setReadMapCenter] = useState([9.964037, 53.568269])
const refMapCenter = useRef([9.964037, 53.568269]);
const handleMoveEnd = (map) => {
const curCenter = [map.getCenter().lng.toFixed(4), map.getCenter().lat.toFixed(4)]
console.log(curCenter)
refMapCenter.current = curCenter;
//setReadMapCenter(curCenter) <== uncommenting this line creates infinite loop
}
return (
<Map
style="mapbox://styles/mapbox/streets-v9"
center={[9.964037, 53.568269]}
zoom={[12]}
movingMethod="easeTo"
containerStyle={{
height: '100%',
width: '100%'
}}
onMoveEnd={(map) => handleMoveEnd(map)}
>
</Map>
)
}
@dhysong but if you want to use the map with controlled center values, does setting the center to a ref cause the component re-render?
say you want to set the initial center based off some state and then keep it updated with the latest center after moving it?
const [center, setCenter] = React.useState( // some data fetch )
const setMapCenter = (map) => { // logic to set center }
<Map
center={center}
onMoveEnd={setMapCenter}
/>
im pretty sure you can't use refs to this??
okay I found a way to get it working and that is to use refs + state
which means we can use the state in other parts of the app, but use the ref for the map and stops the loop
const mapCenter = React.useRef(props.initialCenter)
const [center, setCenter] = React.useState(props.initialCenter)
const setMapCenter = (map) => {
const newCenter = getNewCenterFromMap
// this doesn't cause infinite loop anymore?
setCenter(newCenter)
mapCenter.current = newCenter
}
<Map
center={mapCenter.current}
onMoveEnd={setMapCenter}
/>
Edit: Okay this doesn't actually work, as if props.initialCenter changes, there's no way of updating the center without going back into the infinite loop
Note: You can check whether onZoomEnd has been triggered by changed some Map property (contrary to being triggered by user interaction like dragging), by checking fitboundUpdate extra event data, like so:
onZoomEnd={(_, event) => {
if (event.fitboundUpdate) {
console.log('Map bounds have been programmatically changed')
} else {
console.log('Map bounds have been changed by user interaction')
}
}}
/>
source: https://github.com/alex3165/react-mapbox-gl/blob/master/docs/API.md