Pass arbitrary callbacks/params in React - javascript

I want to pass callbacks through to a map component, and this requires being able to give any args I want. I try with
MapView.js:
import React, { Component } from 'react';
import { browserHistory } from 'react-router';
export default class MapView extends Component {
constructor(props){
super(props);
this.renderMap = this.renderMap.bind(this);
this.addMarkerToMap = this.addMarkerToMap.bind(this);
this.clickMarker = this.clickMarker.bind(this);
}
componentDidMount() {
this.renderMap();
}
renderMap() {
let map_data = {
zoom: this.props.zoom || 10,
center: this.props.center || {lat: 30.3, lng: -97.75} // default to Austin
};
let this_map = new google.maps.Map(this.refs.map, map_data);
let markers_data = this.props.markers_data || [];
markers_data.map(function(data) {
this.addMarkerToMap(data, this_map);
}.bind(this));
}
addMarkerToMap(data, the_map) {
let marker = new google.maps.Marker({
position: data.coordinates,
map: the_map
});
if (data.callback) {
let params = data.params || [];
marker.addListener('click', data.callback.bind(this, ...params));
}
return marker
}
clickMarker(item_id, pathname) {
// http://stackoverflow.com/questions/31079081/programmatically-navigate-using-react-router
browserHistory.push(
{
pathname: pathname,
query: { item_id: item_id } // https://stackoverflow.com/questions/36350644/how-can-i-pass-parameters-on-redirection-using-react-router
}
);
}
render() {
return (
<div>
<h3>{this.props.title}</h3>
<p>{this.props.description}</p>
<div style={ {height: 500, width: 500 } } ref="map" />
</div>
)
}
}
MapShowCustomers.js:
import React, {Component} from "react";
import { browserHistory } from 'react-router';
import MapView from './MapView';
export default class MapShowCustomers extends Component {
constructor(props){
super(props);
this.clickMarker2 = this.clickMarker2.bind(this);
this.exampleCallback = this.exampleCallback.bind(this);
}
clickMarker2(pathname, item_id) {
alert(pathname);
// http://stackoverflow.com/questions/31079081/programmatically-navigate-using-react-router
browserHistory.push(
{
pathname: pathname,
query: { item_id: item_id } // https://stackoverflow.com/questions/36350644/how-can-i-pass-parameters-on-redirection-using-react-router
}
);
}
exampleCallback(text) {
console.log(text)
if (text) {
alert(text);
} else {
alert("it worked anyway");
}
}
render() {
let titleText = "This one to show customers for restaurant employees...more of a map example";
let description = "Notice the usage of the 'center' prop...by overwriting the default center, this one shows up in Illinois (center={ {lat: 40.3, lng: -88.75} } )"
let coordinates = {lat: 40.3, lng: -88.75};
let markersData = [
// {callback: this.clickMarker2, args: ['/profile-customer', 2], coordinates: {lat: 40.25, lng: -88.65}},
// {callback: this.clickMarker2, args: ['/profile-customer', 7], coordinates: {lat: 40.35, lng: -88.85}},
// {callback: this.clickMarker2, args: ['/profile-customer', 6], coordinates: {lat: 40.37, lng: -88.78}}
{callback: this.exampleCallback, params: ["blah"], pathname: '/profile-customer', item_id: 1, coordinates: {lat: 40.25, lng: -88.65}},
{callback: this.exampleCallback, params: ["blah"], pathname: '/profile-customer', item_id: 13, coordinates: {lat: 40.35, lng: -88.85}},
{callback: this.exampleCallback, pathname: '/profile-customer', item_id: 37, coordinates: {lat: 40.37, lng: -88.78}}
];
{/* look at the center prop for how to pass props...in GoogleMap component it defaults to Austin */}
return (
<div>
<MapView markers_data={markersData} center={coordinates} title={titleText} description={description}/>
</div>
);
}
}
I try pass {callback: this.exampleCallback, pathname: '/profile-customer', item_id: 37, coordinates: {lat: 40.37, lng: -88.78}} through without params and see exampleCallback alert "it worked anyway" by doing
if (data.callback) {
let params = data.params || [];
marker.addListener('click', data.callback.bind(this, ...params));
}
but now I see this:
and it wants to alert [object Object] instead of "it worked anyway". This code can be seen at this repo (to try it, use npm i; npm start).

In addMarkerToMap:
if (data.callback) {
let params = data.params || [undefined];
marker.addListener('click', data.callback.bind(this, ...params));
}
The ...params unpacks an array into separate parameters, for example running data.callback.bind(this, ...[1, 2, 3) unpacks to data.callback.bind(this, 1, 2, 3). This is a newer feature in ES6. By defaulting to [undefined], you end up unpacking to data.callback.bind(this, undefined), which gets ignored if your callback doesn't need arguments.

Related

Updating Markers to Google Maps in google-map-react

onGoogleApiLoaded not invoked on locations data change, Is there any way to achieve this?
I tried to use useEffect to load markers but then I have to save map and maps objects in useRef as well as markers
Is there any easier way to achieve this?
import { useEffect, useRef } from 'react';
import GoogleMapReact from 'google-map-react';
export type SimpleMapProps = {
locations: any[];
};
export default function SimpleMap({ locations }: SimpleMapProps) {
const defaultProps =
locations.length > 0
? {
center: {
lat: locations[0].Lat,
lng: locations[0].Lon
},
zoom: 11
}
: {
center: {
lat: 0,
lng: 0
},
zoom: 11
};
const handleApiLoadData = (map: any, maps: any) => {
for (let i = 0; i < locations.length; i++) {
const { Lat, Lon, hn, cur, rate } = locations[i];
const marker = new maps.Marker({
animation: maps.Animation.DROP,
position: { lat: Lat, lng: Lon },
map
});
marker.customInfowindow = new maps.InfoWindow({
content: `<div>${hn} </br> (${cur} ${rate})</div>`
});
marker.addListener('click', () => {
marker.customInfowindow.open(map, marker);
});
}
};
return (
// Important! Always set the container height explicitly
<div style={{ height: '50vh', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{
key: 'XXXXXXXXXXXXXXX'
}}
center={defaultProps.center}
zoom={defaultProps.zoom}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({ map, maps }) => handleApiLoadData(map, maps)}
/>
</div>
);
}

Passing Data Using Props to Display Polylines with Google Maps

Here is the reactjs code for displaying the movement of a vehicle on google map.
In the code, for the path array, latitude and longitude coordinates are assigned as hard-code values.
What I need is, how should pass latitude and longitude coordinates to "path" array from another component using props.
import React from "react";
import {
withGoogleMap,
withScriptjs,
GoogleMap,
Polyline,
Marker,
} from "react-google-maps";
class Map extends React.Component {
state = {
progress: [],
};
path = [
{ lat: 18.558908, lng: -68.389916 },
{ lat: 18.558853, lng: -68.389922 },
{ lat: 18.558375, lng: -68.389729 },
{ lat: 18.558032, lng: -68.389182 },
{ lat: 18.55805, lng: -68.388613 },
{ lat: 18.558256, lng: -68.388213 },
{ lat: 18.558744, lng: -68.387929 },
];
velocity = 5;
initialDate = new Date();
getDistance = () => {
// seconds between when the component loaded and now
const differentInTime = (new Date() - this.initialDate) / 1000; // pass to seconds
return differentInTime * this.velocity; // d = v*t -- thanks Newton!
};
componentDidMount = () => {
this.interval = window.setInterval(this.moveObject, 1000);
};
componentWillUnmount = () => {
window.clearInterval(this.interval);
};
moveObject = () => {
const distance = this.getDistance();
if (!distance) {
return;
}
let progress = this.path.filter(
(coordinates) => coordinates.distance < distance
);
const nextLine = this.path.find(
(coordinates) => coordinates.distance > distance
);
if (!nextLine) {
this.setState({ progress });
return; // it's the end!
}
const lastLine = progress[progress.length - 1];
const lastLineLatLng = new window.google.maps.LatLng(
lastLine.lat,
lastLine.lng
);
const nextLineLatLng = new window.google.maps.LatLng(
nextLine.lat,
nextLine.lng
);
// distance of this line
const totalDistance = nextLine.distance - lastLine.distance;
const percentage = (distance - lastLine.distance) / totalDistance;
const position = window.google.maps.geometry.spherical.interpolate(
lastLineLatLng,
nextLineLatLng,
percentage
);
progress = progress.concat(position);
this.setState({ progress });
};
componentWillMount = () => {
this.path = this.path.map((coordinates, i, array) => {
if (i === 0) {
return { ...coordinates, distance: 0 }; // it begins here!
}
const { lat: lat1, lng: lng1 } = coordinates;
const latLong1 = new window.google.maps.LatLng(lat1, lng1);
const { lat: lat2, lng: lng2 } = array[0];
const latLong2 = new window.google.maps.LatLng(lat2, lng2);
// in meters:
const distance = window.google.maps.geometry.spherical.computeDistanceBetween(
latLong1,
latLong2
);
return { ...coordinates, distance };
});
console.log(this.path);
};
render = () => {
return (
<GoogleMap
defaultZoom={16}
defaultCenter={{ lat: 18.559008, lng: -68.388881 }}
>
{this.state.progress && (
<>
<Polyline
path={this.state.progress}
options={{ strokeColor: "#FF0000 " }}
/>
<Marker
position={this.state.progress[this.state.progress.length - 1]}
/>
</>
)}
</GoogleMap>
);
};
}
const MapComponent = withScriptjs(withGoogleMap(Map));
export default () => (
<MapComponent
googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
loadingElement={<div style={{ height: `100%` }} />}
containerElement={<div style={{ height: `400px`, width: "940px" }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
);
Here is sample data from json object, which I get from other component.I need to pass this data using props to the above path array.
[]
0: {lat: 6.8667528, lng: 79.8769134}
1: {lat: 6.8667112, lng: 79.8769667}
2: {lat: 6.8666556, lng: 79.8769856}
3: {lat: 6.8666023, lng: 79.8769823}
4: {lat: 6.8665584, lng: 79.8770412}
5: {lat: 6.8665478, lng: 79.8771573}
6: {lat: 6.8665295, lng: 79.8772695}
7: {lat: 6.8664823, lng: 79.8774434}
8: {lat: 6.8664434, lng: 79.8777684}
9: {lat: 6.8664023, lng: 79.87823}
10: {lat: 6.8663373, lng: 79.8786712}
11: {lat: 6.86628, lng: 79.87902}
12: {lat: 6.8662312, lng: 79.879335}
13: {lat: 6.8662145, lng: 79.8795562}
14: {lat: 6.8662095, lng: 79.879695}
15: {lat: 6.8661978, lng: 79.8797523}
16: {lat: 6.8659873, lng: 79.8798639}
Can anyone help me to build this? Thanks for your help!
This is a sample code Note: use your own API key for the code to work) and a code snippet below on how I implement it. In the index.js, I put the path array in json file then imported the json file to be used as an element in my map. Then in my Map.js, I set the constuctor(props) and super(props). I put the react-google-maps <GoogleMap> in the render inside the GoogleMapExample variable. Then I use this variable in the return. In the componentWillMount function of your code, you need to use this.props.path.map to get the value of your path from the props.
Index.js
import React, { Component } from "react";
import { render } from "react-dom";
import { withScriptjs } from "react-google-maps";
import Map from "./Map";
import "./style.css";
import jsonPath from "./data.json";
const App = () => {
const MapLoader = withScriptjs(Map);
return (
<MapLoader
path={jsonPath}
googleMapURL="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=geometry,places"
loadingElement={<div style={{ height: `100%` }} />}
/>
);
};
render(<App />, document.getElementById("root"));
Map.js
import React, { Component } from "react";
import {
withGoogleMap,
GoogleMap,
Polyline,
Marker
} from "react-google-maps";
class Map extends Component {
constructor(props) {
super(props);
this.state = {
progress: []
};
}
velocity = 5;
initialDate = new Date();
getDistance = () => {
// seconds between when the component loaded and now
const differentInTime = (new Date() - this.initialDate) / 1000; // pass to seconds
return differentInTime * this.velocity; // d = v*t -- thanks Newton!
};
componentDidMount = () => {
this.interval = window.setInterval(this.moveObject, 1000);
console.log(this.props.path);
};
componentWillUnmount = () => {
window.clearInterval(this.interval);
};
moveObject = () => {
const distance = this.getDistance();
if (!distance) {
return;
}
let progress = this.path.filter(
coordinates => coordinates.distance < distance
);
const nextLine = this.path.find(
coordinates => coordinates.distance > distance
);
if (!nextLine) {
this.setState({ progress });
return; // it's the end!
}
const lastLine = progress[progress.length - 1];
const lastLineLatLng = new window.google.maps.LatLng(
lastLine.lat,
lastLine.lng
);
const nextLineLatLng = new window.google.maps.LatLng(
nextLine.lat,
nextLine.lng
);
// distance of this line
const totalDistance = nextLine.distance - lastLine.distance;
const percentage = (distance - lastLine.distance) / totalDistance;
const position = window.google.maps.geometry.spherical.interpolate(
lastLineLatLng,
nextLineLatLng,
percentage
);
progress = progress.concat(position);
this.setState({ progress });
};
componentWillMount = () => {
this.path = this.props.path.map((coordinates, i, array) => {
if (i === 0) {
return { ...coordinates, distance: 0 }; // it begins here!
}
const { lat: lat1, lng: lng1 } = coordinates;
const latLong1 = new window.google.maps.LatLng(lat1, lng1);
const { lat: lat2, lng: lng2 } = array[0];
const latLong2 = new window.google.maps.LatLng(lat2, lng2);
// in meters:
const distance = window.google.maps.geometry.spherical.computeDistanceBetween(
latLong1,
latLong2
);
return { ...coordinates, distance };
});
console.log(this.path);
};
render() {
const GoogleMapExample = withGoogleMap(props => (
<GoogleMap
defaultZoom={16}
defaultCenter={{ lat: 6.8667528, lng: 79.8769134 }}
>
{this.state.progress && (
<>
<Polyline
path={this.state.progress}
options={{ strokeColor: "#FF0000 " }}
/>
<Marker
position={this.state.progress[this.state.progress.length - 1]}
/>
</>
)}
</GoogleMap>
));
return (
<div>
<GoogleMapExample
containerElement={<div style={{ height: `500px`, width: "500px" }} />}
mapElement={<div style={{ height: `100%` }} />}
/>
</div>
);
}
}
export default Map;

Why can I not delete Google Markers?

I have methods of rendering Markers as shown below. I'm passing markers array from props and rendering it each time componentDidUpdate triggered. The problem is my old markers is not removing from maps. For example if I had 1 coordinates inside my parent component and update it with new ones, the new one appears and the old one stands still.
`
import React from 'react';
const google = window.google;
export class GMap extends React.Component {
mapRef = React.createRef();
directionsService
directionsRenderer
map;
componentDidMount() {
this.initMap();
const { onClick } = this.props;
onClick && this.onMapClick();
}
componentDidUpdate() {
const { markers } = this.props;
this.calcRoute();
if (markers && markers.length > 0) {
this.clear(markers);
this.renderMarkers(markers);
}
}
initMap() {
this.directionsService = new google.maps.DirectionsService();
this.directionsRenderer = new google.maps.DirectionsRenderer();
const mapOptions = {
zoom: 13,
center: { lat: 40.386119, lng: 49.860925 }
}
const map = new google.maps.Map(document.getElementById('map'), mapOptions);
this.map = map;
this.directionsRenderer.setMap(map);
}
onMapClick() {
this.map.addListener('click', (e) => {
this.props.onClick(e);
})
}
renderMarkers(markers) {
markers.forEach(position => {
const marker = new google.maps.Marker({ position });
marker.setMap(this.map);
})
}
calcRoute() {
const { directions } = this.props;
if (directions) {
const [{ lat: fLat, lng: fLng }, { lat: tLat, lng: tLng }] = directions;
if (fLat && fLng && tLat && tLng) {
var request = {
origin: { lat: fLat, lng: fLng },
destination: { lat: tLat, lng: tLng },
travelMode: 'DRIVING'
};
this.directionsService.route(request, (result, status) => {
if (status === 'OK') {
this.directionsRenderer.setDirections(result);
}
});
}
}
}
render() {
return (
<div id='map' ref={this.mapRef} />
)
}
}
`
How are you removing the markers? All I can see in the posted code is this.clear(markers) with no reference to clear. Try doing something like this:
clear(markers) {
for(let i = 0; i < markers.length; i++) {
markers[i].setMap(null);
}
}
Hope this helps!

Adding multiple markers to react google maps while using geocoder

What I'm trying to do is send in an array of addresses to a component, use google's geocoder to convert those addresses into lat / long coordinates, and then plat those places on a google map with markers using the google maps api react wrapper. I followed this tutorial pretty closely (https://dev.to/jessicabetts/how-to-use-google-maps-api-and-react-js-26c2) with the biggest difference being that I worked in geocoder. Because geocoder is asynchronous, I can't get the map to re-render with the newly converted coordinates after the promise is resolved. Below is the code I have right now:
import React, { Component } from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
const mapStyles = {
width: '100%',
height: '300px'
};
let geocoder;
let addressData = [{location: "146 Pierrepont St, Brooklyn, NY, USA"}, {location: "153 Remsen St, Brooklyn, NY, USA"}];
export class MapContainer extends Component {
constructor (props) {
super(props);
this.onMarkerClick = this.onMarkerClick.bind(this);
this.displayMarkers = this.displayMarkers.bind(this);
this.state = {
lat: 40.6946768,
lng: -73.99161700000002,
showingInfoWindow: false,
activeMarker: {},
selectedPlace: {},
places: [],
stores: [{latitude: 47.49855629475769, longitude: -122.14184416996333},
{latitude: 47.359423, longitude: -122.021071},
{latitude: 47.2052192687988, longitude: -121.988426208496},
{latitude: 47.6307081, longitude: -122.1434325},
{latitude: 47.3084488, longitude: -122.2140121},
{latitude: 47.5524695, longitude: -122.0425407}]
}
}
componentDidMount () {
this.plotPoints()
}
plotPoints () {
let locations = this.getPoints(geocoder)
let places = new Array()
Promise.all(locations)
.then(function(returnVals) {
returnVals.forEach(function(latLng) {
let place = {latitude: latLng[0], longitude: latLng[1]}
places.push(place)
})
})
this.setState (() => {
return {
places: places
}
});
}
getPoints(geocoder) {
let locationData = [];
for (let i = 0; i < addressData.length; i++) {
locationData.push(this.findLatLang(addressData[i].location, geocoder))
}
return locationData // array of promises
}
findLatLang(address, geocoder) {
return new Promise(function(resolve, reject) {
geocoder.geocode({
'address': address
}, function(results, status) {
if (status === 'OK') {
console.log(results);
resolve([results[0].geometry.location.lat(), results[0].geometry.location.lng()]);
} else {
reject(new Error('Couldnt\'t find the location ' + address));
}
})
})
}
displayMarkers (stores) {
return stores.map((place, index) => {
return <Marker key={index} id={index} position={{
lat: place.latitude,
lng: place.longitude
}}
onClick={() => console.log("You clicked me!")} />
})
}
onMarkerClick (props, marker, e) {
this.setState({
selectedPlace: props,
activeMarker: marker,
showingInfoWindow: true
});
};
render() {
geocoder = new this.props.google.maps.Geocoder();
return (
<div className="container place-map">
<div className="row">
<div className="col-md-12">
<Map
google={this.props.google}
zoom={14}
style={mapStyles}
initialCenter={{
lat: this.state.lat,
lng: this.state.lng
}}
>
{this.displayMarkers(this.state.stores)}
{this.displayMarkers(this.state.places)}
<InfoWindow
marker={this.state.activeMarker}
visible={this.state.showingInfoWindow}
>
<div>Your Location Here!</div>
</InfoWindow>
</Map>
</div>
</div>
</div>
);
}
}
export default GoogleApiWrapper({
apiKey: 'AIzaSyCOJDrZ_DXmHzbzSXv74mULU3aMu3rNrQc'
})(MapContainer);
The array of "stores" renders markers on the map since there are coordinates available at the initial render of the map - but the coordinates that get pushed onto the "places" array never render. If I put a log statement of the "places" into render() I can see that I am getting back valid coordinates from geocoder.
Help! Been banging my head on this for forever (as you can tell by the current sloppy state of the code).
You need to move the setState for places into the Promise.all callback.
You are calling it when the array is still empty and before the promises have resolved
Promise.all(locations)
.then((returnVals) =>{
returnVals.forEach((latLng) => {
let place = {
latitude: latLng[0],
longitude: latLng[1]
}
places.push(place)
})
// places now populated
this.setState(() => {
return {
places: places
}
});
});

google-maps-react user location not working

I am trying to display the user location on the map using google-maps-react. I followed the fullstack tutorial, but I just can't seem to display the user location. I will display my Map.js Component below. Please help me point out what I am doing wrong. Thank you.
import React, { Component } from 'react'
import ReactDOM from 'react-dom';
class Map extends Component {
constructor(props) {
super(props);
const {lat, lng} = this.props.initialCenter;
this.state = {
currentLocation: {
lat: lat,
lng: lng
}
}
}
componentDidUpdate(prevProps, prevState) {
if (prevProps.google !== this.props.google) {
this.loadMap();
}
if (prevState.currentLocation !== this.state.currentLocation) {
this.recenterMap();
}
}
recenterMap() {
const map = this.map;
const curr = this.state.currentLocation;
const google = this.props.google;
const maps = google.maps;
if (map) {
let center = new maps.LatLng(curr.lat, curr.lng)
map.panTo(center)
}
}
componentDidMount() {
if (this.props.centerAroundCurrentLocation) {
if (navigator && navigator.geolocation) {
navigator.geolocation.getCurrentPosition((pos) => {
const coords = pos.coords;
this.setState({
currentLocation: {
lat: coords.latitude,
lng: coords.longitude
}
})
})
}
}
this.loadMap();
}
loadMap() {
if (this.props && this.props.google) {
// google is available
const {google} = this.props;
const maps = google.maps;
const mapRef = this.refs.map;
const node = ReactDOM.findDOMNode(mapRef);
let {initialCenter, zoom} = this.props;
const {lat, lng} = initialCenter;
const center = new maps.LatLng(lat, lng);
const mapConfig = Object.assign({}, {
center: center,
zoom: zoom
})
this.map = new maps.Map(node, mapConfig);
}
}
render() {
const style = {
width: '100vw',
height: '100vh'
}
return (
<div ref='map' style={style}>
Loading map...
</div>
)
}
}
Map.propTypes = {
google: React.PropTypes.object,
zoom: React.PropTypes.number,
initialCenter: React.PropTypes.object,
centerAroundCurrentLocation: React.PropTypes.bool
}
Map.defaultProps = {
zoom: 13,
// San Francisco, by default
initialCenter: {
lat: 37.774929,
lng: -122.419416
},
centerAroundCurrentLocation: false
}
export default Map

Categories

Resources