Skip to content

Commit f0febab

Browse files
⚡ Bolt: Optimize Leaflet marker updates
- Avoided recreating all map markers when focusedProfileId changes - Optimized using an O(1) `.setIcon()` imperative update instead of an O(N) re-render - Added learning to `.jules/bolt.md` Co-authored-by: KxlSys <116387953+KxlSys@users.noreply.github.com>
1 parent eea99a9 commit f0febab

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

.jules/bolt.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 2024-05-28 - Optimize Leaflet Marker Updates
2+
**Learning:** React Leaflet implementations often recreate all markers when a single property like `focusedProfileId` changes if dependencies are broad.
3+
**Action:** When working with Leaflet markers in React, avoid putting the focused ID in the main layer creation dependency array. Instead, use a secondary `useEffect` to find the specific Leaflet marker instance and update its icon or state directly. This turns an O(N) DOM operation (recreating hundreds of markers) into an O(1) operation (updating just 1-2 markers), vastly improving interaction performance on maps with many points.

src/components/map/congo-map.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ export const CongoMap = React.memo(function CongoMap({ profiles, onProfileClick,
196196
const marker = L.marker([profile.latitude, profile.longitude], {
197197
icon: createMarkerIcon(profile.open_to_collaboration, profile.id === focusedProfileId),
198198
});
199+
(marker as any)._isCollaborating = profile.open_to_collaboration;
199200

200201
const safeName = escapeHtml(profile.full_name);
201202
const safeCity = escapeHtml(profile.city);
@@ -237,11 +238,34 @@ export const CongoMap = React.memo(function CongoMap({ profiles, onProfileClick,
237238
markersRef.current!.addLayer(marker);
238239
markersMapRef.current.set(profile.id, marker);
239240
});
240-
}, [profiles, onProfileClick, focusedProfileId]);
241+
// eslint-disable-next-line react-hooks/exhaustive-deps
242+
}, [profiles, onProfileClick]); // Omit focusedProfileId to prevent recreating all markers on focus
243+
244+
const prevFocusedIdRef = useRef<string | undefined>(undefined);
241245

242246
useEffect(() => {
243247
const map = mapRef.current;
244248
const cluster = markersRef.current;
249+
250+
// ⚡ Bolt: Efficiently update only the changed marker icons instead of recreating all markers
251+
if (prevFocusedIdRef.current && prevFocusedIdRef.current !== focusedProfileId) {
252+
const prevMarker = markersMapRef.current.get(prevFocusedIdRef.current);
253+
if (prevMarker) {
254+
const isCollab = (prevMarker as any)._isCollaborating;
255+
prevMarker.setIcon(createMarkerIcon(!!isCollab, false));
256+
}
257+
}
258+
259+
if (focusedProfileId) {
260+
const newMarker = markersMapRef.current.get(focusedProfileId);
261+
if (newMarker) {
262+
const isCollab = (newMarker as any)._isCollaborating;
263+
newMarker.setIcon(createMarkerIcon(!!isCollab, true));
264+
}
265+
}
266+
267+
prevFocusedIdRef.current = focusedProfileId;
268+
245269
if (!focusedProfileId || !map || !cluster) return;
246270

247271
const marker = markersMapRef.current.get(focusedProfileId);

0 commit comments

Comments
 (0)