dburles / meteor-google-maps

🗺 Meteor package for the Google Maps Javascript API v3
https://atmospherejs.com/dburles/google-maps
MIT License
196 stars 49 forks source link

Help needed with React usage, collection, read-router and react-komposer #146

Closed melkati closed 7 years ago

melkati commented 7 years ago

Hi.

I'm sorry if it's a very long message with many questions (and probably some of them very basic) but I'm just starting with Meteor and React.

I have some issues while using the package with react. I'll try to document and include enough code and information so, hopefully, when issues are fixed, it serves as an example also for others trying to use it with react as I see these questions are very frequent but can't find a clear answer, after searching the net for days.

I have Mongo collection where I store GPS positions sent from my mobile via MQTT with OwnTracks http://owntracks.org/booklet/features/android/

I managed to show the positions in a Google Map but I'm unable to make it reactive (I created a container with react-composer where the collection is passed to the Google Map component each time there is a new document).

I know I have some duplicate code as I based it on the react example don't I don't totally understand how it works. Also I left a lot of code I used to debug, but I think it makes clear the intent at each part of the code.

The issues I have are:

  1. The map doesn't re-renders when there is a change on the data.
  2. I add a Marker with InfoBox for each document, but all of the markers show the same information.
  3. The first time I load the map it shows but if I change page (via react-router) and select again the Map page it do not re-renders. (I think it's this same issue: https://github.com/dburles/meteor-google-maps/issues/144)

Now to the code:

This is the main page, where everything is loaded:

\imports\ui\pages\Maps.js

import React from 'react';
import { Panel, Row, Col } from 'react-bootstrap';
import NoderedMap from './../containers/NoderedMap'

const Maps = () => (
  <div className="Maps">
    <Panel>
    <Row>
      <Col xs={ 12 }>
        <NoderedMap />
      </Col>
    </Row>
    </Panel>
  </div>
);

export default Maps;

This is the container where new data is loaded into the collection each time there is a new document. I tested it and it works.

\imports\ui\containers\NoderedMap.js

import { composeWithTracker } from 'react-komposer';
import { Meteor } from 'meteor/meteor';
import NodeRedCollection from '../../api/nodered/nodered.js';
import NoderedMap from '../components/NoderedMap.js';
import Loading from '../components/Loading.js';

const composer = (params, onData) => {
  const subscription = Meteor.subscribe('nodered.list');
  if (subscription.ready()) {
    const nodered = NodeRedCollection.find().fetch();
    // const nodered = NodeRedCollection.findOne();
    console.log('NoderedMap Container:', nodered);
    onData(null, { nodered });
  }
};

export default composeWithTracker(composer, Loading)(NoderedMap);

Now to the presentational component... it's in two files as it's based on the example and I don't understand If I should get rid of the createContainer. I already tried to remove it, but then everything stops working:

\imports\ui\components\NoderedMap.js

import React from 'react';
import Moment from 'moment';

import GoogleMap from './googlemaps/GoogleMap';

let dataPoints = [];
let payload = [];
let lat;
let lon;
let theDevice;
let timeStamp;

class NoderedMap extends React.Component {
  componentDidMount() {
    console.log('componentDidMount');
  }

  componentDidUpdate() {
    console.log('componentDidUpdate. Lat:', lat, ' Long: ', lon, 'Device: ', theDevice);
    let marker;
    GoogleMaps.ready(name, map => {
      marker = new google.maps.Marker({
        position: new google.maps.LatLng(lat, lon),
        map: map.instance,
        zoom: 14,
      });
      const content = `
      <div>
        <p> Lat: <span> ${lat} </span> </p>
        <p> Lon: <span> ${lon} </span> </p>
        <p> Device: <span> ${tid} </span> </p>
        <p> Timestamp: <span> ${Moment(timeStamp).format('DD MM YY hh:mm')} </span> </p>
      </div>
      `;
      var infowindow = new google.maps.InfoWindow({
        content: content
      });
      marker.addListener('click', function() {
        infowindow.open(map.instance, marker);
      });
      map.instance.setCenter(new google.maps.LatLng(lat, lon));
    });
  }

  componentWillUnmount() {
    console.log('componentWillUnmount');
  }

  handleMapOptions() {
    return {
      center: new google.maps.LatLng(lat, lon),
      zoom: 18,
    };
  }
  handleMapOptions() {
    return {
      center: new google.maps.LatLng(lat, lon),
      zoom: 18,
      libraries: 'places,visualization',
      key: 'AIzaSyBsMC6mgZNapenjXd5Ygu90ouJHymHH19k',
    };
  }

  handleOnReady(name) {
    console.log('handleOnReady');
    let marker;
    GoogleMaps.ready(name, map => {
      marker = new google.maps.Marker({
        position: new google.maps.LatLng(lat, lon),
        map: map.instance,
        zoom: 14,
      });
      google.maps.event.addListener(map.instance, 'click', function (event) {
        console.log('Lat:', event.latLng.lat(), 'Lon:', event.latLng.lng());
      });
      for (var i = 0; i < dataPoints.length; i++) {
        if ((dataPoints[i].payload.lat !== undefined) && (dataPoints[i].payload.lon !== undefined)) {
          marker = new google.maps.Marker({
            position: new google.maps.LatLng(dataPoints[i].payload.lat, dataPoints[i].payload.lon),
            map: map.instance,
            zoom: 16,
          });
          console.log('timeStamp', timeStamp);
          const content = `
          <div>
            <p> Lat: <span> ${dataPoints[i].payload.lat} </span> </p>
            <p> Lon: <span> ${dataPoints[i].payload.lon} </span> </p>
            <p> Device: <span> ${dataPoints[i].payload.tid} </span> </p>
            <p> Timestamp: <span> ${Moment(timeStamp).format('DD MM YY hh:mm')} </span> </p>
          </div>
          `;
          console.log(content);
          var infowindow = new google.maps.InfoWindow({
            content: content
          });
          marker.addListener('click', function() {
            infowindow.open(map.instance, marker);
          });
        }
      }
    });
  }

  render() {
    dataPoints = this.props.nodered;
    payload = dataPoints[dataPoints.length - 1].payload;
    lat = payload.lat;
    lon = payload.lon;
    theDevice = payload.tid;
    timeStamp = payload.tst * 1000;
    console.log('dataPoints', dataPoints);
    return (
      <GoogleMap onReady={this.handleOnReady} mapOptions={this.handleMapOptions}>
        Loading!
      </GoogleMap>
    );
  }
}

NoderedMap.propTypes = {
  nodered: React.PropTypes.array,
};

export default NoderedMap;

Second component: \imports\ui\components\googlemaps\GoogleMap.js

import React, { PropTypes } from 'react';
import { Random } from 'meteor/random';
import { createContainer } from 'meteor/react-meteor-data';

export class GoogleMap extends React.Component {
  componentDidMount() {
    GoogleMaps.load(this.props.options || {});
  }

  componentDidUpdate() {
    debugger;
    if (this.props.loaded) {
      this.name = Random.id();

      GoogleMaps.create({
        name: this.name,
        element: this.container,
        options: this.props.mapOptions(),
      });

      this.props.onReady(this.name);
    }
  }

  componentWillUnmount() {
    if (GoogleMaps.maps[this.name]) {
      google.maps.event.clearInstanceListeners(GoogleMaps.maps[this.name].instance);
      delete GoogleMaps.maps[this.name];
    }
  }

  render() {
    return (
      <div className="map-container" ref={c => (this.container = c)}>
        {this.props.children}
      </div>
    );
  }
}

GoogleMap.propTypes = {
  loaded: PropTypes.bool.isRequired,
  onReady: PropTypes.func.isRequired,
  options: PropTypes.object,
  mapOptions: PropTypes.func.isRequired,
  children: PropTypes.node,
};

GoogleMapContainer = createContainer(() => ({ loaded: GoogleMaps.loaded() }), GoogleMap);

export default GoogleMapContainer;

One last issue:

I tried including the Google Maps Api Key in the handleMapOptions() but I still can see in the console the message "Google Maps API warning: NoApiKeys https://developers.google.com/maps/documentation/javascript/error-messages#no-api-keys".

  handleMapOptions() {
    return {
      center: new google.maps.LatLng(lat, lon),
      zoom: 18,
      libraries: 'places,visualization',
      key: 'mykey.....AIzaSyBsMC6mgZNape',
    };
  }
dburles commented 7 years ago

I've just updated the GoogleMap component to fix a bug whereby the map would fail to re-render once unmounted/remounted https://github.com/dburles/meteor-google-maps-react-example. Also I've added some reactivity, click the map to add markers and they're inserted into a collection.

dburles commented 7 years ago

Also pass the key into GoogleMaps.load