I'm doing a little app using React Leaflet, and I want to add a marker on the map every time I click somewhere,
I don't know how to do it, I tried something below but the console log doesn't return something,
function App() {
const [position, setPosition] = useState([48.8534, 2.3488]);
const [markers, setMarkers] = useState([]);
function addMarker(e) {
console.log("e", e);
const newMarker = e;
setMarkers([...markers, newMarker]);
}
return (
<div className="App" style={{ width: "100%", height: "100vh" }}>
<MapContainer
center={position}
zoom={6}
scrollWheelZoom={true}
style={{ width: "100%", height: "100vh" }}
onClick={addMarker}
>
<MyComponent />
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
{markers &&
markers.map((marker, index) => {
<Marker key={`marker-${index}`} position={marker}>
<Popup>
<span>Popup</span>
</Popup>
</Marker>;
})}
</MapContainer>
</div>
);
}
export default App;
react-leaflet just updated to version 3, which no longer support inline events like onClick. You need to use the useMapEvents hook. Example here.
import { MapContainer, useMapEvents } from 'react-leaflet'
function AddMarkerToClick() {
const [markers, setMarkers] = useState([]);
const map = useMapEvents({
click(e) {
const newMarker = e.latlng
setMarkers([...markers, newMarker]);
},
})
return (
<>
{markers.map(marker =>
<Marker position={marker}>
<Popup>Marker is at {marker}</Popup>
</Marker>
)}
</>
)
}
function App() {
return (
<div className="App" style={{ width: "100%", height: "100vh" }}>
<MapContainer {...} > {/* omit onClick */}
<AddMarkerToClick />
</MapContainer>
</div>
);
}
export default App;
I didn't get a chance to test this yet, but this should give you an idea.
import React, { memo, useEffect, useState } from 'react'
import { Map, TileLayer } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import Markers from './Markers'
const initialState = ({
lat: '18.4942031',
lng: '-69.8919176',
zoom: '13'
})
export const MapView = memo(({dataMap, searchHistory}) => {
const [ properties, setProperties ] = useState(initialState)
const setPropertiesOnMap = () =>{
setTimeout(function(){
setProperties({
lat: dataMap[0]?.latitude || '18.4942031',
lng: dataMap[0]?.longitude || '-69.8919176',
zoom: '18',
})
},500)
}
useEffect(() =>{
if(dataMap.length === 1){
setPropertiesOnMap()
}else{
setProperties(initialState)
}
//eslint-disable-next-line
},[dataMap])
return (
<Map center={{lat: properties.lat, lng: properties.lng}} zoom={properties.zoom} minZoom={8}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
{dataMap[0] && <Markers dataMap={dataMap} searchHistory={searchHistory} />}
</Map>
)
})
I did something similar, I hope it can help you
Related
I am using React-leaflet to retrieve a position. I created a search input text to place the marker by API address, and I want it to be possible to move the marker more precisely.
I have set the marker to "draggable = true" but I want to update the x and y position and display it in the pop-up window. How to do that please?
import React, { useEffect, useState } from 'react'
import 'leaflet/dist/leaflet.css'
import Leaflet from "leaflet";
import L from "leaflet";
import { MapContainer, Marker, useMap, TileLayer, Popup } from 'react-leaflet'
export default function App(text: any) {
const [x, setX] = useState(2.3522219)
const [y, setY] = useState(48.856614)
console.log(y, x);
const icon = new Leaflet.DivIcon({
className: 'custom-div-icon',
html:
"<div style='background-color:#c30b82;' class='marker-pin'></div><i class='material-icons'><img src='img/marker-icon.png'></i>",
iconSize: [30, 42],
iconAnchor: [15, 42],
popupAnchor: [-3, -42]
})
function SetViewOnClick({ coords }: any) {
const map = useMap()
map.setView(coords, map.getZoom())
return null
}
useEffect(() => {
if (text.text) {
setX(text.text.features[0].geometry.coordinates[0])
setY(text.text.features[0].geometry.coordinates[1])
}
}, [text])
return (
<MapContainer
center={[y, x]}
attributionControl={false}
zoomControl={false}
zoom={12}
style={{
height: '350px',
position: 'relative',
outline: 'none',
maxWidth: '696px',
display: 'block',
margin: '15px auto',
width: '100%'
}}
>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
<Marker position={[y, x]} icon={icon} draggable={true}>
<Popup>
<span>
{text.text
? text.text.query
: "Location Default"}
</span>
</Popup>
<SetViewOnClick coords={[y, x]} />
</Marker>
</MapContainer>
)
}
Use eventHandlers on Marker comp in combination with a marker ref to update x, y.
const markerRef = useRef(null);
const eventHandlers = useMemo(
() => ({
dragend() {
const marker = markerRef.current;
if (marker != null) {
console.log(marker.getLatLng());
const { lat, lng } = marker.getLatLng();
setX(lng);
setY(lat);
setMarkerDragged(true);
}
}
}),
[]
);
...
<Marker
position={[y, x]}
icon={icon}
draggable={"true"}
ref={markerRef}
eventHandlers={eventHandlers}
>
<Popup>
<span>{popup()}</span>
</Popup>
<SetViewOnClick coords={[y, x]} />
</Marker>
You can use also a flag to display the new popup message when the marker is dragged but in the end it depends on you how you would display the popup to evaluate three different conditions.
const [markerDragged, setMarkerDragged] = useState(false);
const popup = () => {
if (markerDragged) return `New latitude: ${y}, new longitude: ${x}`;
return text ? text.query : "Location Default";
};
Demo
My objective is to pan google-maps-react map to a latlng position, after getting a latlong from react-places-autocomplete when a user selects an address suggestion.
I am facing difficulty in setting ref of map from a child functional component, so that I can call map.panTo(location) in the parent functional component.
Following is my Google-Maps and PlaceAutoComplete child Component:
import React, { useEffect } from 'react';
import { Map, GoogleApiWrapper, Marker } from 'google-maps-react';
import { FormGroup, Label, Input, Spinner, Container, Row, Col } from 'reactstrap';
import PlacesAutocomplete from 'react-places-autocomplete';
const InputAndMap = React.forwardRef((props, ref) => {
return (
<div>
<PlacesAutocomplete
value={props.address}
onChange={props.handleInputChange}
onSelect={props.handleInputSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<FormGroup>
<Label for="exampleSearch">Search Address</Label>
<Input
{...getInputProps({
className: 'location-search-input',
})}
type="search"
name="search"
id="exampleSearch"
placeholder="Enter Store Location"
/>
</FormGroup>
<div className="autocomplete-dropdown-container">
{loading && (
<div>
<Spinner size="sm" color="primary" />
Loading...
</div>
)}
{suggestions.map(suggestion => {
const className = suggestion.active ? 'suggestion-item--active' : 'suggestion-item';
const style = suggestion.active
? { backgroundColor: '#007bff', cursor: 'pointer', color: 'white' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<Row className="mb-3" style={{ width: '100%', height: '200px' }}>
<Col>
<Map
id="google-map"
ref={ref} // <<=== setting ref here
style={{ width: '100%', height: '200px' }}
google={props.google}
zoom={8}
initialCenter={{ lat: 47.444, lng: -122.176 }}
onClick={(t, map, e) => props.updateMarker(e.latLng, map)}
>
{props.markerLatLong && <Marker position={props.markerLatLong} />}
</Map>
</Col>
</Row>
</div>
);
});
export default GoogleApiWrapper({
apiKey: process.env.REACT_APP_GOOGLE_API_KEY,
libraries: ['places'],
})(InputAndMap);
This is my parent component, where I want to call the map panto function.
import React, { useState, useEffect } from 'react';
import { Button, Form, Spinner, Container } from 'reactstrap';
import { Redirect } from 'react-router-dom';
import { geocodeByAddress, getLatLng } from 'react-places-autocomplete';
import firebase from 'firebase/app';
import NavBarMenu from '../components/NavBarMenu';
import InputAndMap from '../components/InputAndMap';
import fire from '../config/fire';
function StoreScreen(props) {
const [isLoading, setIsLoading] = useState(false);
const [markerLatLong, setMarkerLatLong] = useState(null);
const [city, setCity] = useState('');
const [address, setAddress] = useState('');
const [redirect, setRedirect] = useState(false);
const ref = React.createRef();
const handleInputChange = address => {
setAddress(address);
};
const handleInputSelect = address => {
setAddress(address);
geocodeByAddress(address)
.then(results => {
processCity(results);
getLatLng(results[0])
.then(latLng => {
console.log('Success', latLng);
console.log(ref);// ==============> this return {current: null}
// ref.current.panTo(latLng);// ==> So I am unable to call this
})
.catch(error => console.error('Error', error));
})
.catch(error => console.error('Error', error));
};
return (
<div>
<NavBarMenu isShopKeeper />
<Container className="h-100">
<Form onSubmit={handleSubmit}>
<h5 className="text-center">Add Store</h5>
<InputAndMap
ref={ref}
markerLatLong={markerLatLong}
updateMarker={updateMarker}
handleInputChange={handleInputChange}
handleInputSelect={handleInputSelect}
address={address}
/>
{isLoading ? (
<div className="row mx-auto justify-content-center align-items-center flex-column">
<Spinner color="secondary" />
</div>
) : (
<Button
disabled={!markerLatLong || !city || !address}
className="mb-4"
color="primary"
size="lg"
block
>
Add Store
</Button>
)}
</Form>
</Container>
</div>
);
}
export default StoreScreen;
I am also attaching the image for better visualizing my problem.
Map.panTo changes the center of the map to the given LatLng in Maps JavaScript API. Since you are using google-maps-react library, you can use react states as value of the center parameter of this library to change the value of the Map's center everytime the state changes. In my example code below, I use the code from the getting started docs of react-places-autocomplete and incorporated it with a simple google-maps-react code.
Here's how I declare the state of the center which currently have a value:
state = {
center: {
lat: 40.854885,
lng: -88.081807
},
address: ""
};
Here's the handleSelect event from the react-places-autocomplete library where it geocodes the selected place from the autocomplete. Then you can see that I set the state of the center to the latLng of the geocoded address.
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.setState({ center: latLng }))
.catch(error => console.error("Error", error));
};
Here's how I call the Map component of the google-maps-react library where the value of center parameter is the value of the state named center.
<Map
className="map"
google={this.props.google}
onClick={this.onMapClicked}
center={this.state.center}
style={{ height: "100%", position: "relative", width: "100%" }}
zoom={13}
/>
Here's a complete code snippet and the working code on how I incorporated the 2 libraries you are using to change the center of the map everytime you choose an address from autocomplete:
import React, { Component } from "react";
import { Map, GoogleApiWrapper } from "google-maps-react";
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng
} from "react-places-autocomplete";
export class MapContainer extends Component {
state = {
center: {
lat: 40.854885,
lng: -88.081807
},
address: ""
};
handleChange = address => {
this.setState({ address });
};
handleSelect = address => {
geocodeByAddress(address)
.then(results => getLatLng(results[0]))
.then(latLng => this.setState({ center: latLng }))
.catch(error => console.error("Error", error));
};
render() {
if (!this.props.loaded) return <div>Loading...</div>;
return (
<div>
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
>
{({
getInputProps,
suggestions,
getSuggestionItemProps,
loading
}) => (
<div>
<input
{...getInputProps({
placeholder: "Search Places ...",
className: "location-search-input"
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: "#fafafa", cursor: "pointer" }
: { backgroundColor: "#ffffff", cursor: "pointer" };
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
style
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
<Map
className="map"
google={this.props.google}
center={this.state.center}
style={{ height: "100%", position: "relative", width: "100%" }}
zoom={13}
/>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey: "YOUR_API_KEY"
})(MapContainer);
import React from 'react';
import {GoogleMap, withScriptjs, withGoogleMap, Marker} from 'react-google-maps';
import {db} from './Firebase';
import {useState, useEffect} from 'react';
import InfoWindow from 'react-google-maps/lib/components/InfoWindow';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import { CssBaseline } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import IconButton from '#material-ui/core/IconButton'
import KeyboardArrowLeftRounded from '#material-ui/icons/KeyboardArrowLeftOutlined';
const useStyles = makeStyles( theme =>({
appBar: {
backgroundColor: '#1976d2'
},
card :{
marginTop: 60
}
}));
const Maps = (props) =>
{
const classes = useStyles();
const [positions, setPosition] = useState([])
useEffect(() => {
const unsub = db.collection('Location').onSnapshot (snapshot => {
const allPositions = snapshot.docs.map(doc => ({
id: doc.id,
... doc.data()
}));
setPosition(allPositions);
})
return () => {
unsub();
};
}, [])
const WrappedMap = withScriptjs(withGoogleMap(props => (
<GoogleMap defaultZoom = {13}
defaultCenter = {{ lat: -1.292066 , lng : 36.821945}}>
{
positions.map(positioning => (
props.isMarkerShown &&
<Marker key = {positioning.id}
position = {{lat: positioning.Latitude , lng: positioning.Longitude}}
></Marker>
)
)
}
{
positions.map(positioning => (
<InfoWindow key = {positioning.id} defaultPosition = {{lat: positioning.Latitude, lng: positioning.Longitude}}>
<div>
{positioning.Team} <br/>
Message
</div>
</InfoWindow>
))
}
</GoogleMap>)));
return (
<div>
<div>
<CssBaseline/>
<AppBar position = "fixed" color = "primary" className = {classes.appBar}>
<Toolbar>
<IconButton color = "inherit" edge = "start" onClick={() => props.history.goBack()}>
<KeyboardArrowLeftRounded/>
</IconButton>
</Toolbar>
</AppBar>
</div>
<div>
<Card className = {classes.card}>
<CardContent>
<div style = {{width: "97vw", height: "90vh"}}>
<WrappedMap
isMarkerShown
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</CardContent>
</Card>
</div>
</div>
)
};
export default Maps;
I am using the code above to render markers and infowindows for locations that I'm fetching from Firebase. Currently when an update is made in Firebase, the whole mapview is rendered again in order to update the position of the markers and infowindows. How do I go about re-rendering just the marker when an update is made in Firebase.
In your example WrappedMap gets re-created every time. One solution (to prevent Google Maps API reload and maps re-render) would be to place the instantiation of WrappedMap component outside of Maps:
const WrappedMap = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultZoom={5}
defaultCenter={{ lat: -24.9929159, lng: 115.2297986 }}
>
{props.places.map(
position =>
props.isMarkerShown && (
<Marker
key={position.id}
position={{
lat: position.lat,
lng: position.lng
}}
></Marker>
)
)}
</GoogleMap>
))
);
and then just pass positions prop to reflect updated positions on map:
function Map() {
return (
<div>
<WrappedMap
isMarkerShown
positions={positions}
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
You can prevent re-rendering by making use of PureComponent.
In your case positioning is re-rendering the Marker and InfoWindow.
The Updated Code below will only re-render the updated/newly-added Markers.
import React from 'react';
import {GoogleMap, withScriptjs, withGoogleMap, Marker} from 'react-google-maps';
import {db} from './Firebase';
import {useState, useEffect} from 'react';
import InfoWindow from 'react-google-maps/lib/components/InfoWindow';
import Card from '#material-ui/core/Card';
import CardContent from '#material-ui/core/CardContent';
import AppBar from '#material-ui/core/AppBar';
import Toolbar from '#material-ui/core/Toolbar';
import { CssBaseline } from '#material-ui/core';
import { makeStyles } from '#material-ui/styles';
import IconButton from '#material-ui/core/IconButton'
import KeyboardArrowLeftRounded from '#material-ui/icons/KeyboardArrowLeftOutlined';
const useStyles = makeStyles( theme =>({
appBar: {
backgroundColor: '#1976d2'
},
card :{
marginTop: 60
}
}));
class MarkerPureComponent extends React.PureComponent {
render () {
return (
<Marker
position = {this.props.position}
>
</Marker>
)
}
}
class InfoWindowPureComponent extends React.PureComponent {
render () {
return (
<InfoWindow defaultPosition = {this.props.defaultPosition}>
{this.props.children}
</InfoWindow>
)
}
}
const Maps = (props) =>
{
const classes = useStyles();
const [positions, setPosition] = useState([])
useEffect(() => {
const unsub = db.collection('Location').onSnapshot (snapshot => {
const allPositions = snapshot.docs.map(doc => ({
id: doc.id,
... doc.data()
}));
setPosition(allPositions);
})
return () => {
unsub();
};
}, [])
const WrappedMap = withScriptjs(withGoogleMap(props => (
<GoogleMap defaultZoom = {13}
defaultCenter = {{ lat: -1.292066 , lng : 36.821945}}>
{
positions.map(positioning => (
props.isMarkerShown &&
<MarkerPureComponent key = {positioning.id}
position = {{lat: positioning.Latitude , lng: positioning.Longitude}}
></MarkerPureComponent>
)
)
}
{
positions.map(positioning => (
<InfoWindowPureComponent key = {positioning.id} defaultPosition = {{lat: positioning.Latitude, lng: positioning.Longitude}}>
<div>
{positioning.Team} <br/>
Message
</div>
</InfoWindowPureComponent>
))
}
</GoogleMap>)));
return (
<div>
<div>
<CssBaseline/>
<AppBar position = "fixed" color = "primary" className = {classes.appBar}>
<Toolbar>
<IconButton color = "inherit" edge = "start" onClick={() => props.history.goBack()}>
<KeyboardArrowLeftRounded/>
</IconButton>
</Toolbar>
</AppBar>
</div>
<div>
<Card className = {classes.card}>
<CardContent>
<div style = {{width: "97vw", height: "90vh"}}>
<WrappedMap
isMarkerShown
googleMapURL={`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=AIzaSyD29SDFXKcqARovEjwUqKl0ysEFKK7GCmU`}
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%` }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
</CardContent>
</Card>
</div>
</div>
)
};
export default Maps;
I am trying to get the map to be centered to the defaultMarker. However, when the map finish loading the view was slightly above the default marker. I have to scroll down to the middle to see the defaultMarker.
Any idea how I can center the view when the map is loaded? I am using React, redux, and react-google-maps.
import React, {Component} from 'react';
import {withGoogleMap, GoogleMap, Marker} from "react-google-maps";
import {connect} from 'react-redux';
class VenuesMap extends Component {
render() {
let markers;
if (this.props.venues !== null) {
markers = this.props.venues.map((venue, i) => {
return (
<Marker
key={i}
position={{ lat: venue.location.lat, lng: venue.location.lng}}
/>
)
})
} else {
markers = <Marker position={{ lat: 40.7589, lng:-73.9851}}/>
}
const MapWithAMarker = withGoogleMap(props =>
<GoogleMap
defaultCenter={{ lat: 40.7589, lng: -73.9851 }}
center={{ lat: 40.7589, lng: -73.9851 }}
zoom={15}
>
{markers}
</GoogleMap>
);
const googleMap = <MapWithAMarker
containerElement={<div style={{
height: `410vh`
}} />}
mapElement={<div style={{ height: `410vh`
}} />}
/>
return (
<div>
{googleMap}
</div>
)
}
}
const stateToProps = (state) => {
return {venues: state.venue.venues}
}
export default connect(stateToProps)(VenuesMap)
maximum window height is 100vh.
just try height: 100vh or less.
Where exactly do I call fitBounds or map.fitbounds. I am confused where to put it once I have the bounds or how to use it. I have set the bounds to a local state of bounds.
I have looked at this post here react-google-maps: how to use fitBounds, panBy, panTo, panToBounds public APIs? and either I just do it different or something because it does not make much since to me.
I have created a bounds const and then each time I make a marker I have added each of those to the bounds doing bounds.extends(Marker in here)
The code is a little messy so I will point out where I do this in it. Inside the filterItemsByRadius is where I create and set the bounds. At the end of the map function I think set the state of bounds to the bounds.
/* global google */
import { default as React, Component } from 'react';
import raf from 'raf';
import canUseDOM from 'can-use-dom';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import {
withGoogleMap,
GoogleMap,
Circle,
InfoWindow,
Marker,
withScriptjs,
} from 'react-google-maps';
import geolib from 'geolib';
import { geolocated } from 'react-geolocated';
import ItemList from './ItemList';
import { Col } from 'react-bootstrap';
import Paper from 'material-ui/Paper';
import Img from 'react-image';
import RaisedButton from 'material-ui/RaisedButton';
import FontIcon from 'material-ui/FontIcon';
import CreateRadius from './CreateRadius';
import offerActions from '../../redux/actions/offerActions';
const googleMapURL =
'https://maps.googleapis.com/maps/api/js?libraries=places,geometry&key=AIzaSyA7XEFRxE4Lm28tAh44M_568fCLOP_On3k';
const isJson = str => {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
};
const GeolocationGoogleMap = withScriptjs(
withGoogleMap(props => (
<GoogleMap defaultZoom={6} zoom={props.zoom} center={props.center} onClick={props.onMapClick}>
{props.center && (
<Marker
position={props.center}
title={"User's Location"}
options={{ icon: require('./assets/blueDot.png') }}
>
{props.showCenterInfo && (
<InfoWindow>
<div>User's Location</div>
</InfoWindow>
)}
</Marker>
)}
{/* <Circle
center={props.center}
radius={props.zoom}
options={{
fillColor: 'red',
fillOpacity: 0.2,
strokeColor: 'red',
strokeOpacity: 1,
strokeWeight: 1,
}}
/> */}
{props.markers.map((marker, index) => {
const onClick = () => props.onMarkerClick(marker);
const onCloseClick = () => props.onCloseClick(marker);
return (
<Marker
key={index}
position={marker.position}
title={marker.number.toString()}
onClick={onClick}
options={{ icon: 'https://image.ibb.co/evMHxF/shopping_zone_marker_1.png' }}
>
{marker.showInfo && (
<InfoWindow onCloseClick={onCloseClick}>
<div>
<ItemList marker={marker} />
</div>
</InfoWindow>
)}
</Marker>
);
})}
</GoogleMap>
)),
);
class OfferMap extends Component {
constructor(props) {
super(props);
this.state = {
currentPosition: null,
center: null,
content: null,
radius: 15, // ACZ --> put this const in config_env.
showCenterInfo: true,
markers: [],
zoom: 6,
bounds: null,
};
this.handleMarkerClick = this.handleMarkerClick.bind(this);
this.handleCloseClick = this.handleCloseClick.bind(this);
this.handleMapClick = this.handleMapClick.bind(this);
this.filterItemsByRadius = this.filterItemsByRadius.bind(this);
this.radiusChange = this.radiusChange.bind(this);
this.zoomChange = this.zoomChange.bind(this);
}
componentWillReceiveProps(props) {
if (props.coords && !props.coords.positionError) {
this.setState({ center: { lat: props.coords.latitude, lng: props.coords.longitude } });
} else {
fetch('http://ip-api.com/json')
.then(res => res.json())
.then(data => {
this.setState({ center: { lat: data.lat, lng: data.lon } });
})
.catch(error => {
this.setState({ content: `Error: The Geolocation service failed (${error.message}).` });
});
}
this.setState({ markers: props.items });
}
handleMapClick() {
this.setState({ showCenterInfo: false });
}
handleMarkerClick(targetMarker) {
this.setState({
markers: this.state.markers.map(marker => {
if (marker._id === targetMarker._id) {
return {
...marker,
showInfo: true,
};
}
return marker;
}),
});
}
handleCloseClick(targetMarker) {
this.setState({
markers: this.state.markers.map(marker => {
if (marker._id === targetMarker._id) {
return {
...marker,
showInfo: false,
};
}
return marker;
}),
});
}
filterItemsByRadius(userRadius) {
const items = this.state.markers;
const markers = [];
const bounds = new google.maps.LatLngBounds();
items.map((item, i) => {
let itemGeolocation;
let itemDescription = 'NO DESCRIPTION';
let itemThumb;
// Should be ready - tbaustinedit
if (item) {
itemDescription = item.description;
itemThumb = item.media.mediaVault[item.media.defaultIdx] || {
mediaType: 'txt',
};
}
if (item.geolocation) {
itemGeolocation = item.geolocation.coords;
}
if (this.state.center) {
const currentLocation = {
latitude: this.state.center.lat,
longitude: this.state.center.lng,
};
const distanceArr = geolib.orderByDistance(currentLocation, [itemGeolocation]);
const miles = (distanceArr[0].distance / 1609.34).toFixed(2);
if (miles <= userRadius) {
const loc = new google.maps.LatLng(itemGeolocation.lat, itemGeolocation.lng);
bounds.extends(loc);
markers.push({
_id: item._id,
position: itemGeolocation,
number: i,
content: itemDescription,
price: item.price,
quantity: item.quantity,
currency: item.currency,
category: item.category,
title: item.title,
offer: item.offer,
thumbnail: itemThumb,
showInfo: item.showInfo || false,
});
}
}
});
this.setState({
bounds,
});
return markers;
}
radiusChange(event) {
console.log(event.target.value);
this.setState({
radius: event.target.value,
});
const { filter } = this.props.offers;
filter.radius = event.target.value;
this.props.sortOffersNew(filter);
}
zoomChange(e) {
console.log('value', e.target.value);
this.setState({ zoom: Number(e.target.value) });
}
render() {
const markers = this.filterItemsByRadius(this.state.radius);
return (
<Col xs={12} smOffset={0} mdOffset={0}>
<div>
<div style={{ fontFamily: 'Roboto', fontStyle: 'normal' }}>
Offers within radius of: {' '}
<input type="text" defaultValue={this.state.radius} onChange={this.radiusChange} /> {' '}
miles
<br />
</div>
{/* <CreateRadius
radiusChange={this.radiusChange}
numOffers={markers.length}
initRadius={this.state.zoom}
/> */}
</div>
<br />
<div
style={{
width: '100%',
height: '500px',
}}
>
<GeolocationGoogleMap
googleMapURL={googleMapURL}
loadingElement={<div style={{ height: '100%' }} />}
containerElement={<div style={{ height: '100%' }} />}
mapElement={<div style={{ height: '100%' }} />}
center={this.state.center}
showCenterInfo={this.state.showCenterInfo}
content={this.state.content}
radius={this.state.radius}
onMapClick={this.handleMapClick}
onMarkerClick={this.handleMarkerClick}
onCloseClick={this.handleCloseClick}
markers={markers}
zoom={this.state.zoom}
bounds={this.state.bounds}
/>
</div>
</Col>
);
}
}
function mapStateToProps({ browser, offers }) {
return { browser, offers };
}
const dispatchToProps = dispatch => ({
sortOffersNew: filter => dispatch(offerActions.sortOffers(filter)),
});
export default connect(mapStateToProps, dispatchToProps)(
geolocated({
positionOptions: {
enableHighAccuracy: false,
},
userDecisionTimeout: 5000,
})(OfferMap),
);
You can access fitBounds by adding a ref to the DOM element that google-maps-react generates.
first, add a ref:
<GoogleMap ref={(ref) => { this.map = ref; }}>...</GoogleMap>
Once the component is mounted, you will be able to call fitBounds using the ref.
this.map.fitBounds(bounds)
You can assign a ref to the GoogleMap Component, then call the fitBounds() function using a lifecycle hook.
import React, { useRef, useEffect } from 'react';
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from "react-google-maps";
import { GOOGLE_API_KEY } from '../../config/config';
// =======================================================================
// GOOGLE MAPS
// =======================================================================
const RegularMap = withScriptjs(
withGoogleMap(
({ defaultCenter, markers }) => {
const mapRef = useRef(null);
// Fit bounds function
const fitBounds = () => {
const bounds = new window.google.maps.LatLngBounds();
markers.map(item => {
bounds.extend(item.position);
return item.id
});
mapRef.current.fitBounds(bounds);
};
// Fit bounds on mount, and when the markers change
useEffect(() => {
fitBounds();
}, [markers]);
return (
<GoogleMap ref={mapRef} defaultCenter={defaultCenter}>
{markers.map(
({ position }, index) => <Marker key={`marker_${index}`} position={position} />
)}
</GoogleMap>
);
})
);
// =======================================================================
// THE MAIN COMPONENT
// =======================================================================
const MapView = () => {
const markers = [
{ position: { lat: 37.778519, lng: -122.405640 } },
{ position: { lat: 6.4454594, lng: 3.449074 } }
];
return (
<RegularMap
defaultCenter={{ lat: 6.4454594, lng: 3.449074 }}
markers={markers}
googleMapURL={`https://maps.googleapis.com/maps/api/js?key=${GOOGLE_API_KEY}`}
loadingElement={<div className="loader" />}
containerElement={<div className="mapContainer" style={{ height: "400px" }} />}
mapElement={<div className="map" style={{ height: '100%' }} />}
/>
);
}
export default MapView;
You can use fitbounds in componentDidMount or render function
add ref to GoogleMap and apply fitbounds
<GoogleMap ref={map => map && map.fitBounds(bounds)}> .... </GoogleMap>
The example demonstrates how to center viewport for the given markers:
const MapWithAMarkers = compose(
withProps({
googleMapURL: "https://maps.googleapis.com/maps/api/js?key=AIzaSyC4R6AN7SmujjPUIGKdyao2Kqitzr1kiRg",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
lifecycle({
componentWillMount() {
this.setState({
zoomToMarkers: map => {
//console.log("Zoom to markers");
const bounds = new window.google.maps.LatLngBounds();
map.props.children.forEach((child) => {
if (child.type === Marker) {
bounds.extend(new window.google.maps.LatLng(child.props.position.lat, child.props.position.lng));
}
})
map.fitBounds(bounds);
}
})
},
}),
withScriptjs,
withGoogleMap
)(props =>
<GoogleMap ref={props.zoomToMarkers} defaultZoom={5} defaultCenter={{ lat: 25.0391667, lng: 121.525 }}>
{props.markers.map(marker => (
<Marker
key={marker.id}
position={{ lat: marker.lat, lng: marker.lng }}
/>
))}
</GoogleMap>
);
Demo
I wanted to restrict my map (https://developers.google.com/maps/documentation/javascript/examples/control-bounds-restriction).
I used the options property with GoogleMap component with https://www.npmjs.com/package/#react-google-maps/api library.
The library is complete re-write of the react-google-maps library
CODE:
<GoogleMap
mapContainerStyle={containerStyle}
center={center}
zoom={10}
ref={(ref) => {this.state.map = ref}}
onLoad={this.onGoogleMapLoad}
options={{
restriction: {
latLngBounds: {
north: 19.137384, // Mumbai
south: 18.510866, // Pune
west: 72.874748, // Mumbai
east: 73.879864, // Pune
},
strictBounds: false,
}
}}
>