Skip to content

Commit 1c39128

Browse files
Fix Popup and GeolocateControl under React StrictMode (#1836)
1 parent 048cb20 commit 1c39128

8 files changed

Lines changed: 37 additions & 16 deletions

File tree

docs/api-reference/popup.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ CSS style override that applies to the popup's container.
9191

9292
#### `onOpen`: (evt: [PopupEvent](/docs/api-reference/types.md#popupevent)) => void
9393

94-
Called when the popup is opened manually or programatically.
94+
Called when the popup is opened.
9595

9696
#### `onClose`: (evt: [PopupEvent](/docs/api-reference/types.md#popupevent)) => void
9797

98-
Called when the popup is closed manually or programatically.
98+
Called when the popup is closed by the user clicking on the close button or outside (if `closeOnClick: true`).
9999

100100

101101
## Source

examples/controls/src/app.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@ export default function App() {
2828
longitude={city.longitude}
2929
latitude={city.latitude}
3030
anchor="bottom"
31+
onClick={e => {
32+
// If we let the click event propagates to the map, it will immediately close the popup
33+
// with `closeOnClick: true`
34+
e.originalEvent.stopPropagation();
35+
setPopupInfo(city);
36+
}}
3137
>
32-
<Pin onClick={() => setPopupInfo(city)} />
38+
<Pin />
3339
</Marker>
3440
)),
3541
[]
@@ -60,7 +66,6 @@ export default function App() {
6066
anchor="top"
6167
longitude={Number(popupInfo.longitude)}
6268
latitude={Number(popupInfo.latitude)}
63-
closeOnClick={false}
6469
onClose={() => setPopupInfo(null)}
6570
>
6671
<div>

examples/controls/src/pin.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ const pinStyle = {
1010
stroke: 'none'
1111
};
1212

13-
function Pin({size = 20, onClick}: {size?: number; onClick?: () => void}) {
13+
function Pin({size = 20}) {
1414
return (
15-
<svg height={size} viewBox="0 0 24 24" style={pinStyle} onClick={onClick}>
15+
<svg height={size} viewBox="0 0 24 24" style={pinStyle}>
1616
<path d={ICON} />
1717
</svg>
1818
);

examples/webpack.config.local.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const LOCAL_DEVELOPMENT_CONFIG = {
2828
alias: {
2929
// Imports the react-map-gl library from the src directory in this repo
3030
'react-map-gl': SRC_DIR,
31-
'../utils/mapboxgl': resolve(LIB_DIR, './node_modules/mapbox-gl/dist/mapbox-gl-dev.js'),
31+
'mapbox-gl': resolve(LIB_DIR, './node_modules/mapbox-gl/dist/mapbox-gl-dev.js'),
3232
react: resolve(LIB_DIR, './node_modules/react')
3333
}
3434
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"babel-loader": "^8.0.0",
5454
"coveralls": "^3.0.0",
5555
"jsdom": "^15.0.0",
56-
"mapbox-gl": "^2.1.0",
56+
"mapbox-gl": "^2.8.0",
5757
"ocular-dev-tools": "beta",
5858
"pre-commit": "^1.2.2",
5959
"react": "^17.0.0",

src/components/geolocate-control.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ const GeolocateControl = forwardRef<GeolocateControlRef, GeolocateControlProps>(
7474
({mapLib}) => {
7575
const gc = new mapLib.GeolocateControl(props);
7676

77+
// Hack: fix GeolocateControl reuse
78+
// When using React strict mode, the component is mounted twice.
79+
// GeolocateControl's UI creation is asynchronous. Removing and adding it back causes the UI to be initialized twice.
80+
const setupUI = gc._setupUI;
81+
gc._setupUI = args => {
82+
if (!gc._container.hasChildNodes()) {
83+
setupUI(args);
84+
}
85+
};
86+
7787
gc.on('geolocate', e => {
7888
thisRef.current.props.onGeolocate?.(e as GeolocateResultEvent);
7989
});

src/components/popup.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,19 +81,25 @@ function Popup(props: PopupProps) {
8181
const popup: MapboxPopup = useMemo(() => {
8282
const options = {...props};
8383
const pp = new mapLib.Popup(options).setLngLat([props.longitude, props.latitude]);
84-
pp.on('open', e => {
84+
pp.once('open', e => {
8585
thisRef.current.props.onOpen?.(e as PopupEvent);
8686
});
87-
pp.on('close', e => {
88-
thisRef.current.props.onClose?.(e as PopupEvent);
89-
});
9087
return pp;
9188
}, []);
9289

9390
useEffect(() => {
91+
const onClose = e => {
92+
thisRef.current.props.onClose?.(e as PopupEvent);
93+
};
94+
popup.on('close', onClose);
9495
popup.setDOMContent(container).addTo(map.getMap());
9596

9697
return () => {
98+
// https://github.com/visgl/react-map-gl/issues/1825
99+
// onClose should not be fired if the popup is removed by unmounting
100+
// When using React strict mode, the component is mounted twice.
101+
// Firing the onClose callback here would be a false signal to remove the component.
102+
popup.off('close', onClose);
97103
if (popup.isOpen()) {
98104
popup.remove();
99105
}

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7291,10 +7291,10 @@ map-visit@^1.0.0:
72917291
dependencies:
72927292
object-visit "^1.0.0"
72937293

7294-
mapbox-gl@^2.1.0:
7295-
version "2.6.1"
7296-
resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-2.6.1.tgz#de8aadeb16b157b732d174b51aeaba0223ab71bb"
7297-
integrity sha512-faGbSZfcFuZ4GWwkWnJrRD3oICZAt/mVKnGuOmeBobCj9onfTRz270qSoOXeRBKd3po5VA2cCPI91YwA8DsAoQ==
7294+
mapbox-gl@^2.8.0:
7295+
version "2.8.0"
7296+
resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-2.8.0.tgz#698f028575f202d25f2ccb5d6359761d1affb8e2"
7297+
integrity sha512-sP7TclFDmGhZWfcBlptnA24xO9T4a419W2Y6znGvuRXLkucnGErDIG+/7WLVDrebMYp4E2FtCG+1RtGcYVRIFQ==
72987298
dependencies:
72997299
"@mapbox/geojson-rewind" "^0.5.1"
73007300
"@mapbox/geojson-types" "^1.0.2"

0 commit comments

Comments
 (0)