react components keeps re-rendering - reactjs

hey everyone I'm having re-render issues with my component. every time page loads CISW308Assignment5 component renders. Then the .map functions renders 5 times. After that user is prompted for using geolocation services, and everything re-renders again.
the idea behind this madness:
on page load user is prompter for geolocation. it is stored incase user will later request directions to any of the places. page has dropdown list of some places. each dropdown contains basic info about the place. phone info is a link, and on a mobile device when clicked on; will prompt user if they want to initiate a call. address info is a link to that place on google maps. show map button opens a modal, and with the pre-coded coordinates the map center and pins that location. the modal contains get direction button which when you click on sends user to google maps website. along with the redirect it sends your coordinates and the places coordinates to get the directions.
CISW305Assignment5.tsx
import React, { useEffect, useState } from 'react';
import type { FC } from 'react';
import { Helmet } from 'react-helmet';
import Template from 'layout/Template';
import Assignment from 'components/Assignment';
import ExtraCredit from 'components/ExtraCredit';
import Submissions from 'components/Submissions';
import { Accordion, Image } from 'semantic-ui-react';
import { Boudin, Chilis, InNout, PandaExpress, PaneraBread } from 'assets';
import MapModal from 'components/MapModal';
import { v4 as uuidv4 } from 'uuid';
const assignment = [
{
completed: true,
name: 'a clickable “SHOW MAP” buttons or links for 2 or more Locations that opens a “map page” for each of your food locations.'
}
];
const extracredit = [
{
completed: true,
name: `a clickable “GET DIRECTIONS” link or button on each map page, that links to another map with directions from the users' current location to the restaurant location.`
}
];
const favoritePlaces = [
{
id: uuidv4(),
src: Boudin,
name: 'Boudin Bakery',
phoneNumber: '(916) 973-1849',
phoneNumberLink: 'tel:+1-916-973-1849',
address: '2573 Fair Oaks Blvd, Sacramento, CA 95825',
addressLink:
'https://www.google.com/maps/place/Boudin+SF/#38.5749736,-121.4019994,17z/data=!3m1!4b1!4m5!3m4!1s0x809ada557d7929af:0x4b8201a127d64751!8m2!3d38.5749736!4d-121.4019994',
placeCoords: { lat: 38.57498557564539, lng: -121.40199854938247 }
},
{
id: uuidv4(),
src: InNout,
name: 'In-N-Out',
phoneNumber: '(800) 786-1000',
phoneNumberLink: 'tel:+1-800-786-1000',
address: '3501 Truxel Rd, Sacramento, CA 95834',
addressLink: 'https://goo.gl/maps/5rtssiGZb2KyLQpKA',
placeCoords: { lat: 38.63682135923305, lng: -121.5041714687768 }
},
{
id: uuidv4(),
src: Chilis,
name: `Chili's`,
phoneNumber: '(916) 285-8703',
phoneNumberLink: 'tel:+1-916-285-8703',
address: '3870 Truxel Rd, Sacramento, CA 95834',
addressLink: 'https://goo.gl/maps/iFCDnk23ywRSH5HN7',
placeCoords: { lat: 38.6385674159852, lng: -121.50372754232959 }
},
{
id: uuidv4(),
src: PandaExpress,
name: 'Panda Express',
phoneNumber: '(916) 575-8927',
phoneNumberLink: 'tel:+1-916-575-8927',
address: '3571 N Freeway Blvd, Sacramento, CA 95834',
addressLink: 'https://goo.gl/maps/qwzi1SxXbahKDNKd8',
placeCoords: { lat: 38.640220171851546, lng: -121.4999965846592 }
},
{
id: uuidv4(),
src: PaneraBread,
name: 'Panera Bread',
phoneNumber: '(916) 515-2012',
phoneNumberLink: 'tel:+1-916-515-2012',
address: '3571 N Freeway Blvd, Sacramento, CA 95834',
addressLink: 'https://goo.gl/maps/erkno1X3d68db3BK9',
placeCoords: { lat: 38.6402061514062, lng: -121.50000428469711 }
}
];
const CISW308Assignment5: FC = () => {
const [userCoords, setUserCoords] = useState<{
lat: number;
lng: number;
}>({
lat: 0,
lng: 0
});
const [activeIndex, setActiveIndex] = useState(0);
useEffect(() => {
setActiveIndex(0);
}, []);
useEffect(() => {
const getLocation = () => {
navigator.geolocation.getCurrentPosition(
({ coords }) => setUserCoords({ lat: coords.latitude, lng: coords.longitude }),
({ message }) => {
alert(`${message}. Please try enabling Geolocation services.`);
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
);
};
const getUserLocation = () => {
if (navigator.permissions && navigator.permissions.query) {
navigator.permissions.query({ name: 'geolocation' }).then(function (result) {
const permission = result.state;
if (permission === 'granted' || permission === 'prompt') {
getLocation();
}
});
} else if (navigator.geolocation) {
getLocation();
} else {
alert("Unfortunately geolocation services aren't supported by your browser");
}
};
getUserLocation();
}, []);
// eslint-disable-next-line #typescript-eslint/no-explicit-any
const handleClick = (e: any, titleProps: any) => {
const { index } = titleProps;
const newIndex = activeIndex === index ? -1 : index;
setActiveIndex(newIndex);
};
console.log('i rendered');
return (
<>
<Helmet>
<title>CISW 308 - Assignment 5</title>
</Helmet>
<Template>
<Assignment assignment={assignment} />
<ExtraCredit assignment={extracredit} />
<Submissions>
<Accordion fluid styled style={{ maxWidth: '550px' }}>
{favoritePlaces.map((place, index) => {
console.log('i rendered');
return (
<div key={place.id}>
<Accordion.Title active={activeIndex === index} index={index} onClick={handleClick}>
<Image style={{ maxWidth: '30px' }} src={place.src} />
{place.name}
</Accordion.Title>
<Accordion.Content active={activeIndex === index}>
Phone Number: <a href={place.phoneNumberLink}>{place.phoneNumber}</a>
<br />
Address:{' '}
<a target='_blank' rel='noopener noreferrer' href={place.addressLink}>
{place.address}
</a>
<br />
<MapModal placeCoords={place.placeCoords} userCoords={userCoords} />
</Accordion.Content>
</div>
);
})}
</Accordion>
</Submissions>
</Template>
</>
);
};
export default CISW308Assignment5;
MapModal.tsx
import React, { useState } from 'react';
import type { FC } from 'react';
import { Button, Modal } from 'semantic-ui-react';
import MapContainer from './MapContainer';
const MapModal: FC<{
userCoords: {
lat: number;
lng: number;
};
placeCoords: {
lat: number;
lng: number;
};
}> = ({ placeCoords, userCoords }) => {
console.log('🚀 ~ file: MapModal.tsx ~ line 18 ~ userCoords', userCoords);
console.log('🚀 ~ file: MapModal.tsx ~ line 20 ~ placeCoords', placeCoords);
const [open, setOpen] = useState(false);
const handleClick = () => {
try {
window.open(
`https://www.google.es/maps/dir/'${userCoords?.lat},${userCoords?.lng}'/'${placeCoords?.lat},${placeCoords?.lng}'`,
'_blank',
'noopener,noreferrer'
);
} catch ({ message }) {
console.error(message);
}
};
return (
<Modal
closeIcon
open={open}
trigger={<Button>SHOW MAP</Button>}
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
>
<Modal.Header style={{ textAlign: 'center' }}>
<Button onClick={handleClick}>GET DIRECTIONS</Button>
</Modal.Header>
<Modal.Content>
<Modal.Description>
<MapContainer placeCoords={placeCoords} />
</Modal.Description>
</Modal.Content>
</Modal>
);
};
export default MapModal;
MapContainer.tsx
import React from 'react';
import type { FC } from 'react';
import { GoogleMap, LoadScript, Marker } from '#react-google-maps/api';
const MapContainer: FC<{
placeCoords: {
lat: number;
lng: number;
};
}> = ({ placeCoords }) => {
console.log('🚀 ~ file: MapContainer.tsx ~ line 11 ~ placeCoords', placeCoords);
const mapStyles = {
height: '500px',
width: '100%'
};
return (
<div>
<LoadScript googleMapsApiKey='API_KEY'>
<GoogleMap mapContainerStyle={mapStyles} zoom={18} center={placeCoords}>
<Marker position={placeCoords} />
</GoogleMap>
</LoadScript>
</div>
);
};
export default MapContainer;
on page load:
image
page ui:
image
map modal:
image
thank you for any and all help in advance.

Related

How to use MarkerClusterer with #googlemaps/react-wrapper

After following this example provided by google for adding a map and markers to a react application using #googlemaps/react-wrapper, I decided to try adding marker clustering to my app. However, I am unable to get the clusters to appear.
I tried following this question/answer but no clusters have appeared despite adding new MarkerClusterer({ ref, markers }); to my code:
App.js
/*global google*/
import { useState, useEffect, useRef } from "react";
import { Wrapper } from "#googlemaps/react-wrapper";
import { MarkerClusterer } from "#googlemaps/markerclusterer";
import { createCustomEqual } from "fast-equals";
const markerList = [
{ id: "A12345", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 44.459744, lng: -73.214126, assetName: "A" },
{ id: "B09423", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 44.465291, lng: -73.190723, assetName: "A" },
{ id: "C98765", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 44.476949, lng: -73.210578, assetName: "B" },
{ id: "D99999", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 44.444572, lng: -73.208741, assetName: "B" },
{ id: "E12345", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 38.459744, lng: -81.214126, assetName: "A" },
{ id: "F09423", uuid: "500924cf83424aad9e7d386bbec88ef6", lat: 38.465291, lng: -81.190723, assetName: "A" },
{ id: "G98765", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 38.476949, lng: -81.210578, assetName: "B" },
{ id: "H99999", uuid: "c0385833-e483-40d1-803f-2b4c26ae3799", lat: 38.444572, lng: -81.208741, assetName: "B" },
]
const render = (status) => {
return <h1>{status}</h1>;
};
const App = () => {
const [zoom, setZoom] = useState(8); // initial zoom
const [center, setCenter] = useState({ lat: 44.45, lng: -73.21 });
const onIdle = (m) => {
console.log("onIdle");
};
return (
<>
<div style={{ width: "500px", height: "500px" }}>
<Wrapper
apiKey={process.env.REACT_APP_GOOGLE_MAPS_API_KEY}
render={render}
>
<Map
center={center}
onIdle={onIdle}
zoom={zoom}
style={{ flexGrow: "1", height: "100%" }}
/>
</Wrapper>
</div>
</>
);
};
const Map = ({ onIdle, children, style, ...options }) => {
const ref = useRef(null);
const [map, setMap] = useState();
useEffect(() => {
if (ref.current && !map) {
setMap(new window.google.maps.Map(ref.current, {}));
}
// Add some markers to the map.
const markers = markerList.map((m) => {
return new window.google.maps.Marker({
position: { lat: parseFloat(m.lat), lng: parseFloat(m.lng) }
//map: map,
});
});
// Add a marker clusterer to manage the markers.
new MarkerClusterer({ ref, markers });
}, [ref, map]);
useDeepCompareEffectForMaps(() => {
if (map) {
map.setOptions(options);
}
}, [map, options]);
useEffect(() => {
if (map) {
["click", "idle"].forEach((eventName) =>
google.maps.event.clearListeners(map, eventName)
);
if (onIdle) {
map.addListener("idle", () => onIdle(map));
}
}
}, [map, onIdle]);
return (
<>
<div ref={ref} style={style} />
</>
);
};
const deepCompareEqualsForMaps = createCustomEqual((deepEqual) => (a, b) => {
if (a instanceof google.maps.LatLng || b instanceof google.maps.LatLng) {
return new google.maps.LatLng(a).equals(new google.maps.LatLng(b));
}
return deepEqual(a, b);
});
function useDeepCompareMemoize(value) {
const ref = useRef();
if (!deepCompareEqualsForMaps(value, ref.current)) {
ref.current = value;
}
return ref.current;
}
function useDeepCompareEffectForMaps(callback, dependencies) {
useEffect(callback, dependencies.map(useDeepCompareMemoize));
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
How can I get MarkerClusterer to function with #googlemaps/react-wrapper?
Replace
new MarkerClusterer({ ref, markers });
with
new MarkerClusterer({ map, markers });
PS: The answer to the mentioned question was updated

How to Fit Boundaries and Center React Google Maps

I am new to React Google Maps. I am using this library to create a map with several locations and trying fit the boundaries and center it. I have used this example.
After I populate the map, I want the map to change its center based on the places which have been populated rather than the default center. I am unable to achieve this. The following is my code.
import React, { useState, useEffect } from "react"
import {
GoogleMap,
LoadScript,
Marker,
InfoWindow,
} from "#react-google-maps/api"
const Map = () => {
const mapStyles = {
height: "400px",
width: "100%",
}
const defaultCenter = {
lat: 39.76866069032195,
lng: -86.15818042985295,
}
const locations = [
{
name: "Location 1",
location: {
lat: 41.3954,
lng: 2.162,
},
},
{
name: "Location 2",
location: {
lat: 41.3917,
lng: 2.1649,
},
},
{
name: "Location 3",
location: {
lat: 41.3773,
lng: 2.1585,
},
},
{
name: "Location 4",
location: {
lat: 41.3797,
lng: 2.1682,
},
},
{
name: "Location 5",
location: {
lat: 41.4055,
lng: 2.1915,
},
},
]
const [selected, setSelected] = useState({})
const [map, setMap] = useState(null)
const onSelect = item => {
setSelected(item)
}
const Somefunction = map => {
const bounds = new window.google.maps.LatLngBounds()
locations.map((location, i) => {
bounds.extend(new window.google.maps.LatLng(location.lat, location.lng))
})
map.fitBounds(bounds)
setMap(map)
}
return (
<div className="map-container">
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap
mapContainerStyle={mapStyles}
zoom={13}
center={defaultCenter}
onLoad={map => {
const bounds = new window.google.maps.LatLngBounds()
map.fitBounds(bounds)
setMap(map)
}}
>
{locations.map(item => {
return (
<Marker
key={item.name}
position={item.location}
onClick={() => onSelect(item)}
/>
)
})}
{selected.location && (
<InfoWindow
position={selected.location}
clickable={true}
onCloseClick={() => setSelected({})}
>
<p>{selected.name}</p>
</InfoWindow>
)}
</GoogleMap>
</LoadScript>
</div>
)
}
export default Map
With the current code, the map gets centered in some bizarre location and not in the middle of the 5 locations listed above.
I would also appreciate if someone can shed light on how to change the zoom level for different screen size automatically.
The problem I see in the use of functional components is that it is not as flexible as using class based components in terms of this kind of implementation. To be more precise, it would be more beneficial to make use of the react life cycles such as ComponentDidMount which you can only do when using class based components.
Furthermore, you may want to stay away from using 3rd party packages/libraries and instead implement it locally by dynamically adding the script. That way, you would be able to follow Google's official documentation instead of the the 3rd party's. The following is a sample I made to demonstrate this as well as making use of the bounds:
StackBlitz sample: https://stackblitz.com/edit/react-bounds-65524739
or
App.js
import React, { Component } from "react";
import { render } from "react-dom";
import Map from "./components/map";
import "./style.css";
class App extends Component {
render() {
return (
<Map
id="myMap"
options={{
center: { lat: 39.76866069032195, lng: -86.15818042985295 },
zoom: 8
}}
/>
);
}
}
export default App;
map.js
import React, { Component } from "react";
import { render } from "react-dom";
class Map extends Component {
constructor(props) {
super(props);
this.state = {
map: "",
locations: [
{
name: "Location 1",
location: {
lat: 41.3954,
lng: 2.162
}
},
{
name: "Location 2",
location: {
lat: 41.3917,
lng: 2.1649
}
},
{
name: "Location 3",
location: {
lat: 41.3773,
lng: 2.1585
}
},
{
name: "Location 4",
location: {
lat: 41.3797,
lng: 2.1682
}
},
{
name: "Location 5",
location: {
lat: 41.4055,
lng: 2.1915
}
}
]
};
}
onScriptLoad() {
const map = new window.google.maps.Map(
document.getElementById(this.props.id),
this.props.options
);
this.addMarker(map);
}
addMarker(map) {
const bounds = new window.google.maps.LatLngBounds();
this.state.locations.map((location, i) => {
new google.maps.Marker({
position: location.location,
map: map
});
bounds.extend(
new window.google.maps.LatLng(
location.location.lat,
location.location.lng
)
);
});
map.fitBounds(bounds);
}
componentDidMount() {
if (!window.google) {
var s = document.createElement("script");
s.type = "text/javascript";
s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`;
var x = document.getElementsByTagName("script")[0];
x.parentNode.insertBefore(s, x);
// Below is important.
//We cannot access google.maps until it's finished loading
s.addEventListener("load", e => {
this.onScriptLoad();
});
} else {
this.onScriptLoad();
}
}
render() {
return <div className="map" id={this.props.id} />;
}
}
export default Map;

InvalidValueError: setMap: not an instance of Map; and not an instance of StreetViewPanorama #react-google-maps/api

I am using #react-google-maps/api and loading the script from cdn to my public directory index.html file.
I get InvalidValueError: setMap: not an instance of Map; and not an instance of StreetViewPanorama and the markers do not appear on my map. Weird thing is that it is totally random to get this error. Sometimes it works without any problem. That is what I do not understand in the first place.
import React, { useEffect } from 'react';
import { GoogleMap, Marker } from '#react-google-maps/api';
import mapStyles from './mapStyles';
//google map height 100
import './MapDisplay.css';
import { connect } from 'react-redux';
const mapContainerStyle = {
height: '100%',
width: '100%',
};
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
};
const MapDisplay = ({
userLocation,
mouseHoverIdR,
selectedInfoR,
searchedResults,
unAuthNoSearchResults,
selectedLocationInfoWindowS,
}) => {
const onClickMarker = (e) => {
selectedLocationInfoWindowS({
selectedInfoR: e,
type: 'infoWindowLocations',
});
};
const onClickMap = () => {
selectedLocationInfoWindowS({
type: '',
selectedInfoR: {
_id: '',
},
});
};
return (
<div id='map'>
<GoogleMap
id='map_canvas'
mapContainerStyle={mapContainerStyle}
zoom={12}
center={userLocation}
options={options}
onClick={() => onClickMap()}
>
{!unAuthNoSearchResults &&
searchedResults.map((searchedResult) => {
if (
mouseHoverIdR === searchedResult._id ||
selectedInfoR._id === searchedResult._id
) {
var a = window.google.maps.Animation.BOUNCE;
}
return (
<Marker
position={searchedResult.coordinates}
key={searchedResult._id}
clickable
onClick={() => onClickMarker(searchedResult)}
animation={a}
id={'hello'}
/>
);
})}
</GoogleMap>
</div>
);
};
How can I fix the error that I get when I try to display the marker on the map?

How to call another function from withHandlers?

I am trying to implement google maps in my app using react-google-maps package. In map, I am showing multiple Marker and used MarkerCluster.
Until now I have no issue and easily implemented from the doc. But now I want to show InfoWindow if the marker is clicked.
So, I thought of making a function to get the click event and pass the markerId, so I can call the API and get the relevant data for that marker and then put it in infowindow in a tabular manner.
Now, the problem I am facing is:
1) Calling onToggleOpen from onMarkerClick
2) how to set data in infowindow object in onMarkerClick
All this problem I am facing is because i am using HOC i.e recompose.
I am used to Class implement but tried functional implement trying to make it purely stateless.
Reference link: https://tomchentw.github.io/react-google-maps/#infowindow
Following is my code:
import React, { Component } from "react";
import Header from "./Header.js";
import Sidebar from "./Sidebar.js";
import axios from "axios";
import imgmapcluster from "./pins/iconmapcluster.png";
import user from "./pins/user1copy.png";
import { compose, withProps, withHandlers } from "recompose";
import {
withScriptjs,
withGoogleMap,
GoogleMap,
Marker,
InfoWindow
} from "react-google-maps";
// const fetch = require("isomorphic-fetch");
const {
MarkerClusterer
} = require("react-google-maps/lib/components/addons/MarkerClusterer");
const MapWithAMarkerClusterer = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key=AIzaSyCHi5ryWgN1FcZI-Hmqw3AdxJQmpopYJGk&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `90vh` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withHandlers(
{
onMarkerClustererClick: () => markerClusterer => {
// console.log("markerCluster", markerClusterer);
const clickedMarkers = markerClusterer.getMarkers();
// console.log(`Current clicked markers length: ${clickedMarkers.length}`);
// console.log(clickedMarkers);
},
onMarkerClick: (props) => markerss => {
//calling api and setting info window object
props.isOpen=!props.isOpen //showing error
},
onToggleOpen: ({ isOpen }) => () => ({
isOpen: !isOpen
})
}
),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap
defaultZoom={5}
defaultCenter={{ lat: 22.845625996700075, lng: 78.9629 }}
>
<MarkerClusterer
onClick={props.onMarkerClustererClick}
averageCenter
styles={[
{
textColor: 'white',
url: imgmapcluster,
height: 68,
lineHeight: 3,
width: 70
}
]}
enableRetinaIcons
gridSize={50}
>
{props.markers.map((marker, index) => (
<Marker
key={index}
icon={user}
onClick={props.onMarkerClick.bind(props,marker)}
position={{ lat: marker.latitude, lng: marker.longitude }}
/>
))}
{props.isOpen && (
<InfoWindow
// position={{ lat: props.infowindow.lat, lng: props.infowindow.lng }}
onCloseClick={props.onToggleOpen}
>
<h4>hello</h4>
</InfoWindow>
)}
</MarkerClusterer>
</GoogleMap>
));
class DemoApp extends React.PureComponent {
componentWillMount() {
this.setState({ markers: [],isOpen:false,infowindow:{} });
}
componentDidMount() {
axios({
url: "http://staging.clarolabs.in:6067/farmerinfo/farmercoordinates",
method: "POST",
data: {
temp: "temp"
},
headers: {
"Content-Type": "application/json"
}
}).then(res => {
this.setState({ markers: res.data.data.list });
});
}
render() {
return <MapWithAMarkerClusterer markers={this.state.markers} isOpen={this.state.isOpen} InfoWindowobject={this.state.InfoWindowobject}/>;
}
}
In order to call a withHandler from another, you need to separate them within two handlers. Also you can make use of withStateHandler and store the infoWindow state
withStateHandlers(
{ infoWindow: null },
{
setInfoWindow: () => (value) => ({ infoWindow: value)
}
),
withHandlers({
onToggleOpen: ({ isOpen }) => () => ({
isOpen: !isOpen
})
}),
withHandlers(
{
onMarkerClustererClick: () => markerClusterer => {
// console.log("markerCluster", markerClusterer);
const clickedMarkers = markerClusterer.getMarkers();
// console.log(`Current clicked markers length: ${clickedMarkers.length}`);
// console.log(clickedMarkers);
},
onMarkerClick: (props) => markerss => {
const { setInfoWindow, onToggleOpen} = props;
//calling api and setting info window object
setInfoWindow({lat: res.lat, lng: res.lng}) // set infoWindow object here
onToggleOpen() // Toggle open state
}
}
),
You probably should use withStateHandlers to handle state. Also, you can write withHandlers more than once, so handlers inside following withHandlers will have access to other handlers.
compose(
// .....
withStateHandlers(
{ isOpen: false },
{
toggleOpen: (state) => () => ({ isOpen: !state.isOpen }),
// or
setOpen: () => (value) => ({ isOpen: value }),
}
),
withHandlers(
// .....
{
onMarkerClick: (props) => markerss => {
// .....
props.toggleOpen();
// .....
},
},
// .....
),
// .....
)

Center AND zoom by prop in react-google-maps?

I am building a Zillow-like map with real estate property points in page body, and filters in page header.
What I need is to center AND zoom react-google-maps map, when selecting Google place from external Google places filter. Currently I am able to center map on place select, but not zoom in (that is because i need to refetch data on every zoom in/out, so need to control zoom, see code below).
I tried to use fitBounds from here, but ref in my code is always undefined.
I have API that fetches points, based on filters, with params like this:
viewPort: {
northEast: { lat: 57.04834755670983, lng: 24.3255340332031 },
southWest: { lat: 56.83635114109457, lng: 23.93826596679685 }
},
priceMin: 0,
priceMax: 500,
propType: "RENT",
numRooms: 3,
...
In header section there is also address filter input, that has Google places autocomplete. When selected, Google provides info about place:
"geometry" : {
...
"viewport" : {
"northeast" : {
"lat" : 56.95662478029149,
"lng" : 24.2054917802915
},
"southwest" : {
"lat" : 56.9539268197085,
"lng" : 24.2027938197085
}
}
...
Here is my Map component:
import React, { Component } from 'react';
import { compose, withProps, withState, withHandlers } from "recompose";
import {
withGoogleMap,
GoogleMap,
Marker,
} from "react-google-maps";
import { MarkerClusterer } from "react-google-maps/lib/components/addons/MarkerClusterer";
const defaultMapOptions = {
fullscreenControl: false,
streetViewControl: false,
};
const PropertyMap = compose(
withProps({
loadingElement: <div>Loading...</div>,
containerElement: <div style={{ height: "500px" }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withState('zoom', 'onZoomChange', 11),
withHandlers(() => {
const refs = {
map: undefined,
}
return {
onMapMounted: () => ref => {
refs.map = ref
},
onZoomChanged: ({ onZoomChange, onMapBoundsChange }) => () => {
onZoomChange(refs.map.getZoom())
onMapBoundsChange(refs.map.getBounds());
},
onDragEnd: ({ onMapBoundsChange }) => () => {
onMapBoundsChange(refs.map.getBounds());
},
onMarkerClustererClick: () => (markerClusterer) => {
const clickedMarkers = markerClusterer.getMarkers()
console.log(`Current clicked markers length: ${clickedMarkers.length}`)
console.log(clickedMarkers)
}
}
}),
withGoogleMap
)(props =>
<GoogleMap
defaultCenter={props.defaultCenter ? props.defaultCenter : { lat: 56.9425, lng: 24.1319 }}
zoom={props.zoom}
ref={props.onMapMounted}
onZoomChanged={props.onZoomChanged}
onDragEnd={props.onDragEnd}
defaultOptions={defaultMapOptions}
>
<MarkerClusterer
onClick={props.onMarkerClustererClick}
averageCenter
enableRetinaIcons
gridSize={60}
key={props.mapUid}
>
{props.features !== null && props.features.map(feature => {
const coord = { lng: feature.geometry.coordinates[0], lat: feature.geometry.coordinates[1] };
return <Marker key={feature.id} position={coord} />;
})}
</MarkerClusterer>
</GoogleMap>
);
export default PropertyMap;
So I think I would like to make it possible to pass zoom param in props, like this:
<PropertyMap
mapUid={this.state.mapUid}
features={this.state.features}
key={this.state.defaultCenter ? `${this.state.defaultCenter.lat}` : '1'}
defaultCenter={this.state.defaultCenter}
defaultZoom={calculateZoomFromViewPort(this.state.viewPort)}
onMapBoundsChange={this.handleMapBoundsChange}
/>
This way I could calculateZoomFromViewPort(this.state.viewPort), but then I need to somehow set it to withState zoom default value.
So what I need is some how pass default zoom value to withState('zoom', 'onZoomChange', 11), (11 is hardcoded, currently). But I am new to recompose package, but is it heavily used in react-google-maps package examples.
If you have other ideas, how to zoom and center map based on viewport & center params from Google place, please share!
fitbounds() is the recommended way to achieve auto zoom. Remember refs always created after first render of the component. I have made changes in your code by adding fitBounds. There might be few syntax errors as I prefer to use react-google-maps like class based utility wrapper. It's easier to manage with redux and component lifecycles. Below is the latest code:
import React, { Component } from 'react';
import { compose, withProps, withState, withHandlers } from "recompose";
import {
withGoogleMap,
GoogleMap,
Marker,
} from "react-google-maps";
import { MarkerClusterer } from "react-google-maps/lib/components/addons/MarkerClusterer";
const defaultMapOptions = {
fullscreenControl: false,
streetViewControl: false,
};
const PropertyMap = compose(
withProps({
loadingElement: <div>Loading...</div>,
containerElement: <div style={{ height: "500px" }} />,
mapElement: <div style={{ height: `100%` }} />,
}),
withState('zoom', 'onZoomChange', 11),
withHandlers(() => {
const refs = {
map: undefined,
}
return {
onMarkersUpdate: ()=> () => {
if(refs.map){
const mapBounds = new google.maps.LatLngBounds();
//just add markers position into mapBounds
markers.forEach((marker)=>{
mapBounds.extend(marker.position)
});
this.refs.map.fitBounds(mapBbounds);
}
},
onZoomChanged: ({ onZoomChange, onMapBoundsChange }) => () => {
onZoomChange(refs.map.getZoom())
onMapBoundsChange(refs.map.getBounds());
},
onDragEnd: ({ onMapBoundsChange }) => () => {
onMapBoundsChange(refs.map.getBounds());
},
onMarkerClustererClick: () => (markerClusterer) => {
const clickedMarkers = markerClusterer.getMarkers()
console.log(`Current clicked markers length: ${clickedMarkers.length}`)
console.log(clickedMarkers)
}
}
}),
withGoogleMap
)(props =>
<GoogleMap
defaultCenter={props.defaultCenter ? props.defaultCenter : { lat: 56.9425, lng: 24.1319 }}
zoom={props.zoom}
ref='map'
onZoomChanged={props.onZoomChanged}
onDragEnd={props.onDragEnd}
defaultOptions={defaultMapOptions}
>
{props.onMarkersUpdate()}
<MarkerClusterer
onClick={props.onMarkerClustererClick}
averageCenter
enableRetinaIcons
gridSize={60}
key={props.mapUid}
>
{props.features !== null && props.features.map(feature => {
const coord = { lng: feature.geometry.coordinates[0], lat: feature.geometry.coordinates[1] };
return <Marker key={feature.id} position={coord} />;
})}
</MarkerClusterer>
</GoogleMap>
);
export default PropertyMap;
Please feel free to react out me. If you make a codesandbox for this. It will be easier to fix syntax errors. And Don't miss out fitbounds(). It makes things easier for us.

Resources