[Bug] Marker beaving weirdly when i try to drag it with maxBounds defined and the map reached maxBounds
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 8.5k
- Forks
- 1.4k
- Avg merge
- 5d 17h
- Merged PRs (30d)
- 3
Description
Description
When i try to move my marker, it behaves normally, but when i zoom out so i reach the maxBounds, either on top/left/right/bottom, and then, when i try to move my marker, it jumps randomly across the map
video (showing how my marker behaves normally, but when i reach the edge of the map and try to move it, bug starts to occur):
https://github.com/user-attachments/assets/b3f76ff2-cd44-4fca-adbb-30f94de45b6f
If needed, i can add the component code here
Expected Behavior
Marker should not behave differently when the map is at the edge of the bounds
Steps to Reproduce
Here's my component code, maybe it will be useful:
import { useState, useCallback, useRef, useEffect } from 'react';
import Map, {
NavigationControl,
GeolocateControl,
Marker,
} from 'react-map-gl/mapbox';
import 'mapbox-gl/dist/mapbox-gl.css';
import { MapFill } from '../styles';
import { useData } from '@/context/DataContext';
import {
LngLatBoundsLike,
} from 'mapbox-gl';
import EntityDetailsPanel from '@/components/Mapbox/EntityDetailsPanel';
import GeoJsonDataProcessor from './GeoJsonDataProcessor';
import PieChartImageGenerator from './PieChartImageGenerator';
interface MapComponentProps {
searchMarker?: { longitude: number; latitude: number } | null;
searchingViaInput?: { longitude: number; latitude: number } | null;
onMarkerDrag?: (position: { longitude: number; latitude: number }) => void;
radiusSize: number;
isDragEnabled: boolean;
isRadiusVisible: boolean;
}
export default function MapComponent({
searchMarker,
searchingViaInput,
onMarkerDrag,
radiusSize,
isDragEnabled,
isRadiusVisible,
}: MapComponentProps) {
const [viewState, setViewState] = useState({
longitude: 15.9819,
latitude: 44.915,
zoom: 7,
});
const [markerPosition, setMarkerPosition] = useState(
searchMarker || { longitude: 0, latitude: 0 }
);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [hoveredId, setHoveredId] = useState<string | null>(null);
const [mapLoaded, setMapLoaded] = useState(false);
const [activeEntity, setActiveEntity] = useState<any>(null);
const activeEntityIdRef = useRef<string | null>(null);
const mapRef = useRef<any>(null);
const { entities } = useData();
const { data: geoJson, loading, error } = entities;
const mapBounds: LngLatBoundsLike = [
[-7, 37],
[37.8, 52],
];
// Handle map click
const handleMapClick = useCallback((e: any) => {
if (!mapRef.current) return;
try {
const layers = mapRef.current.getStyle().layers || [];
const layerExists = layers.some(
(l: any) =>
l.id === 'unselected-entities' || l.id === 'selected-entities'
);
if (!layerExists) return;
const features = mapRef.current.queryRenderedFeatures(e.point, {
layers: ['unselected-entities', 'selected-entities'],
});
if (features.length > 0) {
const clickedFeature = features[0];
const id = clickedFeature.properties?.id;
if (activeEntityIdRef.current === id) {
return;
}
setSelectedId(id);
activeEntityIdRef.current = id;
const cleanProperties = {
...clickedFeature.properties,
kategorije: clickedFeature.properties.kategorije
? JSON.parse(clickedFeature.properties.kategorije)
: [],
mainCats: clickedFeature.properties.mainCats
? JSON.parse(clickedFeature.properties.mainCats)
: [],
groupedCategories: clickedFeature.properties.groupedCategories
? JSON.parse(clickedFeature.properties.groupedCategories)
: [],
};
setActiveEntity(cleanProperties);
} else {
setSelectedId(null);
activeEntityIdRef.current = null;
setActiveEntity(null);
}
} catch (error) {
console.warn('Error querying features:', error);
}
}, []);
// Handle mouse move
const handleMouseMove = useCallback((e: any) => {
if (!mapRef.current) return;
try {
const layers = mapRef.current.getStyle().layers || [];
const layerExists = layers.some(
(l: any) =>
l.id === 'unselected-entities' || l.id === 'selected-entities'
);
if (!layerExists) return;
const features = mapRef.current.queryRenderedFeatures(e.point, {
layers: ['unselected-entities', 'selected-entities'],
});
if (features.length > 0) {
setHoveredId(features[0].properties?.id);
mapRef.current.getCanvas().style.cursor = 'pointer';
} else {
setHoveredId(null);
mapRef.current.getCanvas().style.cursor = '';
}
} catch (error) {
console.warn('Error querying features:', error);
}
}, []);
// Handle mouse leave
// const handleMouseLeave = useCallback(() => {
// setHoveredId(null);
// if (mapRef.current) {
// mapRef.current.getCanvas().style.cursor = '';
// }
// }, []);
// Handle marker drag
const handleDragEnd = (event: any) => {
const newPosition = {
longitude: event.lngLat.lng,
latitude: event.lngLat.lat,
};
// IF I DISABLE setMarkerPosition PROBLEM DOESN'T OCCUR, BUT I INTRODUCE NEW PROBLEMS
setMarkerPosition(newPosition);
if (onMarkerDrag) onMarkerDrag(newPosition);
};
// Fly to location when searching via input
useEffect(() => {
if (!searchingViaInput || !mapLoaded || !mapRef.current) return;
mapRef.current.flyTo({
center: [searchingViaInput.longitude, searchingViaInput.latitude],
zoom: 9,
duration: 300,
});
}, [searchingViaInput, mapLoaded]);
// Update marker position on search
useEffect(() => {
if (searchMarker) {
setMarkerPosition(searchMarker);
}
}, [searchMarker]);
const token = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN;
if (!token) return <p>No Mapbox token.</p>;
if (loading) return <p>Loading entities…</p>;
if (error) return <p>Error: {String(error)}</p>;
if (!geoJson) return <p>No data.</p>;
return (
<MapFill>
{activeEntity && (
<EntityDetailsPanel
entity={activeEntity}
onClose={() => {
setSelectedId(null);
activeEntityIdRef.current = null;
setActiveEntity(null);
}}
/>
)}
<Map
ref={mapRef}
mapboxAccessToken={token}
mapStyle="mapbox://styles/mapbox/streets-v12"
{...viewState}
onMove={(ev) => setViewState(ev.viewState)}
onClick={handleMapClick}
onMouseMove={handleMouseMove}
dragRotate={false}
minZoom={6.2}
maxZoom={18}
maxBounds={mapBounds} //When bounds are set, on map bounds, marker breaks
// onMouseLeave={handleMouseLeave} //not using
onLoad={() => setMapLoaded(true)}
interactiveLayerIds={['unselected-entities', 'selected-entities']}
>
<NavigationControl position="top-right" />
<GeolocateControl position="top-right" trackUserLocation />
<GeoJsonDataProcessor
geoJson={geoJson}
selectedId={selectedId}
hoveredId={hoveredId}
markerPosition={markerPosition}
radiusSize={radiusSize}
isRadiusVisible={isRadiusVisible}
/>
<PieChartImageGenerator
mapRef={mapRef}
mapLoaded={mapLoaded}
geoJson={geoJson}
/>
{markerPosition && (
<Marker
longitude={markerPosition.longitude}
latitude={markerPosition.latitude}
color="red"
style={{ cursor: isDragEnabled?"pointer":"" }}
draggable={isDragEnabled}
onDragEnd={handleDragEnd}
/>
)}
</Map>
</MapFill>
);
}
Environment
-
Framework version: "react-map-gl": "^8.0.4",
-
Map library: "mapbox-gl": "^3.12.0",
-
Browser: all browsers
-
OS: windows
Logs
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the provided React component with react-map-gl 8.0.4 and mapbox-gl 3.12.0, focusing on maxBounds, a draggable Marker, and the setMarkerPosition update in handleDragEnd. Trace the marker drag behavior when the map reaches each bound; done means dragging no longer causes the marker to jump while bounds remain enforced.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100