react-map-gl-supercluster
v4.0.0
Published
> The easiest way to get `react-map-gl` and `supercluster` to work together
Maintainers
Readme
react-map-gl-supercluster
The easiest way to get
react-map-glandsuperclusterto work together
Highlights
- TypeScript support.
- ESM support.
- Ready for tree shaking.
- No unnecessary re-renders.
Install
$ pnpm add react-map-gl-supercluster
# or
$ yarn add react-map-gl-supercluster
# or
$ npm install react-map-gl-superclusterUsage
Choose the react-map-gl-supercluster entrypoint that matches your react-map-gl entrypoint.
| react-map-gl entrypoint | react-map-gl-supercluster entrypoint |
| ---------------------------- | ----------------------------------------- |
| react-map-gl/mapbox | react-map-gl-supercluster/mapbox |
| react-map-gl/maplibre | react-map-gl-supercluster/maplibre |
| react-map-gl/mapbox-legacy | react-map-gl-supercluster/mapbox-legacy |
There is no root entrypoint in v3+ because react-map-gl v8 no longer has a default root export.
Example usage
import { type ReactElement, useMemo, useState } from 'react'
import Map, { type MapRef, Marker } from 'react-map-gl/mapbox'
import { isCluster, type PointFeature, useSupercluster } from 'react-map-gl-supercluster/mapbox'
type Item = {
id: string
longitude: number
latitude: number
}
type ItemProperties = { item: Item }
type ItemClusterProperties = { items: Item[] }
function MyAwesomeMap({ items }: { items: Item[] }): ReactElement {
const [map, setMap] = useState<MapRef | null>(null)
const points = useMemo(() => createPoints(items), [items])
const { supercluster, clusters } = useSupercluster(points, {
mapRef: map,
map: mapFeature,
reduce: reduceCluster,
})
const expandCluster = (clusterId: number, coordinates: { longitude: number; latitude: number }) => {
const zoom = supercluster.getClusterExpansionZoom(clusterId)
map?.easeTo({
center: [coordinates.longitude, coordinates.latitude],
zoom,
})
}
return (
<Map ref={setMap}>
{clusters.map((cluster) => {
const [longitude, latitude] = cluster.geometry.coordinates
return isCluster(cluster) ? (
<ClusterMarker
key={`cluster-${cluster.properties.cluster_id}`}
longitude={longitude}
latitude={latitude}
onClick={() => expandCluster(cluster.properties.cluster_id, { longitude, latitude })}
/>
) : (
<Marker key={`item-${cluster.properties.item.id}`} longitude={longitude} latitude={latitude} />
)
})}
</Map>
)
}
function createPoints(items: Item[]): Array<PointFeature<ItemProperties>> {
return items.map(createPoint)
}
function createPoint(item: Item): PointFeature<ItemProperties> {
return {
type: 'Feature',
properties: { item },
geometry: {
type: 'Point',
coordinates: [item.longitude, item.latitude],
},
}
}
function mapFeature(props: ItemProperties): ItemClusterProperties {
return { items: [props.item] }
}
function reduceCluster(memo: ItemClusterProperties, props: ItemClusterProperties): void {
memo.items = memo.items.concat(props.items)
}If the hook is rendered inside Map, mapRef can be omitted. The hook resolves the current map from the matching react-map-gl entrypoint.
import { type ReactElement, useMemo } from 'react'
import Map, { Marker } from 'react-map-gl/mapbox'
import { isCluster, useSupercluster } from 'react-map-gl-supercluster/mapbox'
function MyAwesomeMap({ items }: { items: Item[] }): ReactElement {
return (
<Map>
<Markers items={items} />
</Map>
)
}
type MarkersProps = {
items: Item[]
}
function Markers(props: MarkersProps) {
const { items } = props
const points = useMemo(() => createPoints(items), [items])
const { clusters } = useSupercluster(points, {
map: mapFeature,
reduce: reduceCluster,
})
return (
<>
{clusters.map((cluster) => {
const [longitude, latitude] = cluster.geometry.coordinates
return isCluster(cluster) ? (
<ClusterMarker
key={`cluster-${cluster.properties.cluster_id}`}
longitude={longitude}
latitude={latitude}
/>
) : (
<Marker key={`item-${cluster.properties.item.id}`} longitude={longitude} latitude={latitude} />
)
})}
</>
)
}Demo
The repository includes a Vite + MapLibre example. It uses the public MapLibre demo style, so no Mapbox token is required.
$ pnpm exampleOpen the URL printed by Vite.
API
useSupercluster
The hook can be used in a component which renders Map component or inside Map children.
Arguments
points- GeoJSON points array. The value should be memoized.options– various options, see below.
Return value
Object which contains 2 fields:
clusters– clusters listsupercluster– supercluster instance.
Options
| Option | Default | Description |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| mapRef | Optional | MapRef instance. Optional when the hook is rendered inside Map. |
| boundsPadding | 0 | Extra viewport fraction (per side) included when querying clusters. Markers near the edges don't pop in and out, and pans at the same zoom inside the padded area skip recomputation. Larger values render more off-screen markers. |
| minZoom | 0 | Minimum zoom level at which clusters are generated. |
| maxZoom | 16 | Maximum zoom level at which clusters are generated. |
| minPoints | 2 | Minimum number of points to form a cluster. |
| radius | 40 | Cluster radius, in pixels. |
| extent | 512 | Tile extent. |
| nodeSize | 64 | Size of the KD-tree leaf node. |
| generateId | false | Whether to generate ids for input features. |
| map | Optional | A function that returns cluster properties corresponding to a single point. Should be memoized. See supercluster property map/reduce options. |
| reduce | Optional | A reduce function that merges properties of two clusters into one. Should be memoized. See supercluster property map/reduce options. |
react-map-gl-supercluster supports all supercluster options, you can find more information about them there.
FAQ
Why does it cause component re-rendering or why do I get infinite component update loop?
Please be careful with points and map/reduce functions. They always should be memoized.
Why does TypeScript reject my properties type?
Properties types must satisfy Record<string, unknown>. Type aliases get an implicit index signature, interface declarations don't — declare properties types with type, not interface. Also note that the cluster key is reserved for generated clusters and must not appear in point properties.
Does it support WebWorker?
No, the hook is running in the main thread. But probably WebWorker support will come in the future.
