react-google-maps-api icon indicating copy to clipboard operation
react-google-maps-api copied to clipboard

Unmounting Markers performance using MarkerClusterer

Open csarellano opened this issue 2 years ago • 10 comments

Hello everyone!

I'm currently rendering and clustering more than a 1500 markers. On the first load everything works ok but if I change the state filtering those markers it takes too long (1min) to remove all the markers and just leave the ones I want. It only happens if I use the MarkerClusterer component.

This is what I see while it is removing the markers Screen Shot 2021-09-05 at 11 11 19 AM

I found that the same issue happened on the react-google-maps library

I'm using: os: mac/linux node --version: 16.3.0 react version: 17.0.2 @react-google-maps/api: 2.2.0

Any workaround to resolve this issue?

csarellano avatar Sep 05 '21 16:09 csarellano

Hi mate :), we seem to be studying this problem at the same time. I share with you what I have found

Context

I built this prototype image

function EventsMap(props) {
    console.log('Rendered: Cluster')
    return (
        <MarkerClusterer options={options}>
            {(clusterer) => {
                    return props.markers.map(marker =>
                        <Marker position={marker.position} key={marker.id} clusterer={clusterer}  />
                    )
                }
            }
        </MarkerClusterer>
    )
}

Problem

When you change "tabs" or filters, this changes the marker array causing react to have to remove the previous markers and causing the markers to be generated.

Which causes it to have a behavior similar to this: image

And it takes several seconds to finish removing the old markers to re-render the new ones.

Solution

image

I'm not an expert in react or google maps so I'm not sure of the theoretical basis of this, but it seems that if you "help" React to remove the old markers before rendering the new ones with clusterer.clearMarkers(); the performance goes up a lot.

New Problem (For me)

The problem I have now is that when a new marker arrives in a new tab, the previous ones are erased and only this one remains

If you have a static map that will not receive new markers with Push events or websockets this will be a problem for you, but for me it is.

I think what I will do is to execute clusterer.clearMarkers(); when the tab/filter change is done 🤔... in any case I have to look for how to access clusterer from outside <ClustersMarker>.

Hector1567XD avatar Sep 06 '21 14:09 Hector1567XD

Something else that improves performance is to add noClustererRedraw={true} to each marker, in fact I think it is the right way to deal with this problem, the documentation talks a bit about it in:

https://react-google-maps-api-docs.netlify.app/#!/Marker

But although it solves my performance problems it adds me another problem, the marker does not reload itself, then I have to "move" or interact with it so that new markers are added. 🤔


UPDATE 1:

In another thread someone seems to have solved this: https://github.com/tomchentw/react-google-maps/issues/836#issuecomment-894381349 But I have not quite understood the solution he proposes.

Hector1567XD avatar Sep 06 '21 14:09 Hector1567XD

Hello everyone! I am dealing with this issue and I can't find a way to actually make it work correctly, meaning markers and clusters do get updated correctly. Did anyone found a comprehensive working solution so far?

ivannovazzi avatar Jan 12 '22 16:01 ivannovazzi

Setting noClustererRedraw and re-painting clusterer everytime markers change boosted performance significantly. E.g. in my case I re-paint whenever number of markers has changed:

function Map({ markers }) {
  const clustererRef = useRef()
  useEffect(() => {
    clustererRef.current?.repaint()
  }, [markers.length])
  return (
    <GoogleMap center={center}>
      <MarkerClusterer
        options={clustererOptions}
        onLoad={clusterer => (clustererRef.current = clusterer)}
      >
        {clusterer =>
          markers.map(marker => (
            <MarkerF
              key={marker.id}
              position={marker.position}
              clusterer={clusterer}
              noClustererRedraw={true}
            />
          ))
        }
      </MarkerClusterer>
    </GoogleMap>
  )
}

madox2 avatar Aug 13 '22 09:08 madox2

I confirm calling repaint is helping a lot. We had another problem: we have marker arrays that differ in size (from a couple of hundreds to tens of thousands) and when the size was greater than ten thousands, nothing was painted. We ended up playing with clusterer.batchSize, and a good value seems to be markers.length / 20.

hamez0r avatar Sep 02 '22 14:09 hamez0r

@madox2 Thanks for your suggestion, it helped a lot in my specific case and I haven't observed anything else than improved performance.

ivannovazzi avatar Sep 05 '22 09:09 ivannovazzi

Solution by @madox2 works fantastically. I can confirm repaint the clusterer every time data changes help in performance with big data and some other weird issues.

alifdarsim avatar Oct 08 '22 22:10 alifdarsim

please test MarkerClustererF with 2.15.0 version

JustFly1984 avatar Nov 06 '22 06:11 JustFly1984

Hi @JustFly1984 I just upgraded from 2.8 to 2.17 and the performance seems the same without manually calling repaint as described above. I ran a performance profile in Chrome, with the default clusterer behavior, the "scripting" takes around 5 seconds on my macbook with ~800 markers (same on both 2.8 and 2.17). Using noClustererRedraw + manually calling repaint brings this down to 1.5 seconds.

dylantf avatar Dec 06 '22 15:12 dylantf

If anyone wants a solution for React18, I resolved this by using a combination of state management and clearing of markers, you also need to use MarkerClustererF and MarkerF as they are compatible with React18.

When updating your set of markers, set the state to an empty array and then repopulate it with the desired markers and then use the onUnMount handle to clear the set of markers.

What happens is that when the markers length is set to 0, the onUnMount handler is called which clears the markers and then when you repopulate the markers the markerclusterer is rendered from scratch which means no repaint needed.

Hope this helps!

markers && markers.length > 0 && (<MarkerClustererF ... onUnmount={(clusterer) => clusterer.clearMarkers()} > {(clusterer: Clusterer) => {markers.map(marker => <MarkerF.../>)} </MarkerClustererF>)

releshreyas avatar Jul 21 '23 12:07 releshreyas