React.js Google Maps API integration with components and hooks for seamless Google Maps functionality
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Components for grouping nearby markers into clusters to improve performance and user experience with large marker datasets. Includes both community and official Google clustering implementations, plus direct access to the Google Maps MarkerClusterer library.
Community-driven marker clustering component that groups nearby markers into clusters for better performance and visual clarity.
/**
* Groups nearby markers into clusters for better performance
* Community implementation with extensive customization options
*/
interface MarkerClustererProps {
children: React.ReactNode;
options?: ClustererOptions;
// Event handlers
onClusteringBegin?: (clusterer: Clusterer) => void;
onClusteringEnd?: (clusterer: Clusterer) => void;
onClick?: (cluster: Cluster) => void;
onMouseOver?: (cluster: Cluster) => void;
onMouseOut?: (cluster: Cluster) => void;
// Lifecycle events
onLoad?: (clusterer: Clusterer) => void;
onUnmount?: (clusterer: Clusterer) => void;
}
interface ClustererOptions {
gridSize?: number;
maxZoom?: number;
zoomOnClick?: boolean;
averageCenter?: boolean;
minimumClusterSize?: number;
styles?: ClusterIconStyle[];
calculator?: (markers: google.maps.Marker[], numStyles: number) => ClusterIconInfo;
ignoreHidden?: boolean;
enableRetinaIcons?: boolean;
imageExtension?: string;
imagePath?: string;
imageSizes?: number[];
title?: string;
}
function MarkerClusterer(props: MarkerClustererProps): JSX.Element;
function MarkerClustererF(props: MarkerClustererProps): JSX.Element;
// Community clusterer type definitions
interface Clusterer {
setAverageCenter(averageCenter: boolean): void;
setBatchSizeIE(batchSizeIE: number): void;
setCalculator(calculator: TCalculator): void;
setClusterClass(clusterClass: string): void;
setEnableRetinaIcons(enableRetinaIcons: boolean): void;
setGridSize(gridSize: number): void;
setIgnoreHidden(ignoreHidden: boolean): void;
setImageExtension(imageExtension: string): void;
setImagePath(imagePath: string): void;
setImageSizes(imageSizes: number[]): void;
setMaxZoom(maxZoom: number): void;
setMinimumClusterSize(minimumClusterSize: number): void;
setStyles(styles: ClusterIconStyle[]): void;
setTitle(title: string): void;
setZoomOnClick(zoomOnClick: boolean): void;
addMarker(marker: google.maps.Marker, nodraw?: boolean): void;
addMarkers(markers: google.maps.Marker[], nodraw?: boolean): void;
clearMarkers(): void;
getCalculator(): TCalculator;
getGridSize(): number;
getMap(): google.maps.Map;
getMarkers(): google.maps.Marker[];
getMaxZoom(): number;
getStyles(): ClusterIconStyle[];
getTotalClusters(): number;
getTotalMarkers(): number;
}
interface Cluster {
getCenter(): google.maps.LatLng;
getSize(): number;
getMarkers(): google.maps.Marker[];
}
interface ClusterIconInfo {
text: string;
index: number;
title?: string;
}
type TCalculator = (markers: google.maps.Marker[], numStyles: number) => ClusterIconInfo;Usage Examples:
import React, { useState, useMemo } from 'react';
import { GoogleMap, LoadScript, Marker, MarkerClusterer } from '@react-google-maps/api';
// Basic marker clustering
function BasicMarkerClustering() {
const markers = useMemo(() => {
const locations = [];
const center = { lat: 40.7128, lng: -74.0060 };
// Generate random markers around NYC
for (let i = 0; i < 100; i++) {
locations.push({
id: i,
position: {
lat: center.lat + (Math.random() - 0.5) * 0.2,
lng: center.lng + (Math.random() - 0.5) * 0.2
}
});
}
return locations;
}, []);
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap
center={{ lat: 40.7128, lng: -74.0060 }}
zoom={10}
mapContainerStyle={{ width: '100%', height: '400px' }}
>
<MarkerClusterer>
{markers.map(marker => (
<Marker
key={marker.id}
position={marker.position}
/>
))}
</MarkerClusterer>
</GoogleMap>
</LoadScript>
);
}
// Custom clustering with styled clusters
function CustomStyledClustering() {
const [clusterSize, setClusterSize] = useState(40);
const markers = useMemo(() => {
const locations = [];
const center = { lat: 40.7128, lng: -74.0060 };
for (let i = 0; i < 200; i++) {
locations.push({
id: i,
position: {
lat: center.lat + (Math.random() - 0.5) * 0.3,
lng: center.lng + (Math.random() - 0.5) * 0.3
},
title: `Marker ${i + 1}`
});
}
return locations;
}, []);
const clusterOptions = {
gridSize: clusterSize,
styles: [
{
textColor: 'white',
url: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m1.png',
height: 53,
width: 53
},
{
textColor: 'white',
url: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m2.png',
height: 56,
width: 56
},
{
textColor: 'white',
url: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m3.png',
height: 66,
width: 66
}
],
maxZoom: 15,
zoomOnClick: true,
averageCenter: true,
minimumClusterSize: 2
};
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<div>
<div style={{ padding: '10px', background: '#f0f0f0' }}>
<label>
Cluster Grid Size: {clusterSize}
<input
type="range"
min="20"
max="100"
value={clusterSize}
onChange={(e) => setClusterSize(parseInt(e.target.value))}
style={{ marginLeft: '10px' }}
/>
</label>
</div>
<GoogleMap
center={{ lat: 40.7128, lng: -74.0060 }}
zoom={10}
mapContainerStyle={{ width: '100%', height: '400px' }}
>
<MarkerClusterer
options={clusterOptions}
onLoad={(clusterer) => console.log('Clusterer loaded')}
onClick={(cluster) => console.log('Cluster clicked:', cluster.getSize())}
>
{markers.map(marker => (
<Marker
key={marker.id}
position={marker.position}
title={marker.title}
/>
))}
</MarkerClusterer>
</GoogleMap>
</div>
</LoadScript>
);
}
// Advanced clustering with custom calculator
function AdvancedClustering() {
const [showClustering, setShowClustering] = useState(true);
const markers = useMemo(() => {
// Generate markers with different categories
const categories = ['restaurant', 'hotel', 'attraction', 'shop'];
const locations = [];
const center = { lat: 40.7128, lng: -74.0060 };
for (let i = 0; i < 150; i++) {
locations.push({
id: i,
position: {
lat: center.lat + (Math.random() - 0.5) * 0.25,
lng: center.lng + (Math.random() - 0.5) * 0.25
},
category: categories[Math.floor(Math.random() * categories.length)],
rating: Math.floor(Math.random() * 5) + 1
});
}
return locations;
}, []);
const customCalculator = (markers: google.maps.Marker[], numStyles: number) => {
let index = 0;
const count = markers.length;
let dv = count;
while (dv !== 0) {
dv = Math.floor(dv / 10);
index++;
}
index = Math.min(index, numStyles);
return {
text: count.toString(),
index: index,
title: `Cluster of ${count} markers`
};
};
const clusterStyles = [
{
textColor: 'white',
textSize: 12,
url: 'data:image/svg+xml;base64,' + btoa(`
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40">
<circle cx="20" cy="20" r="18" fill="#ff6b6b" stroke="white" stroke-width="2"/>
</svg>
`),
height: 40,
width: 40
},
{
textColor: 'white',
textSize: 14,
url: 'data:image/svg+xml;base64,' + btoa(`
<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50">
<circle cx="25" cy="25" r="23" fill="#4285f4" stroke="white" stroke-width="2"/>
</svg>
`),
height: 50,
width: 50
},
{
textColor: 'white',
textSize: 16,
url: 'data:image/svg+xml;base64,' + btoa(`
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60">
<circle cx="30" cy="30" r="28" fill="#34a853" stroke="white" stroke-width="2"/>
</svg>
`),
height: 60,
width: 60
}
];
const clusterOptions = {
styles: clusterStyles,
calculator: customCalculator,
gridSize: 50,
maxZoom: 14,
zoomOnClick: true,
averageCenter: true
};
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<div>
<div style={{ padding: '10px', background: '#f0f0f0' }}>
<label>
<input
type="checkbox"
checked={showClustering}
onChange={(e) => setShowClustering(e.target.checked)}
/>
Enable Clustering ({markers.length} markers)
</label>
</div>
<GoogleMap
center={{ lat: 40.7128, lng: -74.0060 }}
zoom={11}
mapContainerStyle={{ width: '100%', height: '400px' }}
>
{showClustering ? (
<MarkerClusterer options={clusterOptions}>
{markers.map(marker => (
<Marker
key={marker.id}
position={marker.position}
title={`${marker.category} (Rating: ${marker.rating})`}
icon={`https://maps.google.com/mapfiles/ms/icons/${
marker.category === 'restaurant' ? 'red' :
marker.category === 'hotel' ? 'blue' :
marker.category === 'attraction' ? 'green' : 'yellow'
}-dot.png`}
/>
))}
</MarkerClusterer>
) : (
markers.map(marker => (
<Marker
key={marker.id}
position={marker.position}
title={`${marker.category} (Rating: ${marker.rating})`}
icon={`https://maps.google.com/mapfiles/ms/icons/${
marker.category === 'restaurant' ? 'red' :
marker.category === 'hotel' ? 'blue' :
marker.category === 'attraction' ? 'green' : 'yellow'
}-dot.png`}
/>
))
)}
</GoogleMap>
</div>
</LoadScript>
);
}Functional component variant of MarkerClusterer that uses React hooks internally.
/**
* Functional variant of MarkerClusterer component using hooks internally
*/
function MarkerClustererF(props: MarkerClustererProps): JSX.Element;Official Google Maps marker clustering implementation with enhanced performance and features.
/**
* Official Google marker clustering implementation
* Enhanced performance and features compared to community version
*/
interface GoogleMarkerClustererProps {
children: React.ReactNode;
options?: google.maps.MarkerClustererOptions;
// Event handlers
onClusterClick?: (event: google.maps.MapMouseEvent, cluster: google.maps.markerclusterer.Cluster, map: google.maps.Map) => void;
// Lifecycle events
onLoad?: (clusterer: google.maps.MarkerClusterer) => void;
onUnmount?: (clusterer: google.maps.MarkerClusterer) => void;
}
interface google.maps.MarkerClustererOptions {
algorithm?: google.maps.markerclusterer.Algorithm;
map?: google.maps.Map;
markers?: google.maps.Marker[];
renderer?: google.maps.markerclusterer.Renderer;
onClusterClick?: (event: google.maps.MapMouseEvent, cluster: google.maps.markerclusterer.Cluster, map: google.maps.Map) => void;
}
function GoogleMarkerClusterer(props: GoogleMarkerClustererProps): JSX.Element;Usage Examples:
import React, { useMemo } from 'react';
import {
GoogleMap,
LoadScript,
Marker,
GoogleMarkerClusterer
} from '@react-google-maps/api';
// Basic Google marker clustering
function BasicGoogleClustering() {
const markers = useMemo(() => {
const locations = [];
const center = { lat: 40.7128, lng: -74.0060 };
for (let i = 0; i < 1000; i++) {
locations.push({
id: i,
position: {
lat: center.lat + (Math.random() - 0.5) * 0.5,
lng: center.lng + (Math.random() - 0.5) * 0.5
}
});
}
return locations;
}, []);
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap
center={{ lat: 40.7128, lng: -74.0060 }}
zoom={9}
mapContainerStyle={{ width: '100%', height: '400px' }}
>
<GoogleMarkerClusterer
onLoad={(clusterer) => console.log('Google clusterer loaded')}
onClusterClick={(event, cluster, map) => {
console.log('Cluster clicked:', cluster.count);
map.fitBounds(cluster.bounds);
}}
>
{markers.map(marker => (
<Marker
key={marker.id}
position={marker.position}
/>
))}
</GoogleMarkerClusterer>
</GoogleMap>
</LoadScript>
);
}
// Google clustering with custom renderer
function CustomGoogleClustering() {
const markers = useMemo(() => {
const locations = [];
const center = { lat: 40.7128, lng: -74.0060 };
for (let i = 0; i < 500; i++) {
locations.push({
id: i,
position: {
lat: center.lat + (Math.random() - 0.5) * 0.4,
lng: center.lng + (Math.random() - 0.5) * 0.4
},
weight: Math.floor(Math.random() * 10) + 1
});
}
return locations;
}, []);
// Custom renderer for cluster styling
const createCustomRenderer = () => {
return {
render: ({ count, position }: { count: number; position: google.maps.LatLng }) => {
const color = count > 100 ? '#ea4335' : count > 50 ? '#fbbc05' : '#34a853';
const size = count > 100 ? 60 : count > 50 ? 50 : 40;
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 ${size} ${size}">
<circle cx="${size/2}" cy="${size/2}" r="${size/2 - 2}" fill="${color}" stroke="white" stroke-width="2"/>
<text x="${size/2}" y="${size/2}" text-anchor="middle" dy="0.3em" font-family="Arial" font-size="12" font-weight="bold" fill="white">
${count}
</text>
</svg>
`;
return new google.maps.Marker({
position,
icon: {
url: `data:image/svg+xml;base64,${btoa(svg)}`,
scaledSize: new google.maps.Size(size, size),
anchor: new google.maps.Point(size / 2, size / 2)
},
label: {
text: count.toString(),
color: 'white',
fontSize: '12px',
fontWeight: 'bold'
},
zIndex: 1000 + count
});
}
};
};
const clusterOptions = {
renderer: createCustomRenderer(),
// algorithm: new google.maps.markerclusterer.SuperClusterAlgorithm({
// radius: 100,
// maxZoom: 16
// })
};
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<GoogleMap
center={{ lat: 40.7128, lng: -74.0060 }}
zoom={10}
mapContainerStyle={{ width: '100%', height: '400px' }}
>
<GoogleMarkerClusterer
options={clusterOptions}
onLoad={(clusterer) => console.log('Custom Google clusterer loaded')}
onClusterClick={(event, cluster, map) => {
console.log('Custom cluster clicked:', cluster);
}}
>
{markers.map(marker => (
<Marker
key={marker.id}
position={marker.position}
title={`Weight: ${marker.weight}`}
/>
))}
</GoogleMarkerClusterer>
</GoogleMap>
</LoadScript>
);
}Performance comparison and recommendations for choosing between clustering implementations.
/**
* Performance characteristics of different clustering approaches
*/
interface ClusteringPerformanceMetrics {
markerCount: number;
renderTime: number;
memoryUsage: number;
interactionLatency: number;
}
// Performance testing utility
const measureClusteringPerformance = (
clusteringComponent: React.ReactElement,
markerCount: number
): Promise<ClusteringPerformanceMetrics> => {
// Implementation would measure actual performance metrics
return Promise.resolve({
markerCount,
renderTime: 0,
memoryUsage: 0,
interactionLatency: 0
});
};Performance Comparison Examples:
// Side-by-side clustering comparison
function ClusteringComparison() {
const [markerCount, setMarkerCount] = useState(100);
const [activeMethod, setActiveMethod] = useState<'community' | 'google'>('community');
const markers = useMemo(() => {
const locations = [];
const center = { lat: 40.7128, lng: -74.0060 };
for (let i = 0; i < markerCount; i++) {
locations.push({
id: i,
position: {
lat: center.lat + (Math.random() - 0.5) * 0.3,
lng: center.lng + (Math.random() - 0.5) * 0.3
}
});
}
return locations;
}, [markerCount]);
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<div>
<div style={{ padding: '10px', background: '#f0f0f0' }}>
<div style={{ marginBottom: '10px' }}>
<label>
Marker Count: {markerCount}
<input
type="range"
min="50"
max="2000"
step="50"
value={markerCount}
onChange={(e) => setMarkerCount(parseInt(e.target.value))}
style={{ marginLeft: '10px', width: '200px' }}
/>
</label>
</div>
<div>
<label style={{ marginRight: '20px' }}>
<input
type="radio"
value="community"
checked={activeMethod === 'community'}
onChange={(e) => setActiveMethod(e.target.value as 'community')}
/>
Community Clusterer
</label>
<label>
<input
type="radio"
value="google"
checked={activeMethod === 'google'}
onChange={(e) => setActiveMethod(e.target.value as 'google')}
/>
Google Clusterer
</label>
</div>
<div style={{ marginTop: '10px', fontSize: '12px', color: '#666' }}>
Recommendations:
<ul style={{ margin: '5px 0', paddingLeft: '20px' }}>
<li>Community: Better customization, lighter weight (<1000 markers)</li>
<li>Google: Better performance, official support (>1000 markers)</li>
</ul>
</div>
</div>
<GoogleMap
center={{ lat: 40.7128, lng: -74.0060 }}
zoom={10}
mapContainerStyle={{ width: '100%', height: '400px' }}
>
{activeMethod === 'community' ? (
<MarkerClusterer
options={{
gridSize: 60,
maxZoom: 15,
zoomOnClick: true
}}
>
{markers.map(marker => (
<Marker key={marker.id} position={marker.position} />
))}
</MarkerClusterer>
) : (
<GoogleMarkerClusterer>
{markers.map(marker => (
<Marker key={marker.id} position={marker.position} />
))}
</GoogleMarkerClusterer>
)}
</GoogleMap>
</div>
</LoadScript>
);
}
// Clustering with dynamic data loading
function DynamicClusteringExample() {
const [markers, setMarkers] = useState<Array<{id: number, position: google.maps.LatLngLiteral}>>([]);
const [isLoading, setIsLoading] = useState(false);
const loadMoreMarkers = async (count: number) => {
setIsLoading(true);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
const newMarkers = [];
const center = { lat: 40.7128, lng: -74.0060 };
const startId = markers.length;
for (let i = 0; i < count; i++) {
newMarkers.push({
id: startId + i,
position: {
lat: center.lat + (Math.random() - 0.5) * 0.5,
lng: center.lng + (Math.random() - 0.5) * 0.5
}
});
}
setMarkers(prev => [...prev, ...newMarkers]);
setIsLoading(false);
};
const clearMarkers = () => {
setMarkers([]);
};
return (
<LoadScript googleMapsApiKey="YOUR_API_KEY">
<div>
<div style={{ padding: '10px', background: '#f0f0f0' }}>
<div>Markers: {markers.length}</div>
<button
onClick={() => loadMoreMarkers(100)}
disabled={isLoading}
style={{ marginRight: '10px' }}
>
{isLoading ? 'Loading...' : 'Add 100 Markers'}
</button>
<button
onClick={() => loadMoreMarkers(500)}
disabled={isLoading}
style={{ marginRight: '10px' }}
>
{isLoading ? 'Loading...' : 'Add 500 Markers'}
</button>
<button onClick={clearMarkers}>
Clear All
</button>
</div>
<GoogleMap
center={{ lat: 40.7128, lng: -74.0060 }}
zoom={9}
mapContainerStyle={{ width: '100%', height: '400px' }}
>
<GoogleMarkerClusterer
onLoad={(clusterer) => {
console.log('Clusterer ready for', markers.length, 'markers');
}}
>
{markers.map(marker => (
<Marker
key={marker.id}
position={marker.position}
title={`Marker ${marker.id}`}
/>
))}
</GoogleMarkerClusterer>
</GoogleMap>
</div>
</LoadScript>
);
}Guidelines for optimal clustering implementation and performance.
/**
* Best practices for marker clustering implementation
*/
interface ClusteringBestPractices {
// Performance optimization
maxMarkersBeforeClustering: number; // ~500-1000 markers
optimalGridSize: number; // 40-80 pixels
maxZoomForClustering: number; // 15-17
// Visual design
clusterIconSizes: number[]; // [40, 50, 60] progressive sizes
colorScheme: string[]; // Consistent color progression
textVisibility: boolean; // Always show count numbers
// Interaction design
zoomOnClusterClick: boolean; // Usually true
spiderfyOnClick: boolean; // For overlapping markers
showClusterBounds: boolean; // Debug/development only
}
// Example optimized clustering configuration
const optimizedClusterConfig = {
community: {
gridSize: 60,
maxZoom: 15,
zoomOnClick: true,
averageCenter: true,
minimumClusterSize: 2,
styles: [
{ textColor: 'white', url: '/cluster-small.png', height: 40, width: 40 },
{ textColor: 'white', url: '/cluster-medium.png', height: 50, width: 50 },
{ textColor: 'white', url: '/cluster-large.png', height: 60, width: 60 }
]
},
google: {
// Use default algorithm for best performance
// Customize renderer for visual consistency
}
};Direct access to the official Google Maps MarkerClusterer library with all its classes, algorithms, and renderers.
Complete namespace re-export providing access to the official @googlemaps/markerclusterer library components.
/**
* Complete namespace export of @googlemaps/markerclusterer
* Provides direct access to Google's official clustering library
*/
import { GoogleMapsMarkerClusterer } from "@react-google-maps/api";
// Access to core MarkerClusterer class
class MarkerClusterer {
constructor(options: {
algorithm?: Algorithm;
map?: google.maps.Map;
markers?: google.maps.Marker[];
renderer?: Renderer;
onClusterClick?: onClusterClickHandler;
});
addMarker(marker: google.maps.Marker, noDraw?: boolean): void;
addMarkers(markers: google.maps.Marker[], noDraw?: boolean): void;
clearMarkers(noDraw?: boolean): void;
render(): void;
setMap(map: google.maps.Map | null): void;
}
// Access to clustering algorithms
interface Algorithm {
calculate(request: AlgorithmInput): AlgorithmOutput;
}
// Access to cluster renderers
interface Renderer {
render(cluster: Cluster, stats: ClusterStats): google.maps.Marker;
}
// Cluster statistics for renderer
interface ClusterStats {
count: number;
markers: google.maps.Marker[];
}
// Event handler types
type onClusterClickHandler = (
event: google.maps.MapMouseEvent,
cluster: Cluster,
map: google.maps.Map
) => void;Usage Example:
import { GoogleMapsMarkerClusterer } from "@react-google-maps/api";
// Direct instantiation of Google's MarkerClusterer
function MyAdvancedClusteringComponent() {
const map = useGoogleMap();
React.useEffect(() => {
if (map && markers.length > 0) {
const markerClusterer = new GoogleMapsMarkerClusterer.MarkerClusterer({
map,
markers,
onClusterClick: (event, cluster, map) => {
map.fitBounds(cluster.bounds!);
}
});
return () => markerClusterer.setMap(null);
}
}, [map, markers]);
return null;
}Install with Tessl CLI
npx tessl i tessl/npm-react-google-maps--api