Skip to content

Commit ed7650f

Browse files
committed
feat: add MarkerClusterer
# Conflicts: # pnpm-lock.yaml
1 parent 5c98d25 commit ed7650f

8 files changed

Lines changed: 202 additions & 57 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Google map react componentize project
1515
- [x] Rectangle
1616
- [x] Circle
1717
- [ ] AdvancedMarkerView
18-
- [ ] MarkerClusterer
18+
- [x] MarkerClusterer
1919
- [ ] HeatmapLayer
2020

2121
## DEMO

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"@babel/plugin-syntax-typescript": "^7.22.5",
4747
"@babel/types": "^7.22.19",
4848
"@googlemaps/js-api-loader": "^1.16.2",
49+
"@googlemaps/markerclusterer": "^2.5.1",
4950
"@types/google.maps": "^3.54.6",
5051
"react": "^18.2.0",
5152
"react-dom": "^18.2.0",

pnpm-lock.yaml

Lines changed: 74 additions & 54 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/InfoWindow/Context.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export interface AnchorContextData {
55
}
66

77
export const AnchorContext = createContext<AnchorContextData>({
8-
setAnchor: () => void 0,
8+
setAnchor: () => {},
99
});
1010

1111
export const useSetAnchor = () => useContext(AnchorContext).setAnchor;

src/components/Marker/index.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { forwardRef, useEffect, useState } from 'react';
1+
import { forwardRef, useEffect, useRef, useState } from 'react';
22

33
import { MarkerProps } from './type';
44
import { useApplyMarkerEvent } from '../../hooks/useApplyMarkerEvent';
55
import { useImportLibrary } from '../../hooks/useImportLibrary';
66
import { passRef } from '../../utils/passRef';
77
import { useSetAnchor } from '../InfoWindow/Context';
88
import { useMapContext } from '../Provider/MapProvider';
9+
import { useMarkerClusterer } from '../MarkerClusterer/Context';
910

1011
export const Marker = forwardRef<google.maps.Marker, MarkerProps>(
1112
function Marker(
@@ -41,6 +42,8 @@ export const Marker = forwardRef<google.maps.Marker, MarkerProps>(
4142
const map = useMapContext();
4243
const markerLib = useImportLibrary('marker');
4344
const setAnchor = useSetAnchor();
45+
const cluster = useMarkerClusterer();
46+
const isFirstMount = useRef(true);
4447

4548
const [marker, setMarker] = useState<google.maps.Marker | null>(null);
4649

@@ -61,13 +64,20 @@ export const Marker = forwardRef<google.maps.Marker, MarkerProps>(
6164

6265
// for InfoWindow
6366
setAnchor(marker);
67+
// for MarkerClusterer
68+
cluster?.addMarker(marker);
6469

6570
return () => {
6671
marker?.setMap(null);
6772
setAnchor(null);
73+
cluster?.removeMarker(marker);
6874
};
6975
}, [markerLib?.Marker]);
7076

77+
useEffect(() => {
78+
marker?.setMap(cluster ? null : map);
79+
}, [cluster]);
80+
7181
useEffect(() => {
7282
marker?.setPosition({ lat, lng });
7383
}, [lat, lng]);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { ComponentProps, createContext, useContext } from 'react';
2+
3+
export interface MarkerClustererContextData {
4+
addMarker: (marker: google.maps.Marker) => void;
5+
removeMarker: (marker: google.maps.Marker) => void;
6+
}
7+
8+
const initialContextData = {
9+
addMarker: () => {
10+
throw Error(
11+
'You must use `useMarkerClusterer` inside of `MarkerClustererProvideer`',
12+
);
13+
},
14+
removeMarker: () => {
15+
throw Error(
16+
'You must use `useMarkerClusterer` inside of `MarkerClustererProvideer`',
17+
);
18+
},
19+
};
20+
21+
export const MarkerClustererContext =
22+
createContext<MarkerClustererContextData>(initialContextData);
23+
24+
export const useMarkerClusterer = () => {
25+
const data = useContext(MarkerClustererContext);
26+
27+
if (data === initialContextData) {
28+
return null;
29+
}
30+
31+
return data;
32+
};
33+
34+
export function MarkerClustererProvider(
35+
props: ComponentProps<typeof MarkerClustererContext.Provider>,
36+
) {
37+
return <MarkerClustererContext.Provider {...props} />;
38+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
2+
3+
import {
4+
MarkerClusterer as MarkerClustererClass,
5+
MarkerClustererOptions,
6+
} from '@googlemaps/markerclusterer';
7+
8+
import { passRef } from '../../utils/passRef';
9+
import { useMapContext } from '../Provider/MapProvider';
10+
import { MarkerClustererProps } from './type';
11+
import { MarkerClustererProvider } from './Context';
12+
13+
const createMarkerClusterer = (options: MarkerClustererOptions) =>
14+
new MarkerClustererClass(options);
15+
16+
export const MarkerClusterer = forwardRef<
17+
MarkerClustererClass,
18+
MarkerClustererProps
19+
>(function MarkerClusterer(
20+
{ children, algorithmOptions, algorithm, renderer, onClusterClick },
21+
ref,
22+
) {
23+
const map = useMapContext();
24+
const markers = useRef(
25+
new Set<google.maps.Marker | google.maps.marker.AdvancedMarkerElement>(),
26+
);
27+
const markerCluster = useRef(
28+
createMarkerClusterer({
29+
map,
30+
algorithmOptions,
31+
algorithm,
32+
renderer,
33+
onClusterClick,
34+
}),
35+
);
36+
37+
const value = useMemo(
38+
() => ({
39+
addMarker: (marker) => (
40+
markers.current.add(marker), markerCluster.current.addMarker(marker)
41+
),
42+
removeMarker: (marker) => (
43+
markers.current.delete(marker),
44+
markerCluster.current.removeMarker(marker)
45+
),
46+
}),
47+
[],
48+
);
49+
50+
useEffect(() => {
51+
passRef(ref, markerCluster.current);
52+
}, []);
53+
54+
useEffect(() => {
55+
markerCluster.current.setMap(null);
56+
markerCluster.current = createMarkerClusterer({
57+
map,
58+
markers: Array.from(markers.current),
59+
algorithmOptions,
60+
algorithm,
61+
renderer,
62+
onClusterClick,
63+
});
64+
}, [algorithmOptions, algorithm, renderer, onClusterClick]);
65+
66+
return (
67+
<MarkerClustererProvider value={value}>{children}</MarkerClustererProvider>
68+
);
69+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { MarkerClustererOptions } from '@googlemaps/markerclusterer';
2+
import { ReactNode } from 'react';
3+
4+
export interface MarkerClustererProps
5+
extends Omit<MarkerClustererOptions, 'markers' | 'map' | ''> {
6+
children?: ReactNode;
7+
}

0 commit comments

Comments
 (0)