Single custom marker google maps API in react - javascript

I have been working with Google Maps & Places API and have successfully got it working using react hooks to allow the user to drop markers, onClick info window, auto-complete place search and Geolocation.
However, what I need is that onClick it will only add a single marker which will change location every time the user clicks a new coord. Not add multiple markers on every click as it currently does.
Code below:
const [markers, setMarkers] = useState([]);
const [selected, setSelected] = useState(null);
const onMapClick = useCallback((event) => {
setMarkers((current) => [
...current,
{
lat: event.latLng.lat(),
lng: event.latLng.lng(),
time: new Date(),
},
]);
}, []);
...
return (
<GoogleMap
mapContainerStyle={mapContainerStyle}
zoom={13}
center={center}
onClick={onMapClick}
onLoad={onMapLoad}
>
{markers.map((marker) => (
<Marker
key={marker.time.toISOString()}
position={{ lat: marker.lat, lng: marker.lng }}
icon={{
url: "/crane-pin.svg",
scaledSize: new window.google.maps.Size(40, 40),
origin: new window.google.maps.Point(0, 0),
anchor: new window.google.maps.Point(15, 15),
}}
onClick={() => {
setSelected(marker);
}}
/>
))}
...
</GoogleMap>
any help would be appreciated!

To achieve your use case, you need to have access to the marker object and use the setPosition method to change the position of the marker to the clicked coordinates in the map.
Are you using any google maps react library? Here is a sample reactjs code that implements this without using any react libraries. Code snippet below:
import React, { Component } from "react";
import { render } from "react-dom";
import Map from "./Map";
import "./style.css";
class App extends Component {
render() {
return (
<div id="container">
<Map
id="myMap"
options={{
center: { lat: 37.769, lng: -122.446 },
zoom: 12,
}}
onMapLoad={(map) => {
let marker = new google.maps.Marker({
position: { lat: 37.769, lng: -122.446 },
map: map,
});
//Changing Marker position for clicked coordinate
map.addListener("click", (event) => {
marker.setPosition(event.latLng);
});
}}
/>
</div>
);
}
}
export default App;

Related

How can I make google map to appear?

Hey everyone so I'm learning React Native, using Expo VS code, and android studio
I was trying to get a location image with a pin using google maps.
But I cant see any map to pick my location at google maps in android studio.
Also, I cant see any marker after picking a location in iphone expo go google maps image.
What should I do?
Below is my code about getting image of map
export function getMapPreview(lat, lng) {
const imagePreviewUrl = `https://maps.googleapis.com/maps/api/staticmap?center=${lat},${lng}&zoom=13&size=400x200&maptype=roadmap&markers=color:red%7Clabel:S%7C${lat},${lng}
&key=${GOOGLE_API_KEY}`;
return imagePreviewUrl;
}
Map screen
import { useCallback, useLayoutEffect, useState } from "react";
import { StyleSheet, Alert } from "react-native";
import MapView, { Marker } from "react-native-maps";
import IconButton from "../components/UI/IconButton";
export default function Map({navigation}) {
const [selectedLocation, setSelectedLocation] = useState();
const region = {
latitude: 37.56,
longitude: 126.97,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421
}
function selectLocationHandler(event) {
console.log(event);
const lat = event.nativeEvent.coordinate.latitude;
const lng = event.nativeEvent.coordinate.longitude;
setSelectedLocation({lat: lat, lng: lng})
}
const savePickedLocationHandler = useCallback(() => {
if (!selectedLocation) {
Alert.alert(
"No location picked",
'You have to pick a loacation (by tapping on the map) first!'
)
return;
}
navigation.navigate('AddPlace', { pickedLat: selectedLocation.lat, pickedLng: selectedLocation.lng })
}, [navigation, selectedLocation])
useLayoutEffect(() => {
navigation.setOptions({
headerRight: ({tintColor}) => (
<IconButton icon="save" size={24} color={tintColor} onPress={savePickedLocationHandler} />
)
})
}, [navigation, savePickedLocationHandler])
return (
<MapView style={styles.map} initialRegion={region} onPress={selectLocationHandler}>
{selectedLocation &&
<Marker
title="Picked Location"
coordinate={{latitude: selectedLocation.lat,longitude: selectedLocation.lng}}
/>
}
</MapView>
)
}
const styles = StyleSheet.create({
map: {
flex: 1
}
})
I reviewed my code several times, but I cant find why
Try use width and height to map styles
You need to define the width and height of the map.
const styles = StyleSheet.create({
map: {
width: MAP_WIDTH_VALUE,
height:MAP_HEIGHT_VALUE
}
})

Render MarkerClusterer with Custom Marker and Dynamic location

I have a issue when rendering markers and clustering them. When i fetch location data list from api completely, map doesn't re-render marker.
When i use custom marker child component, that can re-render map when api is fetch completely. But I can't Clustering these custom marker.
As i know that onGoogleApiLoaded just called once times at the first render so now I have no solution to solve this issue.
Here my code below. Thanks for any help.
const get_list_unit_location = useCallback(() => {
if (units.length) {
const listLocation = [];
units.forEach((unit) => {
if (unit.lat && unit.lng) {
listLocation.push({
lat: unit.lat,
lng: unit.lng,
subUnitQuantity: unit.sub_units.length,
});
}
});
setUnitLocations(listLocation);
}
}, [units]);
useEffect(() => {
get_list_unit_location();
}, [get_list_unit_location]);
const setGoogleMapRef = useCallback(
(map, maps) => {
if (unitLocations.length) {
const markers = unitLocations.map((location) => {
return new maps.Marker({ position: location, map });
});
// eslint-disable-next-line no-unused-vars
const markerCluster = new MarkerClusterer({ map, markers });
}
},
[unitLocations]
);
<GoogleMapReact
bootstrapURLKeys={{ key: process.env.REACT_APP_GOOGLE_MAP_API_KEY }}
defaultCenter={center}
defaultZoom={zoom}
options={{
fullscreenControl: false,
zoomControl: false,
}}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({ map, maps }) => setGoogleMapRef(map, maps)}
>
{unitLocations.map((location) => (
// eslint-disable-next-line react/jsx-key
<Marker
lat={location.lat}
lng={location.lng}
text={location.subUnitQuantity}
/>
))}
</GoogleMapReact>
One solution would be to have setGoogleMapRef do nothing more than store map and maps into some state, and then have a separate useEffect that creates the MarkerClusterer. This will ensure that the clusterer isn't created until both map and data are loaded (and will be recreated if the data changes.) Something like:
const [ map, setMap ] = useState();
const [ maps, setMaps ] = useState();
const setGoogleMapRef = useCallback((map, maps) => {
setMap(map);
setMaps(maps);
}, [ setMap, setMaps ]);
useEffect(() => {
if (unitLocations.length && map && maps) {
// create markers and clusterer
}
}, [ unitLocations, map, maps]);
Alternatively, if you're willing to look at a different package, #react-google-maps/api has a component for the clusterer as well as the map, marker, etc:
<GoogleMap ...>
<MarkerClusterer ...>
{clusterer => unitLocations.map((location, index) => {
<Marker position={location} clusterer={clusterer}/>
}
</MarkerClusterer>
</GoogleMap >

Update tileLayer url in react-leaflet

So I got this code running to render a leaflet ok, trying to replace the url whenever the colorMode changes is the challenge here.
useEffect is triggered ok displaying the correct variable but I can't update that TileLayer in any way.
export const Map = () => {
const { colorMode } = useColorMode();
let state = { center: { lat: 51.505, lng: -0.09 }, zoom: 13 };
const colorModeUrl = ['https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 'https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png']
useEffect(() => {
console.log(colorMode);
}, [colorMode]);
return (
<MapContainer
center={state.center}
zoom={state.zoom}
style={{ height: '100%' }}>
<TileLayer url={colorMode === 'light' ? colorModeUrl[0] : colorModeUrl[1]} />
</MapContainer>
)
}
Looking at the TileLayer documentation, the url prop is not mutable. After the initial render the component will not update if the prop is changed:
However, you can add a ref to the layer and update the url that way
export const Map = () => {
const ref = useRef(null);
const state = { center: { lat: 51.505, lng: -0.09 }, zoom: 13 };
const { colorMode } = useColorMode();
const light = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
const dark =
"https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.png";
};
useEffect(() => {
if (ref.current) {
ref.current.setUrl(colorMode === "light" ? light : dark);
}
}, [colorMode]);
return (
<div>
<MapContainer
center={state.center}
zoom={state.zoom}
style={{ height: "100%" }}
>
<TileLayer ref={ref} url={colorMode === "light" ? light : dark} />
</MapContainer>
</div>
);
};
https://codesandbox.io/s/react-leaflet-forked-1534x?file=/src/index.js:0-1158
I encountered a similar issue today trying to change the TileLayer using react-leaflet v4. As mentioned in the accepted answer, the immutability of the TileLayer url attribute is the problem. My dead-simple workaround was to pass a "key" attribute to TileLayer that will change whenever a new tile source is commanded. This will force a remount of the TileLayer component.
Hopefully this isn't subtly dangerous or an antipattern, but it seems to work with minimal complexity.

react-google-maps/api Avoiding re-render of Map after some state changes

I was having problems where my GoogleMaps instance would refresh and self center itself on some onClick function where the state was being set and the entire Component rendering cycle would happen.
After some Googling it was suggested that the component instantiation be separated and re-used. The problem now is I have some logic to display markers inside <GoogleMaps> component that is no longer working as expected and I don't know how to refactor:
export default function LocationSearchResults({
...
}) {
const [map, setMap] = useState(null)
const [markersContainer, setMarkersContainer] = useState([])
const getMap = () => {
if (map) {
return map;
} else {
setMap(<GoogleMap mapContainerStyle={containerStyle}
options={ {
minZoom: 3,
maxZoom: 15
}}
center={{
lat: 49.25,
lng: -84.5
}}
zoom={5}
onLoad={onLoad}
onDragEnd={onDragEnd} >
{
markersContainer.map(place => { //Only executes once? Does not listen for changes
return (< Marker key={place.id}
position={ place.position}
/>
)
})
}
</GoogleMap>
)
return map
}
}
render( <div className="..." >
{
getMap()
}
</div>
)
}
I don't have a ton of experience with React, any help is appreciated thanks!
I set up my component instantiation like so using useMemo
...instantiate all event listener functions here
const map = useMemo(() =>
{
return (<GoogleMap
mapContainerStyle={containerStyle}
options={{ minZoom: 3, maxZoom: 15 }}
center={{
lat: 49.25,
lng: -84.5
}
}
zoom={5}
onLoad={onLoad}
onDragEnd={onDragEnd}
// onUnmount={onUnmount}
>
{markersContainer.map(place => { return ( <Marker
key={place.id}
position={place.position} />
)
})
}
</GoogleMap>)
}, [markersContainer])
then I simply render in my render() function:
return (
<>
<div>...
{map}
</div>
</>)
No more unnecessary refreshes unless new markers are added/removed.

Why can't I see any markers on google maps react?

I have been building the front end of an application that has the google maps API enabled. I want to have the user click on the map to add markers, and store it into an array; However after I implemented an add marker function to the onClick of the google map tag... The markers won't render.
I have had a lot of difficulty implementing the add marker function in react, and watched several tutorials but cannot seem to find a solution. Any help would be greatly appreciated!
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
}
var markersArray = [];
function addMarker(event){
let marker = new window.google.maps.Marker({
position: event.latLng,
time: new Date(),
draggable: true
});
markersArray.push(marker);
}
const Map = withScriptjs(withGoogleMap(props =>
<GoogleMap
defaultZoom={8}
defaultCenter={{ lat: this.state.mapPosition.lat, lng: this.state.mapPosition.lng }}
options={options}
onClick={(event) => {
console.log(event);
console.log(markersArray);
addMarker(event);
}}
>
{markersArray.map((marker) =>(
<Marker
key={marker.time.toISOString()}
position={{lat: marker.lat, lng: marker.lng }}
/>
))}
<AutoComplete
style={{width: "100%", height:'40px'}}
types={['(regions)']}
onPlaceSelected={this.onPlaceSelected}
/>
</GoogleMap>
));
I believe the issue is with this but i'm not sure how to make it work.
{markersArray.map((marker) =>(
<Marker
key={marker.time.toISOString()}
position={{lat: marker.lat, lng: marker.lng }}
/>
))}
markersArray is a plain array. When you do addMarker(event); you're not updating the state of your component, it's just a plain array. So probably React is not aware of that change. If you're using hooks, you could create a state with your markers like this
const [markers, setMarkers] = useState([])
use that markers array to render the <Marker /> and then on your event to add the marker
onClick={(event) => {
setMarker(previousMarkers => previousMarkers.concat([event]))
}
this will cause a re-render with the new value of markers and you should be able to see them.
If it's not a functional component and it's classes, it's the same, but you would define the markers in your class' state
constructor(props) {
super(props)
this.state = { markers: [] }
this.addMarker = this.addMarker.bind(this)
}
// function class below
addMarker(marker) {
this.setState(previousState => previousState.concat([marker])
}
and in your event, you'd call this.addMarker(marker)

Categories