Code example
import { useMemo, useState } from 'react';
import {
ColorDefaultIcon,
createGeoPoint,
createGeoRectBounds,
createMapCameraPosition,
createMarkerState,
type MarkerState,
} from '@mapconductor/js-sdk-core';
import { InfoBubble } from '@mapconductor/js-sdk-react';
import { GoogleMapDesign, GoogleMapView2D, useGoogleMapViewState } from '@mapconductor/react-for-googlemaps';
import { MarkerClusterGroup, type MarkerCluster } from '@mapconductor/react-marker-clustering';
export default function App() {
// (1) Create the map
const initialCamera = createMapCameraPosition({
position: createGeoPoint({ latitude: 43.05, longitude: 141.31 }),
zoom: 11,
});
const mapViewState = useGoogleMapViewState({
apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
mapDesignType: GoogleMapDesign.Normal,
cameraPosition: initialCamera,
});
// (2) Prepare the data and interaction state
const postOffices = useMemo(() => [
{ id: 'maruyama', lat: 43.043751, lng: 141.305428, name: '円山西町簡易郵便局' },
{ id: 'jingu', lat: 43.057305, lng: 141.311483, name: '北海道神宮前郵便局' },
{ id: 'miyanomori', lat: 43.057305, lng: 141.303150, name: '札幌宮の森郵便局' },
{ id: 'araiyama', lat: 43.050833, lng: 141.298262, name: '札幌荒井山簡易郵便局' },
{ id: 'futagoyama', lat: 43.043085, lng: 141.318733, name: '札幌双子山郵便局' },
], []);
const [selected, setSelected] = useState<MarkerState | null>(null);
const postOfficeMarkers = useMemo(() => postOffices.map(office => createMarkerState({
id: office.id,
position: createGeoPoint({ latitude: office.lat, longitude: office.lng }),
extra: office.name,
onClick: markerState => setSelected(markerState),
})), [postOffices]);
const clusterIconProvider = (count: number) => new ColorDefaultIcon({
fillColor: '#dc2626', label: String(count), labelTextColor: '#ffffff',
});
const zoomToCluster = (cluster: MarkerCluster) => {
const bounds = createGeoRectBounds();
postOfficeMarkers.filter(marker => cluster.markerIds.includes(marker.id))
.forEach(marker => bounds.extend(marker.position));
mapViewState.fitBounds(bounds, 60);
};
// (3) Render the map and its overlays
return (
<div style={{ height: 500, position: 'relative', display: 'flex', flexDirection: 'column' }}>
<GoogleMapView2D mapId="DEMO_MAP_ID" state={mapViewState} onMapClick={() => setSelected(null)}>
<MarkerClusterGroup
markers={postOfficeMarkers}
clusterIconProvider={clusterIconProvider}
onClusterClick={zoomToCluster}
minClusterSize={3}
clusterRadiusPx={80}
/>
{selected && <InfoBubble marker={selected}>{selected.extra as string}</InfoBubble>}
</GoogleMapView2D>
</div>
);
}
How the code works
Cluster a large marker collection and provide a custom cluster icon and click behavior through the extension API.
The same postOfficeMarkers array feeds MarkerClusterGroup instead of <Markers>, so nearby offices are grouped automatically as the map zooms out.
clusterIconProvider draws the cluster badge, onClusterClick runs zoomToCluster to open a group, and minClusterSize with clusterRadiusPx tune how aggressively points merge.