Better Native Map Clustering for React Native

Description:

react-native-better-clustering is a React Native map component that groups large marker sets with a C++ Supercluster engine on iOS and Android.

It keeps clustering work off the JavaScript and React Native bridge while preserving the familiar react-native-maps map and marker model.

Features

  • Clusters map markers with a C++ Supercluster engine.
  • Moves clustering computation away from the JavaScript and React Native bridge.
  • Keeps the react-native-maps MapView and Marker programming model.
  • Updates clusters during region changes with a configurable interval.
  • Supports cluster press callbacks and marker change callbacks.
  • Renders custom clusters with access to point counts and expansion regions.
  • Spiders overlapping markers with configurable lines and colors.
  • Lets individual markers opt out with cluster={false}.

Preview

How To Use It

Install Bare React Native Dependencies

Install the package and its native peers:

npm install react-native-better-clustering react-native-nitro-modules react-native-maps react-native-reanimated react-native-worklets
cd ios
pod install
cd ..

Rebuild the native application after installation. Add the Worklets Babel plugin as the last plugin in a bare React Native project:

// babel.config.js
module.exports = {
  presets: ["module:@react-native/babel-preset"],
  plugins: [
    // Keep the Worklets plugin last.
    "react-native-worklets/plugin",
  ],
};

Configure an Expo Development Build

Expo projects can install the same native dependencies with:

npx expo install react-native-better-clustering react-native-nitro-modules react-native-maps react-native-reanimated react-native-worklets

The Expo Babel preset handles the Worklets plugin. Add the react-native-maps configuration to app.json when Google Maps on Android needs an API key:

{
  "expo": {
    "plugins": [
      [
        "react-native-maps",
        {
          "googleMapsApiKey": "YOUR_GOOGLE_MAPS_API_KEY"
        }
      ]
    ]
  }
}

Run the native prebuild and create a development build:

npx expo prebuild --clean
npx expo run:ios
# Or use npx expo run:android

Render a Clustered Map

Import the default MapView from the package and Marker from react-native-maps. The map accepts standard react-native-maps props plus clustering options.

import { StyleSheet, View } from "react-native";
import MapView from "react-native-better-clustering";
import { Marker } from "react-native-maps";
const initialRegion = {
  latitude: 40.7128,
  longitude: -74.006,
  latitudeDelta: 0.35,
  longitudeDelta: 0.35,
};
const places = [
  { id: "museum", title: "Museum", latitude: 40.7794, longitude: -73.9632 },
  { id: "market", title: "Market", latitude: 40.706, longitude: -74.0086 },
];
export default function PlacesMap() {
  return (
    <View style={styles.container}>
      <MapView
        style={styles.map}
        initialRegion={initialRegion}
        radius={50}
        minPoints={2}
        clusterColor="#0F52FF"
        clusterTextColor="#FFFFFF"
        onClusterPress={(cluster, markers) => {
          // Use the cluster and its markers to open a detail panel or fit the map.
          console.log("Cluster selected", cluster, markers.length);
        }}
        onMarkersChange={(markers) => {
          // Store or summarize the markers currently visible in the viewport.
          console.log("Visible markers", markers.length);
        }}
      >
        {places.map((place) => (
          <Marker
            key={place.id}
            identifier={place.id}
            coordinate={{
              latitude: place.latitude,
              longitude: place.longitude,
            }}
            title={place.title}
          />
        ))}
      </MapView>
    </View>
  );
}
const styles = StyleSheet.create({
  container: { flex: 1 },
  map: { flex: 1 },
});

onMarkersChange receives the markers in the current visible cluster state. Keep marker components memoized and give each marker a stable identifier when a large map updates frequently.

Exclude a Marker From Clustering

Set cluster={false} on a marker that should stay visible as an individual annotation:

<Marker
  cluster={false}
  identifier="user-location"
  coordinate={{ latitude: 40.7128, longitude: -74.006 }}
  title="Your location"
/>

Configure Cluster Interaction

Use onClusterPress to respond to a selected cluster. The callback receives the cluster object and its marker list. The cluster object exposes point_count, point_count_abbreviated, and getExpansionRegion() for custom detail or zoom behavior.

The default press flow can be changed with preserveClusterPressBehavior. Use edgePadding when a fit-to-cluster action needs room around the map edges. Set selectedClusterId and selectedClusterColor when the selected cluster needs a distinct color.

Use a Custom Cluster Renderer

Pass renderCluster when the default cluster marker does not match the map design. The documented RenderClusterProps type supplies the cluster data and the press handler needed by a custom marker. Read point_count or point_count_abbreviated from the cluster object and call the provided press handler from the custom marker.

API Reference

Clustering Options

PropDefault or typePurpose
radiusAbout 6% of screen widthSets the cluster radius.
minPoints2Sets the minimum points needed for a cluster.
minZoom1Sets the minimum clustering zoom.
maxZoom20Sets the maximum clustering zoom.
extent512Sets the tile extent used by Supercluster.
nodeSize64Sets the Supercluster node size.
clusteringEnabledtrueTurns clustering on or off.
spiralEnabledtrueControls the spiral layout for overlapping markers.
clusterUpdateIntervalMs100Sets the update interval during region changes. A value of 0 updates on settle.
tracksViewChangesfalseControls marker view tracking.
renderClusterRenderClusterProps rendererRenders a custom cluster marker.

Cluster Styling and Callbacks

PropPurpose
clusterColorSets the default cluster color.
clusterTextColorSets cluster text color.
clusterFontFamilySets the cluster font family.
spiderLineColorSets the line color for spider layouts.
selectedClusterIdIdentifies a selected cluster.
selectedClusterColorSets the selected cluster color.
onClusterPressReceives a cluster and its marker list.
onMarkersChangeReceives the markers in the current visible state.
onRegionChangeCompleteReceives the region, change details, and current markers.

Map and Performance Options

The component accepts standard react-native-maps MapView props. The additional documented options include:

  • animationEnabled for iOS cluster transition animation.
  • layoutAnimationConf for create, delete, and scale update animations.
  • clusterFadeInDuration for cluster fade-in timing.
  • edgePadding for cluster expansion fitting.
  • width and height for map dimensions used by clustering calculations.
  • mapRef and superClusterRef for external references.
  • maxZoom and spiralEnabled for map edge and overlap behavior.

Advanced Exports

  • react-native-better-clustering/hooks exports useClusterer.
  • react-native-better-clustering/clusterer exports Clusterer for declarative rendering.
  • react-native-better-clustering/engine exports Supercluster, createClusterEngine, and geometry helpers.
  • react-native-better-clustering/geojson exports GeoJSON helpers.
  • react-native-better-clustering/utils exports utility functions.
  • react-native-better-clustering/compat provides the compatibility alias documented by the project.

Migration From react-native-map-clustering

The project documents a small migration path:

// Before
import MapView from "react-native-map-clustering";
// After
import MapView from "react-native-better-clustering";

Existing Marker children and cluster props remain part of the migration model. Review the native setup requirements before rebuilding the application.

Tags:

Add Comment