Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Nov 21, 2025

User description

This PR implements the requested change to show the Mapbox NavigationControl (zoom icons) in both DrawingMode and RealTimeMode, ensuring users have precise map control in both scenarios. The control is hidden in FreeMode.


PR Type

Enhancement


Description

  • Implement dynamic NavigationControl visibility based on map mode

  • Show zoom controls in DrawingMode and RealTimeMode

  • Hide zoom controls in FreeMode

  • Manage NavigationControl lifecycle with proper ref tracking


Diagram Walkthrough

flowchart LR
  mapType{"Map Mode Changed?"}
  drawing["DrawingMode or RealTimeMode"]
  free["FreeMode"]
  addControl["Add NavigationControl"]
  removeControl["Remove NavigationControl"]
  
  mapType -->|Yes| drawing
  mapType -->|Yes| free
  drawing --> addControl
  free --> removeControl
Loading

File Walkthrough

Relevant files
Enhancement
mapbox-map.tsx
Dynamic NavigationControl management by map mode                 

components/map/mapbox-map.tsx

  • Added navigationControlRef to track NavigationControl instance
  • Implemented conditional logic to add NavigationControl in DrawingMode
    and RealTimeMode
  • Implemented logic to remove NavigationControl in FreeMode
  • Removed hardcoded NavigationControl initialization from map setup
+20/-1   

Summary by CodeRabbit

  • Bug Fixes
    • Navigation controls now conditionally display based on map mode, appearing only in Drawing and Real-Time modes while remaining hidden in Free mode.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 21, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
qcx Ready Ready Preview Comment Nov 21, 2025 7:19am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Walkthrough

The mapbox-map.tsx component was modified to conditionally manage the Mapbox NavigationControl based on map type. The control is now created and attached when transitioning to DrawingMode or RealTimeMode, and detached in FreeMode, using a ref for persistence across renders.

Changes

Cohort / File(s) Summary
Navigation Control Lifecycle Management
components/map/mapbox-map.tsx
Introduced navigationControlRef to manage NavigationControl instances. Added conditional logic to mount/unmount the control based on mapType (present in DrawingMode and RealTimeMode, removed in FreeMode). Removed unconditional control initialization. Extended mode transition flow to handle control creation and removal.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Map as Mapbox Map
    participant Control as NavigationControl
    
    User->>Map: Change mapType to DrawingMode
    Map->>Map: Check navigationControlRef
    alt ref is null
        Map->>Control: Create new NavigationControl
        Control-->>Map: Instance created
        Map->>Map: Add control to map
        Map->>Map: Store in navigationControlRef
    end
    
    User->>Map: Change mapType to FreeMode
    Map->>Map: Check navigationControlRef
    alt ref exists
        Map->>Control: Remove from map
        Map->>Map: Clear navigationControlRef
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Ref-based state management: Verify that navigationControlRef is correctly initialized, updated, and cleaned up across mode transitions to prevent memory leaks or stale references.
  • Conditional mounting logic: Ensure the logic correctly identifies which map types should have the NavigationControl (DrawingMode and RealTimeMode) and confirm FreeMode properly detaches it.
  • Re-render behavior: Confirm that the ref persistence pattern avoids unnecessary control recreation and doesn't trigger unintended re-renders.
  • Edge cases: Check transitions between different mode combinations and verify cleanup during component unmounting.

Poem

🗺️ A rabbit maps the digital land,
Where navigation tools now understand—
When drawing or real-time modes align,
The controls appear by design,
But fade to free when modes unwind! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main change: conditionally displaying the Mapbox NavigationControl in DrawingMode and RealTimeMode, which directly matches the implementation changes in the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/mapbox-nav-controls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Contributor

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing Audit Logs: The new logic adds/removes NavigationControl based on mode changes without emitting any
audit log indicating the user, mode transition, or outcome, which may be required if mode
changes are considered critical actions in this system.

Referred Code
// Add navigation control for DrawingMode or RealTimeMode
if (mapType === MapToggleEnum.DrawingMode || mapType === MapToggleEnum.RealTimeMode) {
  if (!navigationControlRef.current) {
    navigationControlRef.current = new mapboxgl.NavigationControl()
    if (map.current) {
      map.current.addControl(navigationControlRef.current, 'top-left')
    }
  }
} else {
  // Remove navigation control in FreeMode
  if (navigationControlRef.current) {
    if (map.current) {
      map.current.removeControl(navigationControlRef.current)
    }
    navigationControlRef.current = null
  }
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Missing Try/Catch: Calls to add or remove Mapbox controls are performed without guarding against failures
(e.g., null map, Mapbox errors), lacking contextual error handling or fallback.

Referred Code
  if (!navigationControlRef.current) {
    navigationControlRef.current = new mapboxgl.NavigationControl()
    if (map.current) {
      map.current.addControl(navigationControlRef.current, 'top-left')
    }
  }
} else {
  // Remove navigation control in FreeMode
  if (navigationControlRef.current) {
    if (map.current) {
      map.current.removeControl(navigationControlRef.current)
    }
    navigationControlRef.current = null
  }

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix race condition during initialization

To prevent a race condition, add a guard clause to ensure map.current is
initialized before attempting to add or remove the navigation control. This
avoids a scenario where the control is created but never added to the map.

components/map/mapbox-map.tsx [356-372]

+if (!map.current) return;
+
 // Add navigation control for DrawingMode or RealTimeMode
 if (mapType === MapToggleEnum.DrawingMode || mapType === MapToggleEnum.RealTimeMode) {
   if (!navigationControlRef.current) {
-    navigationControlRef.current = new mapboxgl.NavigationControl()
-    if (map.current) {
-      map.current.addControl(navigationControlRef.current, 'top-left')
-    }
+    navigationControlRef.current = new mapboxgl.NavigationControl();
+    map.current.addControl(navigationControlRef.current, 'top-left');
   }
 } else {
   // Remove navigation control in FreeMode
   if (navigationControlRef.current) {
-    if (map.current) {
-      map.current.removeControl(navigationControlRef.current)
-    }
-    navigationControlRef.current = null
+    map.current.removeControl(navigationControlRef.current);
+    navigationControlRef.current = null;
   }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a race condition where the navigation control might be instantiated but not added to the map if the effect runs before the map is initialized, leading to a bug. The proposed fix correctly prevents this by ensuring the map exists before executing the control logic.

Medium
  • More

Copy link

@charliecreates charliecreates bot left a comment

Choose a reason for hiding this comment

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

The new NavigationControl handling correctly scopes the control to DrawingMode and RealTimeMode, but its dependence on isMapTypeChanged makes it somewhat fragile and tightly coupled to that flag. Removing the initial unconditional NavigationControl addition is correct, yet you now rely on later mode-change logic to attach the control, which might cause a brief mismatch between the initial mode and visible controls. Extracting the control management into a dedicated effect/helper that always derives visibility from mapType and map initialization would improve robustness and clarity.

Additional notes (1)
  • Maintainability | components/map/mapbox-map.tsx:353-373
    The logic for adding/removing NavigationControl based on mapType is correct, but it's tightly coupled to isMapTypeChanged. If mapType were ever changed without isMapTypeChanged being true (or this flag were miscomputed), the control state could get out of sync with the mode. That makes the behavior somewhat brittle and harder to extend.

A more robust pattern is to have a small dedicated effect or helper that always reconciles the control visibility against the current mapType and map.current whenever either changes, instead of only when a particular flag is set.

Summary of changes
  • Introduced a navigationControlRef (useRef<mapboxgl.NavigationControl | null>) to keep track of a single NavigationControl instance on the map.
  • Removed the unconditional NavigationControl addition during initial map creation.
  • Added logic on map mode change to:
    • Add the navigation control when mapType is DrawingMode or RealTimeMode.
    • Remove and clear the navigation control when switching to FreeMode.
  • Ensured the control is only instantiated once and reused across mode switches.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8718e4c and d7073a3.

📒 Files selected for processing (1)
  • components/map/mapbox-map.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
components/map/mapbox-map.tsx (1)
components/map-toggle.tsx (1)
  • MapToggle (14-39)

const map = useRef<mapboxgl.Map | null>(null)
const { setMap } = useMap()
const drawRef = useRef<MapboxDraw | null>(null)
const navigationControlRef = useRef<mapboxgl.NavigationControl | null>(null)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Reset navigationControlRef on unmount to avoid stale control instance across remounts

navigationControlRef is only updated inside the map-type effect; it’s never cleared in the unmount/teardown paths. If the component unmounts while in DrawingMode or RealTimeMode, the ref will still point at a NavigationControl instance on the next mount. Because the add logic is guarded with if (!navigationControlRef.current), this can prevent a control from ever being (re)attached to the new map instance.

Add explicit cleanup so the ref is nulled and (optionally) the control is removed before map.current.remove():

-      if (map.current) {
-        map.current.off('moveend', captureMapCenter)
+      if (map.current) {
+        if (navigationControlRef.current) {
+          try {
+            map.current.removeControl(navigationControlRef.current)
+          } catch (e) {
+            console.log('Navigation control already removed')
+          }
+          navigationControlRef.current = null
+        }
+        map.current.off('moveend', captureMapCenter)-        map.current.remove()
-        map.current = null
+        map.current.remove()
+        map.current = null
       }

You’ll want equivalent handling in the duplicate cleanup effect at lines 578-615, or (better) dedupe those cleanups and have a single teardown path that also clears navigationControlRef.

Also applies to: 481-511, 578-615


I'll verify the concerns in this review by examining the actual code structure.


🏁 Script executed:

#!/bin/bash
# Extract and examine navigationControlRef usage and related effects
cat components/map/mapbox-map.tsx | head -n 650 | tail -n +20

Length of output: 21799


Ensure navigationControlRef is cleared in all cleanup paths to prevent stale control references on remount

The two cleanup effects (lines 481–511 and 578–615) do not reset navigationControlRef, allowing a stale reference to persist across component remounts. If the component unmounts while in DrawingMode or RealTimeMode, the ref will retain the old NavigationControl instance. On remount, the guard if (!navigationControlRef.current) will be false, preventing a fresh control from being added to the new map.

Add cleanup before map.current.remove() in both effects:

      if (navigationControlRef.current) {
        try {
          map.current.removeControl(navigationControlRef.current)
        } catch (e) {
          console.log('Navigation control already removed')
        }
        navigationControlRef.current = null
      }

Consolidate the two cleanup effects into a single teardown path if possible to avoid maintaining duplicate cleanup logic.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
components/map/mapbox-map.tsx (line 23 and cleanup areas around lines 481–511
and 578–615): the navigationControlRef declared at line 23 is not being cleared
in the component cleanup paths, which can leave a stale NavigationControl on
unmount and prevent a fresh control from being added on remount; update both
cleanup branches to null out navigationControlRef.current before calling
map.current.remove() (or wherever the map is torn down), and refactor the two
teardown effects into a single consolidated cleanup effect if possible so the
ref-reset logic is maintained in one place to avoid duplication.

Comment on lines +356 to +372
// Add navigation control for DrawingMode or RealTimeMode
if (mapType === MapToggleEnum.DrawingMode || mapType === MapToggleEnum.RealTimeMode) {
if (!navigationControlRef.current) {
navigationControlRef.current = new mapboxgl.NavigationControl()
if (map.current) {
map.current.addControl(navigationControlRef.current, 'top-left')
}
}
} else {
// Remove navigation control in FreeMode
if (navigationControlRef.current) {
if (map.current) {
map.current.removeControl(navigationControlRef.current)
}
navigationControlRef.current = null
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

NavigationControl is not added on initial mount for Drawing/RealTime modes

The useEffect handling map-type changes only runs after render and currently bails out when the map isn’t initialized:

if (!map.current || !initializedRef.current) return

You also removed the unconditional NavigationControl creation from the map init (around line 431). Combined, this means:

  • On first mount, mapType effect runs before the map is initialized → early return, so no control is added.
  • The effect won’t re-run until mapType changes, so if the initial mapType is RealTimeMode or DrawingMode (which is likely), the user won’t see zoom controls until they toggle modes at least once.

To ensure the control shows up immediately in those modes, initialize it in the map load handler and keep this effect strictly for mode transitions:

      map.current.on('load', () => {
        if (!map.current) return
        setMap(map.current)
        …
        if (mapType === MapToggleEnum.DrawingMode) {
          setupDrawingTools()
        }
+
+       // Ensure NavigationControl is present on initial load for Drawing/RealTime modes
+       if (mapType === MapToggleEnum.DrawingMode || mapType === MapToggleEnum.RealTimeMode) {
+         if (!navigationControlRef.current) {
+           navigationControlRef.current = new mapboxgl.NavigationControl()
+           map.current.addControl(navigationControlRef.current, 'top-left')
+         }
+       }
        …
      })

The existing useEffect block at lines 356-372 can then reliably handle adding/removing the control when mapType changes (including removing it in FreeMode).

Also applies to: 431-431


I'll help verify this review comment by examining the actual code structure. Let me generate shell scripts to investigate the implementation.


🏁 Script executed:

#!/bin/bash
# Find and display the mapbox-map.tsx file structure
fd -t f "mapbox-map.tsx" --exec cat {} \;

Length of output: 22680


🏁 Script executed:

#!/bin/bash
# Check specific line ranges mentioned in the review
# Lines 356-372 for the useEffect and context around line 431
fd -t f "mapbox-map.tsx" --exec sed -n '340,450p' {} \;

Length of output: 4046


🏁 Script executed:

#!/bin/bash
# Search for NavigationControl usage and initialization
rg -nC5 "NavigationControl" --type=tsx

Length of output: 27


🏁 Script executed:

#!/bin/bash
# Search for map initialization and 'load' event handlers
rg -nC5 "map\.current\.on\s*\(\s*['\"]load" --type=tsx

Length of output: 27


Add NavigationControl initialization to map load handler

Your review is correct. The NavigationControl won't appear on initial mount when mapType is DrawingMode or RealTimeMode because:

  1. The mapType effect (lines 341–428) checks if (!map.current || !initializedRef.current) return before doing anything
  2. On first render, this effect runs before the map finishes loading, so it bails out early
  3. The effect won't run again unless mapType changes (it's in the dependency array)
  4. The load handler (lines 412–431) never initializes NavigationControl

To fix this, initialize NavigationControl in the map load handler when the initial mapType is DrawingMode or RealTimeMode. Add the logic immediately after setupDrawingTools() at line ~427:

// Ensure NavigationControl is present on initial load for Drawing/RealTime modes
if (mapType === MapToggleEnum.DrawingMode || mapType === MapToggleEnum.RealTimeMode) {
  if (!navigationControlRef.current) {
    navigationControlRef.current = new mapboxgl.NavigationControl()
    map.current.addControl(navigationControlRef.current, 'top-left')
  }
}

The existing mapType effect can then reliably handle adding/removing the control on subsequent mode transitions.

🤖 Prompt for AI Agents
components/map/mapbox-map.tsx around lines 356 to 372: NavigationControl is only
added by the mapType effect which bails out before the map finishes loading, so
the control never appears on initial mount when mapType is DrawingMode or
RealTimeMode; to fix, add the same initialization into the map load handler
(immediately after setupDrawingTools()): check if mapType is DrawingMode or
RealTimeMode, and if navigationControlRef.current is null create a new
mapboxgl.NavigationControl(), add it to map.current at 'top-left', and assign it
to navigationControlRef.current so subsequent effects can manage it; keep the
guard to avoid duplicate creation.

@ngoiyaeric
Copy link
Collaborator Author

@coderabbitai the zoom icons are not showing even when using the mapping tools

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 21, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the mapbox-map.tsx file to examine the implementation
cat components/map/mapbox-map.tsx

Length of output: 22680


@ngoiyaeric I found the issue! The NavigationControl is only being added when the map mode changes, but not during the initial map load. If you start in DrawingMode or RealTimeMode, the control never gets added.

Here's the fix - you need to add the NavigationControl initialization in the map.on('load') callback. Update the code around line 504 like this:

map.current.on('load', () => {
  if (!map.current) return
  setMap(map.current) // Set map instance in context

  // Add terrain and sky
  map.current.addSource('mapbox-dem', {
    type: 'raster-dem',
    url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
    tileSize: 512,
    maxzoom: 14,
  })

  map.current.setTerrain({ source: 'mapbox-dem', exaggeration: 1.5 })

  map.current.addLayer({
    id: 'sky',
    type: 'sky',
    paint: {
      'sky-type': 'atmosphere',
      'sky-atmosphere-sun': [0.0, 0.0],
      'sky-atmosphere-sun-intensity': 15,
    },
  })

  // Add navigation control for initial DrawingMode or RealTimeMode
  if (mapType === MapToggleEnum.DrawingMode || mapType === MapToggleEnum.RealTimeMode) {
    navigationControlRef.current = new mapboxgl.NavigationControl()
    map.current.addControl(navigationControlRef.current, 'top-left')
  }

  // Initialize drawing tools based on initial mode
  if (mapType === MapToggleEnum.DrawingMode) {
    setupDrawingTools()
  }

  // If not in real-time mode, start rotation
  if (mapType !== MapToggleEnum.RealTimeMode) {
    startRotation()
  }

  initializedRef.current = true
  setIsMapLoaded(true) // Set map loaded state to true
  setupGeolocationWatcher()
})

The key addition is these lines:

// Add navigation control for initial DrawingMode or RealTimeMode
if (mapType === MapToggleEnum.DrawingMode || mapType === MapToggleEnum.RealTimeMode) {
  navigationControlRef.current = new mapboxgl.NavigationControl()
  map.current.addControl(navigationControlRef.current, 'top-left')
}

This ensures the NavigationControl is added during the initial map load if you're starting in DrawingMode or RealTimeMode, and then the existing effect at line 389 will handle adding/removing it when switching between modes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants