I am trying to change a location in Google Maps API component upon a click on a title in my page file. Can't figure out how to access the component through a different file.
Tried different syntax, "MapContainer.func", MapContainer.props.func".
ContactPage.
import GoogleMaps from '../../../components/GoogleMaps';
import MapContainer from '../../../components/GoogleMaps';
const breadcumbMenu = [
{ name: 'Home', route: '/' },
{ name: 'Contact', },
]
function bkLocation() {
console.log('The bk link was clicked.');
}
function mhLocation() {
console.log('The mh link was clicked.');
//MapContainer.changeLoc(40.712637, -74.008116); <-- need to use changeLoc right here
}
function njLocation() {
console.log('The nj link was clicked.');
}
const ContactPage = () => {
return (.......
GoogleMaps.
import React, { Component } from 'react'
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
import './style.scss'
const style = {
width: '70%',
height: '100%',
left: '14%'
}
export class MapContainer extends Component {
static defaultProps = {
center: {lat:40.585384,
lng:-73.951200 },
zoom: 11
};
state = {
center: {
lat: 40.7831,
lng: -73.9712
}
}
_changeLoc = (lt, ln) => {
this.setState({
center: {
lat: lt,
lng: ln
}
});};
render() {
return (
<Map
google={this.props.google}
style={style}
zoom={14}
initialCenter={{
lat:40.585384,
lng:-73.951200
}}
center={this.props.center}
>
<Marker onClick={this.onMarkerClick}
name={'Current location'} />
<InfoWindow onClose={this.onInfoWindowClose}>
</InfoWindow>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: ("AIzaSyBfumfF_4j5uvjaITGn_VO_pb3O59uu-oE")
})(MapContainer)
The error I am getting is that "changeLoc is not a function / undefined". I need the map api location change when the user clicks on a title (there are 3 titles with the console logs). Somehow I need to connect the changeLoc function and make it callable from the ContactPage file.
Thank you in advance!
you can achieve this the following way
Make _changeLoc static, then you will be able to call MapContainer._changeLoc as you have defined
static _changeLoc = (lt, ln) => {
this.setState({
center: {
lat: lt,
lng: ln
}
});};
option 2 would be to use redux and add the refernece of your function into the redux store
Related
I have seen solutions where they use the Map component, but I read that this has been updated to the MapContainer which does not have an onClick Method. Right now, my code (below) allows me to add a marker when I click anywhere on the map. As my title states, how would I store the new marker in some kind of usable variable. My end goal is to store new markers in MongoDB. Thanks in advance.
import React, { Component } from 'react';
import {
MapContainer,
TileLayer,
MapConsumer,
} from "react-leaflet";
import Leaflet from "leaflet";
import { connect } from 'react-redux';
import { Icon } from "../Leaflet/Configurations";
import NavBar from '../components/NavBar';
import Footer from '../components/Footer';
import { registerHouse } from '../actions/houseActions';
import { clearErrors } from "../actions/errorActions";
import "leaflet/dist/leaflet.css";
class MyMap extends Component{
constructor(props){
super(props);
this.state = {
markers: [[40.7, -74]],
data: []
};
this.addMarker = this.addMarker.bind(this);
}
render(){
return(
<div>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossOrigin=""/>
<script src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossOrigin=""></script>
<NavBar/>
{/* <MapContainer className="Map" center={{ lat: 40.7 , lng: -74 }} zoom={15} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<MapConsumer>
{(map) => {
console.log("map center:", map.getCenter());
map.on("click", function (e) {
const { lat, lng } = e.latlng;
Leaflet.marker([lat, lng], { Icon }).addTo(map);
let tmp = this.state.data;
tmp.append([lat,lng]);
this.setState({data:tmp});
});
return null;
}}
</MapConsumer>
</MapContainer> */}
<Footer/>
</div>
)
}
}
const mapStateToProps = state => ({
isAuthenticated: state.auth.isAuthenticated,
error: state.error
});
export default connect(mapStateToProps, { registerHouse, clearErrors })(MyMap);
I am not sure this part of your code works:
let tmp = this.state.data;
tmp.append([lat,lng]);
this.setState({data:tmp});
why? Because this.state is undefined inside map.on('click) block scope. It loses its reference there.
So what you could do is create a custom child component of MapContainer and take advantage of a concept in react called raising an event from the child to the parent component. The parent will send an event to the child and the latter calls that function when something happens.
function MyComponent({ saveMarkers }) {
const map = useMapEvents({
click: (e) => {
const { lat, lng } = e.latlng;
L.marker([lat, lng], { icon }).addTo(map);
saveMarkers([lat, lng]);
}
});
return null;
}
and in your MyMap comp define the event
saveMarkers = (newMarkerCoords) => {
const data = [...this.state.data, newMarkerCoords];
this.setState((prevState) => ({ ...prevState, data }));
};
which will be passed as a prop to the custom comp:
<MapContainer
...
<MyComponent saveMarkers={this.saveMarkers} />
</MapContainer>
Demo
I am using Leaflet in a React Project, and i want to import a plugin which extends Leaflet Polyline which is Leaflet.Polyline.SnakeAnim
I am not using react-leaflet but directly the Javascript library, here is my code :
import React from 'react';
import L from 'leaflet';
import 'leaflet.polyline.snakeanim';
class LeafletMap extends React.Component {
componentDidMount() {
this.map = L.map('leafletMap', {
center:[0,0],
zoom: 2
});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(this.map);
let polylinePoints = [
[57.74, 11.94],
[0,0],
[22,22]
];
L.polyline(polylinePoints, {
color: 'blue',
weight: 8
}).addTo(this.map).snakeIn();
}
}
In my IDE, I got a warning on snakeIn() saying the function is unresolved. And the plugin don't work at all.
How can I properly import a Leaflet Plugin into React ?
If you want to trigger the animation when the map loads then place the snake logic inside componentDidMount otherwise save the mapInstance when the map loads and then , using a button, trigger the snake animation inside componentDidUpdate.
class LeafletMap extends React.Component {
state = {
mapInstance: null,
startAnimation: false
};
startSnake = () => this.setState({ startAnimation: true });
componentDidMount() {
const map = L.map("map").setView([51.505, -0.09], 13);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'© OpenStreetMap contributors'
}).addTo(map);
this.setState({ mapInstance: map });
}
componentDidUpdate(prevProps, prevState) {
if (prevState.startAnimation !== this.state.startAnimation) {
const trd = [63.5, 11];
const mad = [40.5, -3.5];
const lnd = [51.5, -0.5];
const ams = [52.3, 4.75];
const vlc = [39.5, -0.5];
const route = L.featureGroup([
L.marker(trd, { icon }),
L.polyline([trd, ams]),
L.marker(ams, { icon }),
L.polyline([ams, lnd]),
L.marker(lnd, { icon }),
L.polyline([lnd, mad]),
L.marker(mad, { icon }),
L.polyline([mad, vlc]),
L.marker(vlc, { icon })
]);
this.state.mapInstance.fitBounds(route.getBounds());
this.state.mapInstance.addLayer(route);
route.snakeIn();
route.on("snakestart snake snakeend", ev => {
console.log(ev.type);
});
}
}
render() {
return (
<>
<div id="map" style={{ height: "90vh" }} />
<button onClick={this.startSnake}>Snake it!</button>
</>
);
}
}
Demo
I have a list of events, and each event has a click function that displays the details of that event. Each detail holds a google map which takes the latlng coordinates found inside the clicked event object.
My problem is this: when I click on an event for the first time, the detail comes out correctly, with the correct title, location name and map. But then, the second click renders the new detail of the new event with the correct title, location name BUT the map doesn't change.
Why isn't the map being reloaded with the new coordinates?
here is the code for the 'event list' container:
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { selectEvent } from '../actions/index.js';
class EventsList extends React.Component {
render() {
var createEventsList = this.props.events.map((event, i) => {
return <li key={i} onClick={() => this.props.selectEvent(event)}>{event.title}</li>
});
return(
<ul>{createEventsList}</ul>
);
}
}
function mapStateToProps(state) {
return {
events: state.events
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ selectEvent: selectEvent }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(EventsList);
the code for the 'event detail' container:
import React from 'react';
import { connect } from 'react-redux';
import GoogleMap from '../components/google_map.js';
class EventDetail extends React.Component{
render() {
if(!this.props.event) {
return <div>Pick an Event</div>
}
const latcoord = parseFloat(this.props.event.locationLat);
const longcoord = parseFloat(this.props.event.locationLong);
return(
<div>
<div>details for:</div>
<div>{ this.props.event.title }</div>
<div>{ this.props.event.location }</div>
<GoogleMap long={longcoord} lat={latcoord}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
event: state.eventDetail
};
}
export default connect(mapStateToProps)(EventDetail);
and here is the code for the Google Map component:
import React from 'react';
export default class GoogleMap extends React.Component{
componentDidMount() {
new google.maps.Map(this.refs.map, {
zoom: 15,
center: {
lat: this.props.lat,
lng: this.props.long
}
});
}
render() {
return(
<div className="google-map" ref="map"></div>
);
}
}
Since componentDidMount gets called only once when the component is mounted, when you click on an event for the second time, the map is not getting updated. To correct this you will need to implement componentDidUpdate() function. Also you should save the map in a state or in this context. You wouldn't want to create a new map always.
componentDidMount() {
this.map = new google.maps.Map(this.refs.map, {
zoom: 15,
center: {
lat: this.props.lat,
lng: this.props.long
}
});
}
componentDidUpdate() {
this.map.panTo(new google.maps.LatLng(this.props.lat, this.props.lng));
}
Hope it helps. :)
Im using google-maps-react, and it works pretty good, but i just cant understand how can i work with Google Maps API's methods inside my component. Now i need to getBounds of rendered map, but cant find any way to do this. Here is the code, any help would be appreciated.
import React from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
const GOOGLE_MAPS_JS_API_KEY='AIzaSyB6whuBhj_notrealkey';
class GoogleMap extends React.Component {
constructor() {
this.state = {
zoom: 13
}
this.onMapClicked = this.onMapClicked.bind(this);
this.test = this.test.bind(this);
}
onMapClicked (props) {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
})
}
}
test(google) {
// Here i tried a lot of ways to get coords somehow
console.log(google.maps.Map.getBounds())
}
render() {
const {google} = this.props;
if (!this.props.loaded) {
return <div>Loading...</div>
}
return (
<Map className='google-map'
google={google}
onClick={this.onMapClicked}
zoom={this.state.zoom}
onReady={() => this.test(google)}
>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: (GOOGLE_MAPS_JS_API_KEY)
})(GoogleMap);
Google Maps Api v 3.30.4
You could try and adapt your requirements to the following example here.
From what i can see a reference is returned using the onReady prop.
For example :
import React from 'react';
import {Map, InfoWindow, Marker, GoogleApiWrapper} from 'google-maps-react';
const GOOGLE_MAPS_JS_API_KEY='AIzaSyB6whuBhj_notrealkey';
class GoogleMap extends React.Component {
constructor() {
this.state = {
zoom: 13
}
this.onMapClicked = this.onMapClicked.bind(this);
this.handleMapMount = this.handleMapMount.bind(this);
}
onMapClicked (props) {
if (this.state.showingInfoWindow) {
this.setState({
showingInfoWindow: false,
activeMarker: null
})
}
}
handleMapMount(mapProps, map) {
this.map = map;
//log map bounds
console.log(this.map.getBounds());
}
render() {
const {google} = this.props;
if (!this.props.loaded) {
return <div>Loading...</div>
}
return (
<Map className='google-map'
google={google}
onClick={this.onMapClicked}
zoom={this.state.zoom}
onReady={this.handleMapMount}
>
</Map>
);
}
}
export default GoogleApiWrapper({
apiKey: (GOOGLE_MAPS_JS_API_KEY)
})(GoogleMap);
I have a React component which holds an instance of google-maps inside of it. I'm using Meteor for the infrastructure, and the issue is that the React component is getting rendered synchronously, but to grab a server side variable, I have to resort to async. How would one handle this situation appropriately? Here's my snippet below:
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { Gmaps, Marker, InfoWindow, Circle } from 'react-gmaps';
// Goals for this:
// - get the location of the user dynamically
// - see other users as markers
const coords = {
lat: 51.5258541,
lng: -0.08040660000006028,
};
export default class App extends React.Component {
componentWillMount() {
const params = {
v: '3.exp',
key: null,
};
Meteor.call('googleMapsApiKey', (err, res) => {
if (!err) {
params.key = res;
console.log('params', params);
}
});
}
onMapCreated(map) {
map.setOptions({
disableDefaultUI: true,
});
}
onDragEnd(e) {
console.log('onDragEnd', e);
}
onCloseClick() {
console.log('onCloseClick');
}
onClick(e) {
console.log('onClick', e);
}
onDragStart(e) {
console.log('onDragStart', e);
}
render() {
return (
<Gmaps
width={'800px'}
height={'600px'}
lat={coords.lat}
lng={coords.lng}
zoom={12}
loadingMessage={'Be happy'}
params={params}
onMapCreated={this.onMapCreated}
>
<Marker
lat={coords.lat}
lng={coords.lng}
draggable
onDragStart={this.onDragStart}
/>
<InfoWindow
lat={coords.lat}
lng={coords.lng}
content={'Hello, React :)'}
onCloseClick={this.onCloseClick}
/>
<Circle
lat={coords.lat}
lng={coords.lng}
radius={500}
onClick={this.onClick}
/>
</Gmaps>
);
}
}
Here's the telltale console errors that show the sync/async issue:
The render method doesn't seem to wait for Async response to occur. What's the best practise to address this?
When you design a React component, you should think about the multiple states it could have. For instance, your Google maps component has to load before displaying a map, so it has two states: loading and loaded. It depends on your Meteor call's resulting key:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
v: '3.exp',
key: null
};
Meteor.call('googleMapsApiKey', (err, res) => {
if (!err) {
this.setState({ key: res })
}
});
}
...
render() {
if (this.state.key) {
return (
<Gmaps
width={'800px'}
height={'600px'}
lat={coords.lat}
lng={coords.lng}
zoom={12}
loadingMessage={'Be happy'}
params={this.state}
onMapCreated={this.onMapCreated}
>
<Marker
lat={coords.lat}
lng={coords.lng}
draggable
onDragStart={this.onDragStart}
/>
<InfoWindow
lat={coords.lat}
lng={coords.lng}
content={'Hello, React :)'}
onCloseClick={this.onCloseClick}
/>
<Circle
lat={coords.lat}
lng={coords.lng}
radius={500}
onClick={this.onClick}
/>
</Gmaps>
);
} else {
return <div>Loading...</div>;
}
}
}
Basically, I just replaced your const params by a state plus added some loading management. Note that in ES6 Components, you must declare this.state in the constructor.
When you use a React.Component, the major difference is that the componentwillmount logic should be put in the class constructor like so:
From the babel blog:
All of the lifecycle methods but one can be defined as you would expect when using the new class syntax. The class' constructor now assumes the role previously filled by componentWillMount:
// The ES5 way
var EmbedModal = React.createClass({
componentWillMount: function() { … },
});
// The ES6+ way
class EmbedModal extends React.Component {
constructor(props) {
super(props);
// Operations usually carried out in componentWillMount go here
}
}
Link to blog post
The async work should be put in componentDidMount. And there is a library can help you to create a component with async props.
This is the example.
import { Meteor } from 'meteor/meteor';
import React from 'react';
import PropTypes from 'prop-types';
import { Gmaps, Marker, InfoWindow, Circle } from 'react-gmaps';
import { AsyncComponent } from 'react-async-wrapper';
// Goals for this:
// - get the location of the user dynamically
// - see other users as markers
const coords = {
lat: 51.5258541,
lng: -0.08040660000006028,
};
class App extends React.Component {
onMapCreated(map) {
map.setOptions({
disableDefaultUI: true,
});
}
onDragEnd(e) {
console.log('onDragEnd', e);
}
onCloseClick() {
console.log('onCloseClick');
}
onClick(e) {
console.log('onClick', e);
}
onDragStart(e) {
console.log('onDragStart', e);
}
render() {
return (
<Gmaps
width={'800px'}
height={'600px'}
lat={coords.lat}
lng={coords.lng}
zoom={12}
loadingMessage={'Be happy'}
params={this.props.params}
onMapCreated={this.onMapCreated}
>
<Marker
lat={coords.lat}
lng={coords.lng}
draggable
onDragStart={this.onDragStart}
/>
<InfoWindow
lat={coords.lat}
lng={coords.lng}
content={'Hello, React :)'}
onCloseClick={this.onCloseClick}
/>
<Circle
lat={coords.lat}
lng={coords.lng}
radius={500}
onClick={this.onClick}
/>
</Gmaps>
);
}
}
App.propTypes = {
params: PropTypes.object
}
App.defaultProps = {
params: {
v: '3.exp',
key: null
}
}
const AsyncApp = () => (
<AsyncComponent asyncProps={{
params: () => new Promise((resolve, reject) => {
Meteor.call('googleMapsApiKey', (err, res) => {
if (!err) {
resolve({
v: '3.exp',
key: res,
})
} else {
reject(err)
}
});
})
}}
>
<App />
</AsyncComponent>
)