React-google-maps: MarkerClusterer flashing/acting weird?

Created on 11 Dec 2015  Â·  31Comments  Â·  Source: tomchentw/react-google-maps

I tried wrapping my existing array of Marker objects in a MarkerClusterer and I'm seeing the following odd behavior (the marker clusters flash and disappear occasionally, but sometimes they just show up normally):

MarkerClusterer flashing

Is this a known issue? Any clue what's going on?

Most helpful comment

Hey, I got this working.

The trouble is indeed with the controlled zoom. react-google-maps sends a map.setZoom(props.zoom) at every render, even when map.getZoom() === props.zoom.

I fixed the situation by doing this:

1) Changed to an uncontrolled zoom.

            <GoogleMap ref={this.mapRendered}
                       zoom={zoom}
                       center={center} .../

to

            <GoogleMap ref={this.mapRendered}
                       defaultZoom={zoom}
                       center={center} ... /

2) Store a reference to map in this.mapRendered
3) In componentWillReceiveProps (nextProps) do this:

    if (map && map.getZoom() !== nextProps.zoom) {
      map.setZoom(nextProps.zoom)
    }

And it works.

All 31 comments

I personally don't use MarkerCluster a lot but this seems very weird. One question, do you render your <GoogleMap> in a controlled way or uncontrolled way?

Controlled. (I specify zoom and center props rather than defaults)

Does it matter if the <Marker /> instances are in an array as children of <MarkerClusterer /> instead of "naked" children? (I thought they were essentially the same)

What about if the <Makrer />s have child <InfoWindow /> instances?

let elements = [];
for (let item of iterator) {
  if (item === null) continue;
  const id = item.id;
  const position = item.position;
  elements.push(
    <Marker
      {...props}
      key={id}
      ref={`marker_${id}`}
      position={position}
    >
      { info ?
        <InfoWindow
          {...props}
          key={`${info.ref}_info`}
          owner={info.ref}
          content={info.name} />
      : null }
    </Marker>
  );
}

<MarkerClusterer>{ elements }</MarkerClusterer>

I think it should be children of <MarkerCluster />. I'm suspecting this line of code:
https://github.com/tomchentw/react-google-maps/blob/master/src/addons/addonsCreators/MarkerClustererCreator.js#L96

Also seeing this.

This is also the behavior on their demos it seems: https://googlemaps.github.io/js-marker-clusterer/examples/simple_example.html

And our implementation is using timers it seems: https://github.com/mikesaidani/marker-clusterer-plus/search?utf8=%E2%9C%93&q=setTimeout

@vvo I'm not seeing the problematic behavior on the demo site you linked...

@farrrr Any ideas on this?

@idolize may you give me a reproducible example for me? I think that will help me to trace.

I try to modified example to below. and everything is fine. InfoWindow works fine with MarkerClusterer

import {default as React, Component} from 'react';
import {default as fetch} from 'isomorphic-fetch';

import {GoogleMap, Marker, InfoWindow } from 'react-google-maps';
import {default as MarkerClusterer} from 'react-google-maps/lib/addons/MarkerClusterer';

export default class MarkerClustererExample extends Component {
  state = {
    markers: [],
  }

  componentDidMount() {
    fetch('https://gist.githubusercontent.com/farrrr/dfda7dd7fccfec5474d3/raw/758852bbc1979f6c4522ab4e92d1c92cba8fb0dc/data.json')
      .then(res => res.json())
      .then(data => {
        this.setState({ markers: data.photos });
      });
  }

  render() {
    const { markers } = this.state;

    return (
      <GoogleMap
        containerProps={{
          ...this.props,
          style: {
            height: '100%',
          },
        }}
        defaultZoom={ 3 }
        defaultCenter={{ lat: 25.0391667, lng: 121.525 }}>
          <MarkerClusterer
           averageCenter={ true }
           enableRetinaIcons={ true }
           gridSize={ 60 }>
           { markers.map(marker => (
             <Marker
               position={{ lat: marker.latitude, lng: marker.longitude }}
               key={ marker.photo_id }>
               <InfoWindow
                 key={ `${marker.photo_id}_info` }
                 content={ marker.photo_title } />
             </Marker>
           )) }
       </MarkerClusterer>
      </GoogleMap>
    );
  }
}

I've been doing some testing by removing chunks from my code, and it seems to be related to using a _controlled_ <GoogleMap> (where I set the new center and zoom props with a handler from onBoundsChanged).

When I remove this code (delete the onBoundsChanged) and just use the uncontrolled <GoogleMap> it works as expected. I will try to make a fiddle or sample repo.

Try this:

import {default as React, Component} from 'react';
import {default as fetch} from 'isomorphic-fetch';

import {GoogleMap, Marker, InfoWindow } from 'react-google-maps';
import {default as MarkerClusterer} from 'react-google-maps/lib/addons/MarkerClusterer';

export default class MarkerClustererExample extends Component {
  state = {
    markers: [],
    zoom: 3,
    lat: 25.0391667,
    lng: 121.525
  }

  componentDidMount() {
    fetch('https://gist.githubusercontent.com/farrrr/dfda7dd7fccfec5474d3/raw/758852bbc1979f6c4522ab4e92d1c92cba8fb0dc/data.json')
      .then(res => res.json())
      .then(data => {
        this.setState({ markers: data.photos });
      });
  }

  handleBoundsChanged = () => {
    if (!this.map) {
      // Wait for map to load
      return;
    }
    const center = this.map.getCenter();
    const zoom = this.map.getZoom();
    const stateCenter = new google.maps.LatLng(this.state.lat, this.state.lng);

    // Notice: Check equality here, or it will fire event infinitely
    if (zoom !== this.state.zoom || !center.equals(stateCenter)) {
      const lat = center.lat();
      const lng = center.lng();
      this.setState({ lat, lng, zoom });
    }
  }

  render() {
    const { markers, zoom, lat, lng } = this.state;

    return (
      <GoogleMap
        ref={ map => this.map = map }
        containerProps={{
          ...this.props,
          style: {
            height: '100%',
          },
        }}
        onBoundsChanged={ this.handleBoundsChanged }
        zoom={ zoom }
        center={ ({ lat, lng }) }>
          <MarkerClusterer
           averageCenter={ true }
           enableRetinaIcons={ true }
           gridSize={ 60 }>
           { markers.map(marker => (
             <Marker
               position={{ lat: marker.latitude, lng: marker.longitude }}
               key={ marker.photo_id }>
               <InfoWindow
                 key={ `${marker.photo_id}_info` }
                 content={ marker.photo_title } />
             </Marker>
           )) }
       </MarkerClusterer>
      </GoogleMap>
    );
  }
}

@idolize You need disable InfoWindow autoPan, because InfoWindow will autoPan by default.and it will change center always when it show.

<InfoWindow
     options={{ disableAutoPan: true }} 
     key={ `${marker.photo_id}_info` }
     content={ marker.photo_title } />

and everything will be fine.

@farrrr I just tried that and it did not fix the problem. In fact, I think the <InfoWindow>s were a red herring, because I can still reproduce the issue in a controlled <GoogleMap> component without any <InfoWindow>s.

@idolize I will build an online example for u when I back home.

@idolize I made an example here: https://farrrr.github.io/react-google-maps/#basics/marker-clusterer

I suggest change zoom / center to defaultZoom / defaultCenter, because when u move map, it will trigger
handleBoundsChanged serval times, so it made map always repaint. You still can use zoom / center props, but it will be laggy.

@farrrr I just feel some laggy for your example provided. Will adding _.debounce to handleBoundsChanged help?

@tomchentw yep, you are right, I update the example :)

I see the exact same problem. My Map is controlled as well, and after a 2nd render the MarkerClusterer disappears. I noticed that when I then pan the map it will trigger an google maps 'idle' event which in turn triggers a redraw() on the clusterer and it appears again.

Ok, I think I found the problem. Because the zoom property is controlled, the map will generate a zoom_changed when redrawn, but it never generates the idle event. So the viewport is reset, but it is never redrawn.

Ok, I think I found the problem. Because the zoom property is controlled, the map will generate a zoom_changed when redrawn, but it never generates the idle event. So the viewport is reset, but it is never redrawn.

@hmeerlo @lauffenp do you think that this need to be fixed in react-google-maps?

@tomchentw I think it should be fixed, but I have no idea if it is an easy fix. I reverted to the uncontrolled zoom property which was good enough for me.

We're going to look into this shortly.

We're also looking for maintainers. Involve in #266 to help strengthen our community!

I will check it this week...

Has anyone gotten this to work with a controlled map?

Hey, I got this working.

The trouble is indeed with the controlled zoom. react-google-maps sends a map.setZoom(props.zoom) at every render, even when map.getZoom() === props.zoom.

I fixed the situation by doing this:

1) Changed to an uncontrolled zoom.

            <GoogleMap ref={this.mapRendered}
                       zoom={zoom}
                       center={center} .../

to

            <GoogleMap ref={this.mapRendered}
                       defaultZoom={zoom}
                       center={center} ... /

2) Store a reference to map in this.mapRendered
3) In componentWillReceiveProps (nextProps) do this:

    if (map && map.getZoom() !== nextProps.zoom) {
      map.setZoom(nextProps.zoom)
    }

And it works.

@mariusandra Awesome! Can you make a PR to handle this directly in the library so developers won't have to worry about doing this themselves if they use a controlled component?

Oh @mariusandra you made my day! Thank you very much. It works like a charm. Willing to see a PR linked to this issue so other devs won't be digging around any longer.

Would love to see an ultimate solution to get rid of this controlled/uncontrolled hell……

@idolize Hi, does your info window also works for the clustered markers, i m trying to do the same but it works only for one marker not for clustered markers. Please help me if you have any idea how can i show info windows for the clustered markers as well.

Just to let you know 6.0.0 is released on npm beta tag now. We also have a new demo page. Feel free to try it:
https://tomchentw.github.io/react-google-maps/

I had this issue when using fitBounds using refs on the <GoogleMap /> component. It's fine if I wrap fitBounds in Ramda's once so that we only fit to bounds once — which does it make sense. After that it's perfectly fine.

const fitBounds = once((map, bounds) => map.fitBounds(bounds));

// ...

<GoogleMap ref={map => fitBounds(map, bounds)} />
Was this page helpful?
0 / 5 - 0 ratings

Related issues

PaulieScanlon picture PaulieScanlon  Â·  3Comments

MrSaints picture MrSaints  Â·  3Comments

craigcartmell picture craigcartmell  Â·  4Comments

ShintaroNippon picture ShintaroNippon  Â·  3Comments

bossbossk20 picture bossbossk20  Â·  3Comments