Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 77 additions & 43 deletions components/map/mapbox-map.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import { useEffect, useRef, useCallback } from 'react' // Removed useState
import { useEffect, useRef, useCallback } from 'react'
import { createRoot } from 'react-dom/client'
import mapboxgl from 'mapbox-gl'
import MapboxDraw from '@mapbox/mapbox-gl-draw'
import * as turf from '@turf/turf'
Expand All @@ -9,16 +10,20 @@ import 'react-toastify/dist/ReactToastify.css'
import 'mapbox-gl/dist/mapbox-gl.css'
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'
import { useMapToggle, MapToggleEnum } from '../map-toggle-context'
import { useMapData } from './map-data-context'; // Add this import
import { useMapLoading } from '../map-loading-context'; // Import useMapLoading
import { useMapData } from './map-data-context'
import { useMapLoading } from '../map-loading-context'
import { useMap } from './map-context'
import UserMarker from './user-marker'

mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string;
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN as string

export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number; } }> = ({ position }) => {
export const Mapbox: React.FC<{
position?: { latitude: number; longitude: number }
}> = ({ position }) => {
const mapContainer = useRef<HTMLDivElement>(null)
const map = useRef<mapboxgl.Map | null>(null)
const { setMap } = useMap()
const userMarkerRef = useRef<mapboxgl.Marker | null>(null)
const drawRef = useRef<MapboxDraw | null>(null)
const rotationFrameRef = useRef<number | null>(null)
const polygonLabelsRef = useRef<{ [id: string]: mapboxgl.Marker }>({})
Expand Down Expand Up @@ -199,39 +204,55 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
}
}, [stopRotation])

const updateMapPosition = useCallback(async (latitude: number, longitude: number) => {
if (map.current && !isUpdatingPositionRef.current) {
isUpdatingPositionRef.current = true
stopRotation()

try {
// Update our current map center ref
currentMapCenterRef.current.center = [longitude, latitude]

await new Promise<void>((resolve) => {
map.current?.flyTo({
center: [longitude, latitude],
zoom: 12,
essential: true,
speed: 0.5,
curve: 1,
})
map.current?.once('moveend', () => {
resolve()
})
})
setTimeout(() => {
if (mapType === MapToggleEnum.RealTimeMode) {
startRotation()
const updateMapPosition = useCallback(
async (latitude: number, longitude: number) => {
if (map.current && !isUpdatingPositionRef.current) {
isUpdatingPositionRef.current = true
stopRotation()

try {
currentMapCenterRef.current.center = [longitude, latitude]

if (userMarkerRef.current) {
userMarkerRef.current.setLngLat([longitude, latitude])
} else {
const markerElement = document.createElement('div')
const root = createRoot(markerElement)
root.render(<UserMarker />)
userMarkerRef.current = new mapboxgl.Marker({
element: markerElement
})
.setLngLat([longitude, latitude])
.addTo(map.current)
}

await new Promise<void>(resolve => {
map.current?.flyTo({
center: [longitude, latitude],
zoom: 12,
essential: true,
speed: 0.5,
curve: 1
})
map.current?.once('moveend', () => {
resolve()
})
})

setTimeout(() => {
if (mapType === MapToggleEnum.RealTimeMode) {
startRotation()
}
isUpdatingPositionRef.current = false
}, 500)
} catch (error) {
console.error('Error updating map position:', error)
isUpdatingPositionRef.current = false
}, 500)
} catch (error) {
console.error('Error updating map position:', error)
isUpdatingPositionRef.current = false
}
Comment on lines +219 to +251

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createRoot is correctly used to render UserMarker into a detached DOM node, but the React root instance is not retained or unmounted. This leaves React tree lifecycle unmanaged and risks memory leaks when the map is destroyed or if the marker is ever replaced, because root.unmount() is never called.

Also, the userMarkerRef only tracks the Mapbox Marker, not the React root, so you cannot currently cleanly unmount the React component during cleanup. This is particularly important in long-lived sessions with route changes or map reinitializations.

Suggestion

Consider storing the React root in a ref alongside the marker and explicitly unmounting it during cleanup when the marker is removed. For example:

const userMarkerRef = useRef<{
  marker: mapboxgl.Marker | null
  root: Root | null
}>({ marker: null, root: null })

Then when creating the marker:

const markerElement = document.createElement('div')
const root = createRoot(markerElement)
root.render(<UserMarker />)
userMarkerRef.current = {
  marker: new mapboxgl.Marker({ element: markerElement })
    .setLngLat([longitude, latitude])
    .addTo(map.current),
  root,
}

And in your cleanup (both in the main useEffect return and the map-style change cleanup), ensure you unmount:

if (userMarkerRef.current?.marker) {
  userMarkerRef.current.marker.remove()
}
if (userMarkerRef.current?.root) {
  userMarkerRef.current.root.unmount()
}
userMarkerRef.current = { marker: null, root: null }

This keeps React and Mapbox lifecycles aligned and avoids orphaned React roots. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion.

}
}
}, [mapType, startRotation, stopRotation])
},
[mapType, startRotation, stopRotation]
)

// Set up drawing tools
const setupDrawingTools = useCallback(() => {
Expand Down Expand Up @@ -462,7 +483,7 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
return () => {
if (map.current) {
map.current.off('moveend', captureMapCenter)

if (drawRef.current) {
try {
map.current.off('draw.create', updateMeasurementLabels)
Expand All @@ -473,14 +494,20 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
console.log('Draw control already removed')
}
}

// Clean up any existing labels
Object.values(polygonLabelsRef.current).forEach(marker => marker.remove())

if (userMarkerRef.current) {
userMarkerRef.current.remove()
userMarkerRef.current = null
}

Object.values(polygonLabelsRef.current).forEach(marker =>
marker.remove()
)
Object.values(lineLabelsRef.current).forEach(marker => marker.remove())

stopRotation()
setIsMapLoaded(false) // Reset map loaded state on cleanup
setMap(null) // Clear map instance from context
setIsMapLoaded(false)
setMap(null)
map.current.remove()
map.current = null
}
Expand Down Expand Up @@ -579,7 +606,14 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
}
}

Object.values(polygonLabelsRef.current).forEach(marker => marker.remove())
if (userMarkerRef.current) {
userMarkerRef.current.remove()
userMarkerRef.current = null
}

Object.values(polygonLabelsRef.current).forEach(marker =>
marker.remove()
)
Object.values(lineLabelsRef.current).forEach(marker => marker.remove())

stopRotation()
Expand Down
29 changes: 29 additions & 0 deletions components/map/user-marker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use client'

import React from 'react'
import Image from 'next/image'

const UserMarker: React.FC = () => {
return (
<div
style={{
width: '50px',
height: '50px',
borderRadius: '50%',
background: 'rgba(0, 119, 255, 0.2)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}}
>
<Image
src="/user-location-marker.png"
alt="User Location"
width={20}
height={20}
/>
</div>
)
}

export default UserMarker