I am simply trying to put a google map on a page to start in a react project, and am having trouble. The div with the id="map" shows, but not the map inside it.
I'm following the google map API docs for JS, but obviously I must be doing something wrong. I would like to avoid using react-google-maps since I am used to using straight google maps api in another framework.
Here is my component google_map.js:
import React, { Component } from 'react';
class GoogleMap extends Component {
componentDidMount() {
new google.maps.Map(this.refs.map, {
zoom: 12,
center: {
lat: 37.7952,
lng: -122.4029
}
});
}
render() {
return <div ref="map" />;
}
}
export default GoogleMap;
import React, { Component } from 'react';
import GoogleMap from './google_map';
Here is where I am trying to put the map:
class Feature extends Component {
render() {
return (
<div id="map">
<GoogleMap />
</div>
);
}
}
export default Feature;
Style.css:
#map {
height: 300px;
}
html, body {
height: 100%;
margin: 0;
padding: 0;
}
I presume you have to add
shouldComponentUpdate {
return false
}
to your GoogleMap component.
Explanation: probably, React.js synchronizes its virtual DOM with real (which is going to be modified by google maps). Further reading: https://reactjs.org/docs/react-component.html#shouldcomponentupdate
Related
With React.js 16 and OpenLayers 6.5 I created a component which displays a map with an overlay:
import React from "react";
import OSM from "ol/source/OSM";
import TileLayer from "ol/layer/Tile";
import Map from "ol/Map";
import View from "ol/View";
import Overlay from "ol/Overlay";
class Map extends React.Component {
constructor(props) {
super(props);
this.mapRef = React.createRef();
this.overlayRef = React.createRef();
}
componentDidMount() {
this.map = new Map({
layers: [
new TileLayer({
source: new OSM(),
}),
],
target: this.mapRef.current,
view: new View({
center: [800000, 5000000],
zoom: 5,
}),
});
const overlay = new Overlay({
position: [800000, 5000000],
element: this.overlayRef.current,
});
this.map.addOverlay(overlay);
}
render() {
return (
<>
<div ref={this.mapRef} id="map"></div>
<div ref={this.overlayRef}>Overlay</div>
</>
);
}
}
export default Map;
This code works fine until the component gets unmounted. Then I receive the error
Uncaught DOMException: Node.removeChild: The node to be removed is not a child of this node
and the app crashes. I guess it happens because OpenLayers is modifying the DOM structure and thus React gets confused.
Does anybody knows how to add an overlay which does not modify the DOM structure? Or any other solution to circumvent the problem?
The problem is that OL Overlay class takes the passed in element this.overlayRef.current and appends it as child to its internal element changing the DOM structure. You can anticipate this and preemptively place your custom overlay element inside Overlay's internal element using React portal:
ReactDOM.createPortal((<div>Overlay</div>), overlay.element)
I've been using basic HTML/CSS/JS so far, and now I tried using React/Gatsby.
I got API code from Kakao and confirmed the following code is working in index.html:
<body>
<div id="map" style="width:1000px;height:500px;"></div>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608"></script>
<script>{
var mapContainer = document.getElementById('map'),
mapOption = {
center: new kakao.maps.LatLng(37.56591, 126.97894),
level: 4,
mapTypeId : kakao.maps.MapTypeId.ROADMAP
};
var map = new kakao.maps.Map(mapContainer, mapOption);
}</script>
</body>
Since I'm trying React/Gatsby framework, I have to somehow reformat that script to index.js. HTML can be easily copy/pasted to return function, but I don't know how to write the above script in React index.js.
import React from "react"
export default class Home extends React.Component {
render() {
return (
<div style={{ color: `purple` }}>
<p>Welcome to donghwankim.com!</p>
<p>Powered by Gatsby</p>
<div id="map" style={{"height" : "1000px", "width" : "500px"}}></div>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608"></script>
<script>{
var mapContainer = document.getElementById('map'),
mapOption = {
center: new kakao.maps.LatLng(37.56591, 126.97894),
level: 4,
mapTypeId : kakao.maps.MapTypeId.ROADMAP
};
var map = new kakao.maps.Map(mapContainer, mapOption);
}</script>
</div>
)}
}
Few things I tried:
Just copy paste script like above. In this case, I get syntax error from the copied script:
Unexpected token. Did you mean {'}'} or }?
Use dangerouslySetInnerHTML. There is no syntax error, but the map API is not working properly.
Thank you.
It seems like part of this script will need to be rewritten for React. You should look for a library for this that supports React. If there isn't one, you can use DOM refs to access the mapContainer without document.getElementById. Alternatively if you just want to use this script as-is, you can use a simpler static site generator like Jekyll that doesn't require you to use React.
2022 update
Since the release of the Script Gatsby component (powered by Partytown) it's much easier adding third-party scripts. Just:
import React from "react"
import { Script } from "gatsby"
function YourPage() {
return <Script src="https://my-example-script" />
}
export default YourPage
There's a lot of implementation there.
First of all, you need to load your script asynchronously using <Helmet> tag by using:
import React from "react"
import Helmet from "react-helmet"
export default class Home extends React.Component {
render() {
return (
<div style={{ color: `purple` }}>
<Helmet>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608" type="text/javascript"/>
</Helmet>
<p>Welcome to donghwankim.com!</p>
<p>Powered by Gatsby</p>
<div id="map" style={{"height" : "1000px", "width" : "500px"}}></div>
</div>
)}
}
Because of the asynchronous of your issue, you need to load a <div> container for your map and wait for its load, then you need to pass your map options. The preferred method in React, rather than document.getElementById (or similar), what retrieve directly values from the DOM, is using references. You'll need to use a componentDidMount() lifecycle to achieve it, since it's a method invoked that triggers immediately after a component is mounted (inserted into the tree):
import React from "react"
import Helmet from "react-helmet"
export default class Home extends React.Component {
constructor(props) {
super(props);
this.myRef = React.createRef();
}
componentDidMount(){
const map= this.myRef.current;
const mapOption = {
center: new kakao.maps.LatLng(37.56591, 126.97894),
level: 4,
mapTypeId : kakao.maps.MapTypeId.ROADMAP
};
const yourMap = new kakao.maps.Map(map, mapOption);
}
render() {
return (
<div style={{ color: `purple` }}>
<Helmet>
<script src="https://dapi.kakao.com/v2/maps/sdk.js?appkey=3199e8f198aff9d5aff73000faae6608" type="text/javascript"/>
</Helmet>
<p>Welcome to donghwankim.com!</p>
<p>Powered by Gatsby</p>
<div id="map" ref={this.myRef} style={{"height" : "1000px", "width" : "500px"}}></div>
</div>
)}
}
Note: you may need to unmount the map to avoid excessive resource consumption. It depends on how the library is implemented and its documentation.
Recommended readings/references:
https://github.com/nfl/react-helmet
https://reactjs.org/docs/refs-and-the-dom.html
https://reactjs.org/docs/react-component.html#componentdidmount
https://reactjs.org/docs/state-and-lifecycle.html
I'm trying to update a map to my current location using a vue onClick which updates props and sends them to my map component. I am using a :key to rerender my map component when my map data changes and I get some new x,y for my map center. (based on the esri/arcgis example I would need to rebuild the map, if anyone knows this to be wrong let me know please)
VUE js arcgis starting documentation:
https://developers.arcgis.com/javascript/latest/guide/vue/
for some reason my map does render again and seems like it's about to load but then it just stays blank.
maybe someone can tell me if this is an issue with the component still persisting in some way after I force it to render again?
my app.vue
<template>
<div id="app">
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="mapRef"/>
<div class="center">
<b-button class="btn-block" #click="getLocation" variant="primary">My Location</b-button>
</div>
</div>
</template>
<script>
import WebMap from './components/webmap.vue';
export default {
name: 'App',
components: { WebMap },
data(){
return{
lat: -118,
long: 34,
}
},
methods:{
showPos(pos){
this.lat = pos.coords.latitude
this.long = pos.coords.longitude
this.$refs.mapRef.updateCoordinates()
console.log('new location',this.lat,this.long, this.$refs)
},
getLocation(){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPos);
} else {
console.log("Geolocation is not supported by this browser.");
}
},
},
};
</script>
my map component
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
props:['centerX', 'centerY'],
data: function(){
return{
X: this.centerX,
Y: this.centerY,
view: null
}
},
mounted() {
console.log('new data',this.X,this.Y)
// lazy load the required ArcGIS API for JavaScript modules and CSS
loadModules(['esri/Map', 'esri/views/MapView'], { css: true })
.then(([ArcGISMap, MapView]) => {
const map = new ArcGISMap({
basemap: 'topo-vector'
});
this.view = new MapView({
container: this.$el,
map: map,
center: [this.X,this.Y], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
},
methods:{
updateCoordinates(){
this.view.centerAt([this.X,this.Y])
}
}
};
</script>
I don't think the key you're passing as a prop to web-map serves any purpose since it's not being used inside the component.
You could try, instead, to force update the component as such:
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="mapRef" />
this.refs.mapRef.$forceUpdate()
This ensures that you're force updating the whole component, but maybe there's a better solution. Instead of re-rendering the entire component, which means having to create the map once again, you could instead keep the component alive and just use an event to update the coordinates.
Based on https://developers.arcgis.com/javascript/3/jsapi/map-amd.html#centerat, you can re-center the map using the centerAt method.
That way the map component has a method like:
updateCoordinates(coord){
this.view.centerAt(coord)
}
And you can call it on the parent with
this.refs.mapRef.updateCoordinates(newCenter)
Hope it helps, let me know if you do any progress.
I think you can test Watch with setInterVal() for a loop to check your location each 1sec
I Need help to create GeoJSON custom component from React-Leaflet
Write with React and React-Leaflet (last version both)
The code works when write in the Map component, but I want to import/export it to split code
import React from 'react';
import { withLeaflet, GeoJSON } from 'react-leaflet'
import L from 'leaflet'
class CustomGesJSON extends GeoJSON {
getStyle(feature) {
// some code
}
pointToLayer(feature, latlng) {
// some code
}
onEachFeature(feature, layer) {
// some code
}
createLeafletElement(opts) {
const CustomGesJSON = L.geoJSON.extend({
onAdd: (map) => {
this.getStyle = this.getStyle.bind(this);
this.pointToLayer = this.pointToLayer.bind(this);
this.onEachFeature = this.onEachFeature.bind(this);
return this ;
}
});
return new CustomGesJSON({ data: this.props.data });
}
}
function testlog(txt) {
// some code
}
export default withLeaflet(CustomGesJSON);
I've got a error message "GeoJSON is not a constructor"
Function and method (not show here) works, I just need help to make a proper inheritance
Other solution are welcome to
Thanks for your help
It is probable the "GeoJSON" object exported by "react-leaflet" is not an ES6 class, but the Leaflet L.GeoJSON "class".
You can use Leaflet own pre-ES6 class inheritance scheme, as described in the Leaflet class theory tutorial:
const MyCustomClass = GeoJSON.extend({
options: {
onEachFeature: myCustomDefaultFunction
// etc.
}
});
export default MyCustomClass;
I've been trying to initialize a Google map on my vue.js project while including the script :
<script src="https://maps.googleapis.com/maps/api/js?key="MY_API_KEY"&callback=initMap" async defer></script>
The problem is that my .vue files look like that :
<template>
...
</template>
<script>
...
</script>
And I can't include more than one script tag in my vue file, I can show the map while passing by the index.html but I dont really want to put js on the index.html, + I can't point the script callback on a vue method.
Do you guys have some ideas on how to show up that map using a .vue file ? I did use vue2-google-maps but I'd like to use the original google map.
I have a fiddle which is doing something ok : https://jsfiddle.net/okubdoqa/ without using a callback in the script tag, but it doesnt work for me ... Thanks
I'd suggest using npm google-maps instead of adding a script in index.html. You might not need to call google-maps API in every pages, and I'd say it's better to use Webpack properly. You can use npm install google-maps
import GoogleMapsLoader from 'google-maps'
mounted: function () {
GoogleMapsLoader.load(function(google) {
let map = new google.maps.Map(document.getElementById('map'), {
zoom: 15,
center: position
})
})
}
It's a little fussy to get this working without using a library, and there are probably cleaner ways, but you can simply import the library and use it in your components if you want to get up and running.
First, don't use the defer & async options in the <script> tag. Load it in the index.html:
<script src="https://maps.googleapis.com/maps/api/js?key=yourKey"></script>
Then in your component you can access the global google and pass it an element once the component is setup. For example using the setup from the Vuejs cli:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
<div id="myMap"></div>
</div>
</template>
<script>
export default {
name: 'hello',
data () {
return {
msg: 'Welcome to Your Vue.js App'
}},
mounted: function() {
console.log("map: ", google.maps)
this.map = new google.maps.Map(document.getElementById('myMap'), {
center: {lat:61.180059, lng: -149.822075},
scrollwheel: false,
zoom: 4
})
}
}
</script>
<style scoped>
#myMap {
height:300px;
width: 100%;
}
</style>
I was searching for a different issue and found this one, there is another way that you could achieve this without having to add it in index.html.
I faced a similar problem where I had to use two Google API keys for different environments so hard-coding it in to index.html was not a good idea, I did this if it helps anyone -
main.js
export const loadedGoogleMapsAPI = new Promise( (resolve,reject) => {
window['GoogleMapsInit'] = resolve;
let GMap = document.createElement('script');
GMap.setAttribute('src',
`https://maps.googleapis.com/maps/api/js?key=${process.env.GOOGLE_API_KEY}&callback=GoogleMapsInit®ion=IN`);
document.body.appendChild(GMap);
});
MapElem.vue
<template>
<div id="map"></div>
</template>
<script>
import {loadedGoogleMapsAPI} from '#/main'
export default {
name: 'MapEl',
mounted(){
loadedGoogleMapsAPI.then(()=>{
this.initMap()
});
},
methods:{
initMap(){
console.log(google.maps); //You can now access google maps object here
new google.maps.Map(document.getElementById('map'), {
// You configuration goes here
})
}
}
</script>
It's pretty straightforward, you write a Promise in main.js which will resolve to the callback initiated by Google script ( which we dynamically appended to the body ). Once the Promise is resolved you can access the map object in your component.
There's a different way if you would like to keep the code contained in a Component using the async loader $Scriptjs.
Install via npm
npm install scriptjs
import into your component
import $Scriptjs from 'scriptjs
load the script in the mounted hook (assuming you have a method in your component called initMap)
...
mounted () {
$Scriptjs('https://maps.googleapis.com/maps/api/js?key={YOUR_KEY}', () => {
this.initMap()
}
...
Initial vue part after Using google map callback function.
function initMap(){
var app = new Vue({
el:"#app",
data:{
name:"Niklesh Raut",
map:"",
mapOptions:{}
},
mounted(){
this.initMap();
},
methods:{
initMap: function(){
this.mapOptions = {
center: new google.maps.LatLng(21.139808079490507, 79.07690763473511),
zoom: 10,
mapTypeId: 'roadmap'
}
this.map = new google.maps.Map(document.getElementById("map"), this.mapOptions);
}
}
});
}
.wrapper {
display: flex;
width: 100%;
align-items: stretch;
}
#content {
width: 100%;
/*padding: 20px;*/
min-height: 100vh;
transition: all 0.3s;
}
.map{
height: 100%;
width:100%;
}
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script async defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDUFmbwJMBHU_paeMfVO7oqPC1IJEtbJUU&callback=initMap"></script>
</head>
<div id="app" class="wrapper">
<div id="content">
Name : {{name}}
<div id="map" class="map"></div>
</div>
</div>
Jsfiddle Link
Best and simple way to integrate Google Maps to Vue is use npm's libs.
You need to use vue-google-maps
npm install vue2-google-maps
then,
import Vue from 'vue'
import * as VueGoogleMaps from 'vue2-google-maps'
Vue.use(VueGoogleMaps, {
load: {
key: 'YOUR_API_TOKEN',
},
})
Just simple paste code below:
<GmapMap
:center="{lat:10, lng:10}"
:zoom="7"
map-type-id="terrain"
style="width: 500px; height: 300px"
>
</GmapMap>