Skip to main content

Migrating from react-native-map-clustering

The main export is the clustered MapView, with the props and callbacks of react-native-map-clustering 4.0. react-native-better-clustering/compat is a backwards-compatible alias. Change one import:

- import MapView from 'react-native-map-clustering'
+ import MapView from 'react-native-better-clustering'

Your <Marker> children, clustering props and react-native-maps props carry over. Children are picked for clustering by the same rule (see Which children are clustered). Check the differences below, and if you come from 3.x, the onRegionChangeComplete arguments.

Unlike react-native-map-clustering, this package has native code: it needs React Native 0.78+ with the New Architecture, react-native-nitro-modules, and a rebuild of the app (a development build with Expo, not Expo Go). See Platform Setup.

Differences​

react-native-map-clustering 4.0This package
onRegionChangeComplete(region, details, markers); 3.x passed (region, markers)(region, details, markers), called once the clusters for the settled region are computed. Development builds warn about a handler that declares two parameters (from 3.x)
onMarkersChangeCalled each time the region settlesCalled once the first index has loaded, then whenever the visible markers and clusters change, also while the map moves
clusterColor default#00B386 (green)#0F52FF (blue). Pass clusterColor="#00B386" to keep the old color
Default cluster bubbleTwo circles in clusterColor, with the full countA ring and a shadow, with counts from 1,000 abbreviated (1.2k). Pass renderCluster to draw your own
styleApplied to the mapApplied to a View that wraps the map; the map fills that view. Sizing and positioning styles behave the same, but padding now insets the map
animationEnabledRuns layoutAnimationConf when the region settles, on iOSFades and glides the default cluster bubbles on zoom, on iOS and Android, with the optional react-native-reanimated (see clusterFadeInDuration)
layoutAnimationConfLayoutAnimation config, spring by defaultAccepted and ignored: LayoutAnimation cannot animate map markers on the New Architecture
SpiderfyFrom camera zoom 19, once a cluster has been pressedFrom camera zoom spiderfyMinZoom (19), or at any zoom when pressed; only clusters that zooming can't split, of at most spiderfyMaxLeaves markers. See Markers at the same location
Zoomed in past maxZoomWith a maxZoom below 20, every marker shows on its own, and markers at one location stackClusters stay as they are at maxZoom, and spiderfy as above
Cluster pressFits the map to every marker in the clusterFits the map to the cluster's bounds: the same region, without reading every marker unless you pass onClusterPress
superClusterRefA supercluster instance, rebuilt on the JS thread whenever the children changeA Supercluster on the native engine, built in the background (see below)
Build failuresNo onError propA failed native build, or an invalid clustering prop, is thrown during render, unless you pass onError
<Marker cluster={false}> in TypeScriptDoes not type-check: no type declares clusterType-checks (see Per-marker opt-out). A @ts-expect-error you added for it now fails as unused: remove it

MapView also adds props react-native-map-clustering does not have, such as spiderfyMinZoom, spiderfyMaxLeaves, clusterFadeInDuration, clusterUpdateIntervalMs, clusterRenderMargin and onError. See the MapView API.

Coming from react-native-map-clustering 3.x​

onRegionChangeComplete follows react-native-map-clustering 4.x: it receives (region, details, markers). 3.x passed (region, markers), so a handler written for 3.x now gets the details of react-native-maps where it expects the markers. TypeScript only catches this when the handler's parameters have types. Take the markers from the third argument:

<MapView
- onRegionChangeComplete={(region, markers) => setVisible(markers)}
+ onRegionChangeComplete={(region, details, markers) => setVisible(markers)}
>

In development, MapView warns once when the handler declares exactly two parameters (a parameter with a default value, and every one after it, does not count). A handler that means (region, details) can declare the third parameter, (region, details, _markers) => …, to silence the notice.

superClusterRef​

superClusterRef.current is a Supercluster, not a supercluster instance. It has supercluster's query methods (getClusters, getChildren, getLeaves, getClusterExpansionZoom), but no getTile, and it changes at different times:

  • The index builds on a background thread. Until the first build has loaded, current is null, so check it before you query.
  • When markers are added, removed, moved or reordered, the previous engine keeps serving queries until the new index has loaded. Then current is replaced and the previous engine destroyed, so read superClusterRef.current when you query instead of keeping the instance: a destroyed one throws.
  • With clusteringEnabled={false}, and after the map unmounts, current is null.

Whenever current is not null, it is loaded and ready to query.

Known fixes over react-native-map-clustering​

IssueStatus
Crash on NaN zoom (#294)Guarded
Map size can't be 0 on Android (#285)Layout-aware
maxZoom / spiralEnabled ignored (#274, #275)Implemented
Unstable clusters on zoom (#255)Stable references
Per-marker cluster={false} (#297)Supported