I have a form in which the user must specify the location of his home on the map. In this form I have used React Leaflet map.
The default location of the <Marker>
is the current location of user. I want to have a button in which whenever the user clicks on it, the marker becomes draggable, and could placed wherever the user sets it. I have also add a submit button in the <Popup>
, that when the user clicks on it, the marker must not be draggable, and the marker location must be updated and saved to be send to backend.
Here is my code:
const SetViewToCurrentLocation = ({location, setLocation}) => {
const map = useMap();
function getGeo() {
navigator.geolocation.getCurrentPosition(
(position) => {
setLocation({
lat: position.coords.latitude,
lng: position.coords.longitude,
});
},
(error) => {
console.log("--------- ERROR WHILE FETCHING LOCATION ----------- ", error);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000}
) ;
}
useEffect(() => {
getGeo();
}, []);
useEffect(() => {
if (location.lat && location.lng) {
map.setView([location.lat, location.lng]);
}
}, [location]);
return null;
};
const CustomizeMarker = ({location, setLocation}) => {
const [draggable, setDraggable] = useState(false);
console.log("*************** THE INPUT LOCATION IS ***************** ", location);
let lat = location.lat;
let lng = location.lng;
const [position, setPosition] = useState({lat, lng});
console.log("-----------------THE POS VALUE IS ------------------- ", position);
const markerRef = useRef(null);
const eventHandlers = useMemo(
() => ({
dragend() {
const marker = markerRef.current
if (marker != null) {
console.log("+++++++++++ THE OUTPUT OF getLatLng IS ++++++++++++ ", marker.getLatLng());
setPosition(marker.getLatLng());
setDraggable(false);
}
},
}),
[],
);
const toggleDraggable = useCallback(() => {
setDraggable((d) => !d)
}, []);
const saveLocation = () => {
console.log("HELLO WORLD");
};
return (
<>
<Marker
draggable={draggable}
eventHandlers={eventHandlers}
position={[position.lat, position.lng]}
ref={markerRef}>
<Popup minWidth={90}>
<Button onClick={saveLocation}>Submit</Button>
</Popup>
</Marker>
<Button onClick={toggleDraggable} variant='contained' className='edit-location-button'>Edit Your Location</Button>
</>
);
}
const EditHome = () => {
const [location, setLocation] = useState({});
return (
<React.Fragment>
<Box
component="form"
sx={{
"& .MuiTextField-root": { m: 1, maxWidth: "100%"},
}}
noValidate
autoComplete="off"
dir="ltr"
>
<form>
<div style={{ paddingLeft: "2.5rem" }}>
<Grid item xs={12}>
<h6 style={{ fontWeight: "bold", paddingRight: "4.9rem", marginTop: "0.8rem" }}>
Specify The Location of Your Home on The Map
</h6>
<div className='map-container'>
<MapContainer center={[0, 0]} zoom={16} scrollWheelZoom={true}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<GeoSearchField />
<SetViewToCurrentLocation location={location} setLocation={setLocation}/>
{location.lat && location.lng && (<CustomizeMarker location={location} setLocation={setLocation} />)}
</MapContainer>
</div>
</Grid>
</div>
</form>
</Grid>
</Box>
</React.Fragment>
)
}
export default EditHome;
But I have several problems. The first one is that the <Button>
is not visible in the map, I want to set it on the top left part of my map, and I have defined the below CSS class
for it.
.leaflet-container {
width: 57rem;
height: 40rem;
}
.map-container {
position: relative;
}
.edit-location-button {
position: absolute;
top: 10px;
right: 10px;
}
And the second problem is the functionality of marker and the change handler for its location.
This is the current output of my implementation: I will be grateful for any help.
You are close.
First to be able to see the button add a zindex value in your css big enought to be on top of the map
.edit-location-button {
position: absolute;
top: 10px;
right: 10px;
z-index: 10000;
}
Second you need to make the buttons be of type button otherwise they seem to be of type submit which causes the page to refresh on each click. It should be similar in material ui. I did it in plain html. Add type="button"
to your buttons.
<>
<Marker
draggable={draggable}
eventHandlers={eventHandlers}
position={[position.lat, position.lng]}
ref={markerRef}
>
<Popup minWidth={90}>
<button type="button" onClick={saveLocation}>
Submit
</button>
</Popup>
</Marker>
<button
type="button"
onClick={toggleDraggable}
className="edit-location-button"
>
Edit Your Location
</button>
</>
The functionality seems to be ok and working. The only thing is that you don't have to add the edit location button inside the Marker
component as they are irrelevant. I placed it outside under the Fragment you already had. Moreover to get the coordinates upon clicking submit you need to call
const saveLocation = () => {
if (markerRef.current)
console.log(
"+++++++++++ THE OUTPUT OF getLatLng IS ++++++++++++ ",
markerRef.current.getLatLng()
);
};
instead of logging the marker inside the dragend callback.
You can see the result in the demo.