React-google-maps: Fundamental performance problems with MarkerClusterer

Created on 23 May 2018  路  4Comments  路  Source: tomchentw/react-google-maps

I have tried and failed to resolve performance problems with having many markers using the MarkerClusterer. I now believe that the performance issue is an architectural limitation caused by the fact that for each Marker a <div></div> is rendered and performance decreases lineairly with increasing numbers of markers.

The MarkerClusterer is intended to deal with many markers and it's not working as advertised. I would recommend rewriting this and use the clusterer api more directly, avoiding a dom node for every marker. I can share the code I wrote based google-map-react as I was forced to move away from react-google-maps. Certainly not as elegant as it could be, but I was under a time crunch.

Most helpful comment

Hi, I had the same problem. I was helped to hand over the prop "noRedraw" to the marker. The problem is that otherwise for every marker that is added or deleted the entire card is reloaded.

All 4 comments

:+1:

I was also forced to write my own component for dealing with multiple markers.

@mschipperheyn I am also about to use MarkerClusterer with google-map-react. If you happen to have your component as a snippet somewhere, it would be very helpful!

/* global google */
import React from 'react';
import PropTypes from 'prop-types';
import GoogleMapReact from 'google-map-react';
import MarkerClustererPlus from 'marker-clusterer-plus';
import styles from './map.css';

const clusterIconStyles = [
  {
    height: 90,
    textColor: '#ffffff',
    url: '/img/senseo-90.png',
    width: 90
  }
];

const markerClustererFactory = (map, markers) => {
  const mcOptions = {
    maxZoom: 15,
    gridSize: 60,
    enableRetinaIcons: true,
    averageCenter: true,
    styles: clusterIconStyles
  };
  return new MarkerClustererPlus(map, markers, mcOptions);
};

export default class Map extends React.PureComponent {
  static contextTypes = {
    goApiId: PropTypes.string.isRequired
  };

  static propTypes = {
    center: PropTypes.shape({
      lat: PropTypes.number.isRequired,
      lng: PropTypes.number.isRequired
    }),
    markers: PropTypes.arrayOf(
      PropTypes.shape({
        id: PropTypes.number.isRequired,
        lng: PropTypes.number.isRequired,
        lat: PropTypes.number.isRequired,
        rating: PropTypes.number.isRequired
      })
    ),
    zoom: PropTypes.number,
    highlight: PropTypes.number,
    onMarkerClick: PropTypes.func.isRequired
  };

  static defaultProps = {
    center: {
      lat: 52.092876,
      lng: 5.10448
    },
    markers: [],
    zoom: 8,
    highlight: -1
  };

  state = {
    draggable: true
  };

  componentWillMount() {
    const { goApiId } = this.context;
    this.mapConfig = {
      key: goApiId,
      v: '3.exp',
      libraries: ['geometry', 'drawing', 'places']
    };
  }

  componentDidMount() {
    if (typeof window !== 'undefined') {
      window.addEventListener('resize', this.updateWindowDimensions);
      this.updateWindowDimensions();
    }
  }

  componentDidUpdate(prevProps) {
    const { markers } = this.props;
    if (this.markerClusterer) {
      if (prevProps.markers !== markers) this.drawMarkers();
    } else if (typeof window !== 'undefined') {
      this.loadInterval = setInterval(() => {
        if (this.markerClusterer && markers.length > 0) {
          clearInterval(this.loadInterval);
          this.drawMarkers();
        }
      }, 1000);
    }
  }

  componentWillUnmount() {
    this.markerClusterer = null;
    this.map = null;
    if (typeof window !== 'undefined')
      window.removeEventListener('resize', this.updateWindowDimensions);
  }

  handleRenderMarkers = map => {
    const { markers } = this.props;
    this.map = map.map;
    if (!this.markerClusterer)
      this.markerClusterer = markerClustererFactory(map.map, markers);
    if (markers.length !== this.markerClusterer.getMarkers().length)
      this.drawMarkers();
  };

  updateWindowDimensions = () => {
    if (typeof window !== 'undefined')
      this.setState({
        draggable: window.innerWidth > 479.8
      });
  };

  drawMarkers = () => {
    const { markers, highlight, onMarkerClick } = this.props;
    if (!this.markerClusterer) return;
    google.maps.Marker.prototype.isDraggable = () => false;
    const googleMarkers = markers.map(({ id, lng, lat }) => {
      const marker = new google.maps.Marker({
        map: this.map,
        position: new google.maps.LatLng(lat, lng),
        draggable: false,
        icon:
          id === highlight
            ? '/img/senseo-pin-featured.png'
            : '/img/senseo-pin.png'
      });
      google.maps.event.addListener(marker, 'click', () => {
        onMarkerClick(id);
      });
      return marker;
    });
    this.markerClusterer.clearMarkers();
    this.markerClusterer.addMarkers(googleMarkers, true);
    this.markerClusterer.repaint();
  };

  render() {
    const { zoom, center } = this.props;
    return (
      <div className={styles.mapContainer}>
        <GoogleMapReact
          bootstrapURLKeys={this.mapConfig}
          defaultCenter={center}
          defaultZoom={zoom}
          onGoogleApiLoaded={this.handleRenderMarkers}
          yesIWantToUseGoogleMapApiInternals
        />
      </div>
    );
  }
}

Hi, I had the same problem. I was helped to hand over the prop "noRedraw" to the marker. The problem is that otherwise for every marker that is added or deleted the entire card is reloaded.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

PaulieScanlon picture PaulieScanlon  路  3Comments

bansalvks picture bansalvks  路  3Comments

0x1bitcrack3r picture 0x1bitcrack3r  路  3Comments

madbean picture madbean  路  3Comments

farhan687 picture farhan687  路  3Comments