Using React Leaflet, I am trying to fire the Marker component's "click" event from a sibling component. In regular leaflet, this is done using something like below:
L.marker(lng,lat).fire('click');
L.marker(lng,lat).openPopup();
In the "Results" component, I have a list of results that when clicked, I would like to trigger the Marker component's events on the map.
render() {
<Results tileClicked2={this.tileClicked3.bind(this)} items={this.state.items}></Results>
<Map ref={m => { this.leafletMap = m; }}>
<TileLayer .../>
{this.state.items.map((value, index) => {
return (<Marker>...</Marker>)
}
In my "tileClicked3" function, I am able to log this.leafletMap
tileClicked3(index){
console.log(index);
console.log(this.leafletMap)
//I want to do:
// marker.fire();
// marker.openPopup();
}
Here is the console log when clicked:
I figure I must be close, but maybe not.
Any advice would be appreciated...
This can be done by keeping the clicked item index in state and getting the marker item at that index and rendering it.
import React, { Component } from "react";
import ReactDOM from "react-dom";
import { Map, TileLayer, Marker } from "react-leaflet";
import "./styles.css";
const listData = [
{
id: 1,
text: "Location A",
lng: 24.960937499999996,
lat: 38.54816542304656
},
{
id: 2,
text: "Location B",
lng: -103.71093749999999,
lat: 40.97989806962013
},
{
id: 3,
text: "Location C",
lng: 76.9921875,
lat: 24.84656534821976
}
];
const List = ({ onListItemClick }) => (
<div>
<ul>
{listData.map((aLocation, index) => (
<li
key={aLocation.id}
onClick={e => {
onListItemClick(index);
}}
>
{aLocation.text}
</li>
))}
</ul>
</div>
);
class App extends Component {
state = {
center: [51.505, -0.091],
zoom: 1,
markerIndex: 0
};
handleItemClick = index => {
console.log("show Marker for", listData[index]);
this.setState({
markerIndex: index
});
};
render() {
const markerItem = listData[this.state.markerIndex];
const MarkerToShow = <Marker position={[markerItem.lat, markerItem.lng]} />;
return (
<div>
<List onListItemClick={this.handleItemClick} />
<Map center={this.state.center} zoom={this.state.zoom}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
/>
{MarkerToShow}
</Map>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can find the working code here:https://codesandbox.io/s/mm6nmw9z29
Related
I used React Google Maps api in one of my Gatsby sites. I created the following component and imported it into one of my pages. Here is the code for the compenent.
import React, { useState } from "react"
import {
GoogleMap,
useLoadScript,
Marker,
InfoWindow,
} from "#react-google-maps/api"
import { useStaticQuery, graphql } from "gatsby"
import mapStyles from "./mapStyles"
const Indianapolis = {
lat: 39.768402,
lng: -86.158066,
}
const mapContainerStyle = {
height: "100%",
width: "100%",
}
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
}
const Map = () => {
const data = useStaticQuery(graphql`
{
allKmlPoint {
edges {
node {
properties {
name
Longitude
Latitude
FRP_Project_Numbers
description
styleUrl
styleHash
}
id
}
}
}
}
`)
const [selected, setSelected] = useState(null)
const frpLocation = data.allKmlPoint.edges
//console.log(process.env.GATSBY_GOOGLE_MAPS_API_KEY)
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.GATSBY_GOOGLE_MAPS_API_KEY,
})
const mapRef = React.useRef()
const onMapLoad = React.useCallback(map => {
mapRef.current = map
console.log(map)
}, [])
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
}, [])
if (loadError) return "Error"
if (!isLoaded) {
return "Loading..."
}
//console.log("comes here")
return (
<div className="map-container">
<span className="top-text">Project</span>
<span className="horizontal-line"></span>
<span className="bottom-text">
Locati<span className="full-color">o</span>ns
</span>
<span className="map-blurb">
FRP has a project portfolio across a wide geographic region. Click the
Map to Zoom and pan to the project locations for various market types.
</span>
<div className="map-wrapper">
<GoogleMap
zoom={8}
center={Indianapolis}
mapContainerStyle={mapContainerStyle}
options={options}
onUnmount={onUnmount}
onLoad={onMapLoad}
>
{frpLocation.map(marker => (
<Marker
key={marker.node.id}
position={{
lat: parseFloat(marker.node.properties.Latitude),
lng: parseFloat(marker.node.properties.Longitude),
}}
icon={{
url: `icon_${marker.node.properties.styleUrl.slice(-6)}.svg`,
origin: new window.google.maps.Point(0, 0),
anchor: new window.google.maps.Point(15, 15),
scaledSize: new window.google.maps.Size(30, 30),
}}
onClick={() => {
setSelected(marker)
}}
/>
))}
{selected ? (
<InfoWindow
position={{
lat: parseFloat(selected.node.properties.Latitude),
lng: parseFloat(selected.node.properties.Longitude),
}}
onCloseClick={() => {
setSelected(null)
}}
>
<div>
<p>{selected.node.properties.name}</p>
</div>
</InfoWindow>
) : null}
</GoogleMap>
</div>
</div>
)
}
export default Map
The page works just fine. However, when I try to move away from the page (which has the google map) to another page (in Gatsby), the page transition is not smooth. Gatsby reloads the new page entirely. The console gives me the following error:
Uncaught TypeError: a is undefined
ZU marker.js:48
<anonymous> marker.js:45
setTimeout handler*_.bn common.js:17
<anonymous> marker.js:45
H js:207
trigger js:204
remove js:207
removeListener js:203
unregisterEvent reactgooglemapsapi.esm.js:142
unregisterEvents reactgooglemapsapi.esm.js:150
componentWillUnmount reactgooglemapsapi.esm.js:2118
wrappedMethod react-hot-loader.development.js:707
React 27
unlisten index.js:103
unlisten index.js:101
promise callback*componentDidMount/refs.unlisten< index.js:99
navigate history.js:100
navigate history.js:99
navigate navigation.js:120
promise callback*navigate navigation.js:84
___navigate navigation.js:162
onClick index.js:256
onClick index.js:477
React 22
marker.js:48:38
There are several instances of this error on the console (I think as many as the number of markers I have on my map).
I am sure it is a simple fix to get rid of this error. Can someone help
UPDATE:Based on what #Ferran said, I used the following code, Still does not work:
I created a useState hook as you said.
const [frpMap, setFrpMap] = useState(null)
mapRef.current = map
...
const onLoad = React.useCallback(function callback(map) {
setFrpMap(map)
}, [])
const onUnmount = React.useCallback(function callback(map) {
setFrpMap(null)
mapRef.current = null
//console.log(map)
}, [])
I think I am not sure how to use the map variable set in the setFrpMap hook to render the GoogleMap.
So, when I do setFrpMap(null) on unmount nothing really happens.
You aren't unmounting your map so it breaks when the routing changes. This is not doing anything:
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
}, [])
I would suggest an approach using useState hook, in order to mount and unmount/dispose the map when needed:
import React, { useState } from "react"
import {
GoogleMap,
useLoadScript,
Marker,
InfoWindow,
} from "#react-google-maps/api"
import { useStaticQuery, graphql } from "gatsby"
import mapStyles from "./mapStyles"
const Indianapolis = {
lat: 39.768402,
lng: -86.158066,
}
const mapContainerStyle = {
height: "100%",
width: "100%",
}
const options = {
styles: mapStyles,
disableDefaultUI: true,
zoomControl: true,
}
const Map = () => {
const [map, setMap] = React.useState(null)
const data = useStaticQuery(graphql`
{
allKmlPoint {
edges {
node {
properties {
name
Longitude
Latitude
FRP_Project_Numbers
description
styleUrl
styleHash
}
id
}
}
}
}
`)
const [selected, setSelected] = useState(null)
const frpLocation = data.allKmlPoint.edges
//console.log(process.env.GATSBY_GOOGLE_MAPS_API_KEY)
const { isLoaded, loadError } = useLoadScript({
googleMapsApiKey: process.env.GATSBY_GOOGLE_MAPS_API_KEY,
})
const mapRef = React.useRef()
const onMapLoad = React.useCallback(map => {
mapRef.current = map
setMap(map)
console.log(map)
}, [])
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
setMap(null)
}, [])
if (loadError) return "Error"
if (!isLoaded) {
return "Loading..."
}
//console.log("comes here")
return (
<div className="map-container">
<span className="top-text">Project</span>
<span className="horizontal-line"></span>
<span className="bottom-text">
Locati<span className="full-color">o</span>ns
</span>
<span className="map-blurb">
FRP has a project portfolio across a wide geographic region. Click the
Map to Zoom and pan to the project locations for various market types.
</span>
<div className="map-wrapper">
<GoogleMap
zoom={8}
center={Indianapolis}
mapContainerStyle={mapContainerStyle}
options={options}
onUnmount={onUnmount}
onLoad={onMapLoad}
>
{frpLocation.map(marker => (
<Marker
key={marker.node.id}
position={{
lat: parseFloat(marker.node.properties.Latitude),
lng: parseFloat(marker.node.properties.Longitude),
}}
icon={{
url: `icon_${marker.node.properties.styleUrl.slice(-6)}.svg`,
origin: new window.google.maps.Point(0, 0),
anchor: new window.google.maps.Point(15, 15),
scaledSize: new window.google.maps.Size(30, 30),
}}
onClick={() => {
setSelected(marker)
}}
/>
))}
{selected ? (
<InfoWindow
position={{
lat: parseFloat(selected.node.properties.Latitude),
lng: parseFloat(selected.node.properties.Longitude),
}}
onCloseClick={() => {
setSelected(null)
}}
>
<div>
<p>{selected.node.properties.name}</p>
</div>
</InfoWindow>
) : null}
</GoogleMap>
</div>
</div>
)
}
export default Map
The idea is to initially set as null your map and set it in your onLoad function with:
const onMapLoad = React.useCallback(map => {
mapRef.current = map
setMap(map)
console.log(map)
}, [])
Note: adapt the snippet to your needs.
Since the callback is receiving the map as a parameter, you are able to use it along with useState hook.
On the other hand, use the opposite way when unmounting the map (onUnmount):
const onUnmount = React.useCallback(function callback(map) {
console.log(map)
setMap(null) // alternatively use map.data=null
}, [])
The same approach, your callback is receiving the map though you don't need it so, you can set the map object as null.
You can check the docs for further details.
const onUnmount = React.useCallback(function callback(map) {
map.data = null
}, [])
did the trick
Im using react-google-map, and I add poly line every marker in the map. But every time I delete a data the marker on the map wont disappear. Here is my code below.
Googlemap.js
/*global google*/
import React, { Component } from "react";
import { Polyline } from "react-google-maps";
import {
withGoogleMap,
withScriptjs,
Marker,
GoogleMap,
DirectionsRenderer,
} from "react-google-maps";
const Map = ({ places, zoom, center }) => {
return (
<GoogleMap defaultZoom={zoom} defaultCenter={center}>
<Markers places={places} />
<Polyline
path={places}
options={{
strokeColor: "#FFE900",
strokeOpacity: 2,
strokeWeight: 3,
}}
/>
</GoogleMap>
);
};
const Markers = ({ places }) => {
return places.map((place) => {
return (
<Marker key={place.id} position={{ lat: place.lat, lng: place.lng }} />
);
});
};
class MapWithMarker extends Component {
constructor(props) {
super(props);
this.state = { places: this.props.places }; //initialize initial state from props
}
render() {
return (
<div>
<Map
center={this.props.center}
zoom={this.props.zoom}
places={this.state.places}
containerElement={<div style={{ height: `100vh`, width: "100% " }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
export default withScriptjs(withGoogleMap(MapWithMarker));
maploader.js
import React, { Component, useState } from "react";
import "./config";
import Map from "./googlemap";
const App = () => {
const place = places;
return (
<div>
<Map
googleMapURL="https://maps.googleapis.com/maps/api/js?key=API_KEY"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100vh` }} />}
mapElement={<div style={{ height: `100%` }} />}
center={{ lat: 14.6091, lng: 121.0223 }}
zoom={12}
places={places}
/>
</div>
);
};
export default App;
Config.js
places = [
{
name: "Manila",
title: "Newcastle",
lat: 14.6091,
lng: 121.0223,
id: 1,
},
{
name: "Taguig",
title: "Sydney",
lat: 14.5135352,
lng: 121.0303829,
id: 2,
},
{
name: "Makati",
title: "Melbourne",
lat: 14.554592,
lng: 121.0156802,
id: 3,
},
];
And here is the sample code of my button in my map.js. This the button that will delete the last object in array. Everytime i delete a data it should be reflected in the map which is not working but working in console.
function clickSubmit(e) {
places.pop();
}
Any help would be appreciated. :)
In removing a marker in Google Maps JavaScript API, you should call the setMap() method and pass a null argument. However, as you are using react-google-maps library which setMap() method seems to be not included per their documentation.
You can implement your use case without using a library such as this sample code. Please use your API Key in Maps.js file for the code to work.
Please see code snippet with inline comments of the component that shows this:
import React, { Component } from "react";
import { render } from "react-dom";
import Map from "./Map";
import "./style.css";
import "./config";
class App extends Component {
render() {
return (
<div id="container">
<input type="button" value="Delete " id="myBtn" />
<Map
id="myMap"
options={{
center: { lat: 14.6091, lng: 121.0223 },
zoom: 12,
}}
onMapLoad={(map) => {
let placeArray = [];
let markersArray = [];
let polylinePath = [];
let polyline;
//putting the places from config.js in an array
{
places.map((markerJson) => placeArray.push(markerJson));
}
//Adding Marker for each coordinate in the array
for (let i = 0; i < placeArray.length; i++) {
addMarker({ lat: placeArray[i].lat, lng: placeArray[i].lng });
}
//function for creating polyline
createPolyline();
document.getElementById("myBtn").addEventListener("click", function () {
//Removing marker of the last coordinate in the map
markersArray[placeArray.length - 1].setMap(null);
//removing last object in the place and marker array
placeArray.pop();
markersArray.pop();
//Removing polyline in the map
polyline.setMap(null);
//function for creating new polyline using the path that does not include the deleted coordinate
createPolyline();
})
function createPolyline() {
//clearing polyline Path array
polylinePath = [];
//putting the coordinates in the polylinepath array to be used as the path of polyline
for (let i = 0; i < placeArray.length; i++) {
polylinePath.push({
lat: placeArray[i].lat,
lng: placeArray[i].lng,
});
}
//creating polyline
polyline = new google.maps.Polyline({
path: polylinePath,
geodesic: true,
strokeColor: "#FF0000",
strokeOpacity: 1.0,
strokeWeight: 2,
})
//showing polyline in the map
polyline.setMap(map);
}
// Adds a marker to the map and push to the array.
function addMarker(location) {
//creating new marker
const marker = new google.maps.Marker({
position: location,
map: map,
});
//putting the created marker in the markers array to be easily deleted in the map
markersArray.push(marker);
}
}}
/>
</div>
);
}
}
export default App;
Been working with react and still trying to grasp all the concepts. Could use help to understand how I can get this to work. I want to pass an array as props to another react component to map over. Can someone point out what I am doing wrong here? I can map through the array as a function but not inside render:
App.js
import React, { Component } from 'react';
import Leaf from './components/Leaf';
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
height: "100vh",
width: "100vw",
latitude: 40.7128,
longitude: -74.0060,
zoom: 10
},
latitude: 40.7128,
longitude: -74.0060,
zoom: 10,
stations: [],
selectedStation: null,
userLocation: {}
};
}
componentDidMount() {
fetch('https://gbfs.citibikenyc.com/gbfs/en/station_information.json')
.then(res => res.json())
.then(res=>
this.setState({stations: res}))
}
checkData=()=>{
console.log(this.state.stations)
this.state.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
return (
<div>
<button onClick={this.checkData}>click me</button>
<Leaf
viewport={this.state.viewport}
stations={this.state.stations}/>
</div>
);
}
}
export default App;
leaf.js
import React, {Component} from 'react';
import { Map, TileLayer, Marker, Popup } from 'react-leaflet';
import { Button } from 'react-bootstrap';
class leaf extends Component {
checkData=()=>{
this.props.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
const markers = this.props.stations.data.stations.map((station) =>
<Marker
position={[station.lat, station.lon]}
onClick={this.markerClick.bind(this,station)}>
<Popup>
</Popup>
</Marker>
);
const position = [this.props.viewport.latitude, this.props.viewport.longitude]
//const position = [40.7484, -73.9857]
return (
<div>
<button onClick={this.checkData}>check props</button>
<Map center={position} zoom={14}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{markers}
<Marker position={position}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</Map>
</div>
);
}
}
export default leaf;
In the past I've done something like :
const Search = ( props ) => {
return (
)
}
But I am looking to understand how to make this work using class leaf extends Component . Appreciate the help.
Data Structure for Reference
{
last_updated: 1572631066,
ttl: 10,
data: {
stations: [
{
station_id: "237",
external_id: "66db3c29-0aca-11e7-82f6-3863bb44ef7c",
name: "E 11 St & 2 Ave",
short_name: "5746.04",
lat: 40.73047309,
lon: -73.98672378,
region_id: 71,
rental_methods: [
"CREDITCARD",
"KEY"
],
capacity: 39,
rental_url: "http://app.citibikenyc.com/S6Lr/IBV092JufD?station_id=237",
electric_bike_surcharge_waiver: false,
eightd_has_key_dispenser: false,
eightd_station_services: [
{
id: "e73b6bfb-961f-432c-a61b-8e94c42a1fba",
service_type: "ATTENDED_SERVICE",
bikes_availability: "UNLIMITED",
docks_availability: "NONE",
name: "Valet Service",
description: "Citi Bike Station Valet attendant service available",
schedule_description: "",
link_for_more_info: "https://www.citibikenyc.com/valet"
}
],
has_kiosk: true
},
{
station_id: "281",
external_id: "66db5fae-0aca-11e7-82f6-3863bb44ef7c",
name: "Grand Army Plaza & Central Park S",
short_name: "6839.10",
lat: 40.7643971,
lon: -73.97371465,
region_id: 71,
rental_methods: [
"CREDITCARD",
"KEY"
],
capacity: 66,
rental_url: "http://app.citibikenyc.com/S6Lr/IBV092JufD?station_id=281",
electric_bike_surcharge_waiver: false,
eightd_has_key_dispenser: true,
eightd_station_services: [
{
id: "32461582-cd1e-4ecf-a5ea-563593fa7009",
service_type: "ATTENDED_SERVICE",
bikes_availability: "UNLIMITED",
docks_availability: "NONE",
name: "Valet Service",
description: "Citi Bike Valet Attendant Service Available",
schedule_description: "",
link_for_more_info: "https://www.citibikenyc.com/valet"
}
],
has_kiosk: true
}
]
}
}
Attempts
So here I changed the const markers to mirror the conosle.log from checkData:
const markers = this.props.stations.data.stations.map((station) =>
<Marker
position={[station.lat, station.lon]}
onClick={this.markerClick.bind(this,station)}>
<Popup>
</Popup>
</Marker>
);
I get the following error:
TypeError: Cannot read property 'stations' of undefined
When I remove the markers variable and click checkData, notice that it has not issue mapping over and console logging the object:
I'd doublecheck that you're accessing the correct data properties, as in your function you're calling this.props.stations.data.stations.map, but in your render you're calling this.props.stations.data.map, so one must be incorrect.
Also, class components should be PascalCase, i.e. capitalized.
Camilo was correct and I needed to make sure data was available for render. Below is the working code:
App.js
//import logo from './logo.svg';
//import './App.css';
import React, { Component } from 'react';
import Leaf from './components/Leaf';
class App extends Component {
constructor(props) {
super(props);
this.state = {
viewport: {
height: "100vh",
width: "100vw",
latitude: 40.7128,
longitude: -74.0060,
zoom: 10
},
latitude: 40.7128,
longitude: -74.0060,
zoom: 10,
stations: [],
showStations: false,
selectedStation: null,
userLocation: {}
};
}
componentDidMount() {
const request = async()=> {
await fetch('https://gbfs.citibikenyc.com/gbfs/en/station_information.json')
.then(res => res.json())
.then(res=>
this.setState({stations: res, showStations: true}))
}
request();
}
checkData=()=>{
console.log(this.state.stations)
this.state.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
return (
<div>
<button onClick={this.checkData}>click me</button>
<Leaf
viewport={this.state.viewport}
stations={this.state.stations}
showStations={this.state.showStations}/>
</div>
);
}
}
export default App;
Leaf.js
import React, {Component} from 'react';
import { Map, TileLayer, Marker, Popup } from 'react-leaflet';
//import './leaf.css'
//import InfoBox from './InfoBox';
import { Button } from 'react-bootstrap';
//import Search from './Search';
//import Match from './Match';
//import Modal from './Modal';
//import L from 'leaflet';
//import Routing from "./RoutingMachine";
//import { Row, Col, Grid, Container } from 'react-bootstrap';
//import ErrorBoundary from '../ErrorBoundary/ErrorBoundary'
class Leaf extends Component {
checkData=()=>{
this.props.stations.data.stations.map(e=>{
console.log(e)
})
}
render() {
let markers =null;
if(this.props.showStations) {
markers = (
<div>
{
this.props.stations.data.stations.map((station) =>
<Marker
position={[station.lat, station.lon]}>
</Marker>
)
}
</div>
)
}
const position = [this.props.viewport.latitude, this.props.viewport.longitude]
//const position = [40.7484, -73.9857]
return (
<div>
<button onClick={this.checkData}>check props</button>
<Map center={position} zoom={14}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{markers}
<Marker position={position}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</Map>
</div>
);
}
}
export default Leaf;
I have a React app which uses Google Maps API. I am using Foursquare API also, to fetch data about venues. Currently i am fetching about venues near Nashville, TN, keywords "yoga" and "coffee". I want to use the user's current location, and Nashville as a fallback in case they do not allow.
i've got this from MDN:
var options = {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
};
function success(pos) {
var crd = pos.coords;
console.log('Your current position is:');
console.log(`Latitude : ${crd.latitude}`);
console.log(`Longitude: ${crd.longitude}`);
console.log(`More or less ${crd.accuracy} meters.`);
}
function error(err) {
console.warn(`ERROR(${err.code}): ${err.message}`);
}
navigator.geolocation.getCurrentPosition(success, error, options);
and am looking for help implementing this in my code. How do i start with replacing the near: "Nashville, TN", below with the geolocation code? This is my app.js:
import React, { Component } from 'react';
import './App.css';
import SquareAPI from './API/';
import Map from './component/Map';
import SideBar from './component/Sidebar';
class App extends Component {
constructor(){
super();
this.state = {
venues: [],
markers: [],
center: [],
zoom: 14,
updateSuperState: obj => {
this.setState(obj);
}
};
}
closeAllMarkers = () => {
const markers = this.state.markers.map(marker => {
marker.isOpen = false;
return marker;
});
this.setState({ markers: Object.assign(this.state.markers, markers) });
};
handleMarkerClick = marker => {
this.closeAllMarkers();
marker.isOpen = true;
this.setState({ markers: Object.assign(this.state.markers, marker) });
const venue =this.state.venues.find(venue => venue.id === marker.id);
SquareAPI.getVenueDetails(marker.id).then(res => {
const newVenue = Object.assign(venue, res.response.venue);
this.setState({ venues: Object.assign(this.state.venues, newVenue) })
console.log(newVenue);
});
};
handleListItemClick = venue =>{
const marker = this.state.markers.find(marker => marker.id === venue.id)
this.handleMarkerClick(marker)
}
componentDidMount(){
SquareAPI.search({
near:"Nashville, TN",
query: "yoga",
limit: 10
}).then(results => {
const { venues } = results.response;
const { center } = results.response.geocode.feature.geometry;
const markers = venues.map(venue => {
return {
lat: venue.location.lat,
lng: venue.location.lng,
isOpen: false,
isVisible: true,
id: venue.id
};
})
this.setState({ venues, center, markers });
}).catch(error =>{
console.log("Error: " + error)
})
}
render() {
return (
<div className="App">
<SideBar {...this.state} handleListItemClick={this.handleListItemClick}/>
<Map {...this.state}
handleMarkerClick={this.handleMarkerClick}/>
</div>
);
}
}
export default App;
and my Map.js - i may also need to do it at line 10, defaultCenter=...
/* global google */
import React, { Component } from 'react';
import { withScriptjs, withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps';
const MyMapComponent = withScriptjs(
withGoogleMap(props => (
<GoogleMap
defaultZoom={8}
zoom={props.zoom}
defaultCenter={{ lat: -36.186, lng: -87.066 }}
// defaultCenter={
// }
center={{
lat: parseFloat(props.center.lat),
lng: parseFloat(props.center.lng)
}}
>
{props.markers &&
props.markers.filter(marker => marker.isVisible).map((marker, idx, arr) => {
const venueInfo = props.venues.find(venue => venue.id === marker.id);
return (
<Marker
key={idx}
position={{ lat: marker.lat, lng: marker.lng }}
onClick={() => props.handleMarkerClick(marker)}
animation={arr.length === 1
? google.maps.Animation.BOUNCE
: google.maps.Animation.DROP}
>
{marker.isOpen &&
venueInfo.bestPhoto && (
<InfoWindow>
<React.Fragment>
<img src={`${venueInfo.bestPhoto.prefix}300x300${venueInfo.bestPhoto.suffix}`} alt={venueInfo.name} />
<p>{venueInfo.name}</p>
</React.Fragment>
</InfoWindow>
)}
</Marker>
);
})}
</GoogleMap>
))
);
export default class Map extends Component {
render() {
return (
<MyMapComponent
{...this.props}
isMarkerShown
googleMapURL="https://maps.googleapis.com/maps/api/js?key=API_REMOVED"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `100%`, width: `65%` }} />}
mapElement={<div style={{ height: `100%`}} />}
/>
);
}
}
thanks!
Use the browsers geolocation.
There is an example in the docs.
In terms of React, you would set locations to state, (add a field), pass them to the Map component via prop.
Something like this
class Anything extends Component{
state = {
location : ''
} //no need for constructor no more, these are called class fields.
getPosition= ()=> {
console.log(navigator.gelocation)
//look at example in the docs and then
this.setState(response from navigator)
}
render(){
return (
<Map {...this.state}> // as you are spreading you are good here, access to
// geolocation via this.props.location in map
// component
)
}
}
https://developers.google.com/maps/documentation/javascript/geolocation
React/Redux newbie here. I have a form input that allows a user to enter a doctor issue. It returns a list of doctors from the server via Redux action, and displays the doctor list and a marker for each doctor's location on the map (react-google-maps).
When I click submit, the list of doctors for the correct issue displays, the map is there, but with no markers. I can get the markers on the map to display ONLY after submitting the form, THEN clicking on a doctor from the list to display their details.
Want: Enter a doctor issue and render both the list of doctors and markers on the map when the user clicks submit. Then, select a doctor to see their details page (that's another question, routing to dynamic a detail page).
I think, I need to use a life-cycle method but not sure how to implement it. Or, is there a better way to handle this with Redux?
Doctor component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DoctorSearchForm from '../../containers/doctors/DoctorSearchForm';
import DoctorList from './DoctorList';
import Map from '../maps/Map';
class Doctors extends Component {
constructor(props) {
super(props);
this.state = {
markers: [],
isMarkerShown: false
}
}
componentDidMount() {
this.getMarkers();
}
getMarkers = () => {
let practices = this.props.doctors.map(function(doctor, index) {
return {
title: doctor.profile.first_name + ' ' + doctor.profile.last_name,
location: {
lat: doctor.practices[0].visit_address.lat,
lng: doctor.practices[0].visit_address.lon
}
}
});
this.setState({ markers: practices, isMarkerShown: true });
}
render() {
const { doctors, match } = this.props;
return (
<div>
<DoctorSearchForm getMarkers={this.getMarkers} />
<div className="row">
<div className="col-md-4">
<DoctorList doctors={doctors} match={match} />
</div>
<div className="col-md-8">
<Map
isMarkerShown={this.state.isMarkerShown}
center={{ lat: 45.6318,lng: -122.6716 }}
zoom={12}
markers={this.state.markers}
/>
</div>
</div>
</div>
);
}
}
Doctors.propTypes = {
doctors: PropTypes.array.isRequired,
match: PropTypes.object.isRequired
}
export default Doctors;
DoctorList component:
import React from "react";
import { Route } from 'react-router-dom';
import DoctorItem from './DoctorItem';
import DoctorView from './DoctorView';
class DoctorList extends React.Component {
render() {
const { doctors, match } = this.props;
const linkList = doctors.map((doctor, index) => {
return (
<DoctorItem doctor={doctor} match={match} key={index} />
);
});
return (
<div>
<h3>DoctorList</h3>
<ul>{linkList}</ul>
<Route path={`${match.url}/:name`}
render={ (props) => <DoctorView data= {this.props.doctors} {...props} />}
/>
</div>
);
}
}
export default DoctorList;
DoctorItem component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Link, Route } from 'react-router-dom';
import DoctorView from './DoctorView';
const DoctorItem = (props) => {
const { doctor, match } = props;
return (
<li>
<Link to={{ pathname: `${match.url}/${doctor.profile.first_name}-${doctor.profile.last_name}` }}>
{doctor.profile.first_name} {doctor.profile.last_name}
</Link>
</li>
)
}
DoctorItem.propTypes = {
doctor: PropTypes.object.isRequired,
};
export default DoctorItem;
DoctorView component:
import React from 'react';
const DoctorView = ({ data, match }) => {
const doctor = data.find(p => `${p.profile.first_name}-${p.profile.last_name}` === match.params.name);
let doctorData;
if (doctor) {
const mappedSpecialties = Object.entries(doctor.specialties).map(([index, specialty]) => {
return <li key={index} id={index}>{specialty.description}</li>;
});
doctorData =
<div>
<h5><strong>{doctor.profile.first_name} {doctor.profile.last_name}</strong> - {doctor.profile.title}</h5>
<img src={doctor.profile.image_url} alt={"Dr." + doctor.profile.first_name + " " + doctor.profile.last_name} />
<ul>{mappedSpecialties}</ul>
<p>{doctor.profile.bio}</p>
</div>;
}
return (
<div>
{doctorData}
</div>
)
}
export default DoctorView;
Map component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withScriptjs, withGoogleMap, GoogleMap, Marker } from 'react-google-maps';
import { compose, withProps } from "recompose";
export default Map = compose(
withProps({
googleMapURL:
"https://maps.googleapis.com/maps/api/js?key={MY_KEY}&v=3.exp&libraries=geometry,drawing,places",
loadingElement: <div style={{ height: `100%` }} />,
containerElement: <div style={{ height: `400px` }} />,
mapElement: <div style={{ height: `100%` }} />
}),
withScriptjs,
withGoogleMap
)(props => (
<GoogleMap defaultZoom={9} defaultCenter={{ lat: 45.6318,lng: -122.6716 }}>
{props.markers.map((doctor, index) => {
const marker = {
position: { lat: doctor.location.lat, lng: doctor.location.lng },
title: doctor.title
}
return <Marker key={index} {...marker} />;
})}
</GoogleMap>
));
I've spent several days trying and searching for answers but no luck. Any help would be greatly appreciated!
Just like you calculate the markers when the component mounts, you need to recalculate your markers when you receive new props:
componentWillReceiveProps(nextProps) {
this.getMarkers(nextProps);
}
This will require you to change your getMarkers signature a bit so that it can accept an argument and use that instead of this.props in your map operation:
getMarkers = (props = this.props) => {
let practices = props.doctors.map(function(doctor, index) {
return {
title: doctor.profile.first_name + ' ' + doctor.profile.last_name,
location: {
lat: doctor.practices[0].visit_address.lat,
lng: doctor.practices[0].visit_address.lon
}
}
});
this.setState({ markers: practices, isMarkerShown: true });
}
Assuming you are calling getMarkers() in your DoctorSearchForm component, you can remove that since it will automatically update the markers when receiving new props -- or you could bypass state altogether and just calculate it on the fly in render based on the incoming props.