Skip to content

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Sep 24, 2025

User description

When activating the drawing mode, the map would zoom out to the globe level instead of maintaining the current zoom and position.

This was caused by the setupDrawingTools function not restoring the map's view state after adding the MapboxDraw control.

This commit fixes the issue by explicitly restoring the map's center, zoom, and pitch from a stored reference immediately after the drawing control is added. A flyTo with a duration of 0 is used to make the transition seamless for the user.


PR Type

Bug fix


Description

  • Fix map view preservation when entering drawing mode

  • Restore center, zoom, and pitch after adding MapboxDraw control

  • Use instant flyTo transition for seamless user experience


Diagram Walkthrough

flowchart LR
  A["Drawing mode activated"] --> B["MapboxDraw control added"]
  B --> C["Map view state restored"]
  C --> D["flyTo with duration 0"]
  D --> E["Seamless transition maintained"]
Loading

File Walkthrough

Relevant files
Bug fix
mapbox-map.tsx
Restore map view after drawing control setup                         

components/map/mapbox-map.tsx

  • Add map view state restoration after adding MapboxDraw control
  • Use flyTo with zero duration for instant transition
  • Preserve center, zoom, and pitch from currentMapCenterRef
+9/-0     

Summary by CodeRabbit

  • Bug Fixes
    • Preserves your current map view (center, zoom, pitch) on map initialization to avoid unexpected jumps.
    • Retains and restores ongoing drawings when switching drawing mode on/off so work is not lost.
    • Improves drawing toolbar initialization for smoother transitions and fewer interruptions.

When activating the drawing mode, the map would zoom out to the
globe level instead of maintaining the current zoom and position.

This was caused by the `setupDrawingTools` function not restoring
the map's view state after adding the MapboxDraw control.

This commit fixes the issue by explicitly restoring the map's center,
zoom, and pitch from a stored reference immediately after the
drawing control is added. A `flyTo` with a duration of 0 is used
to make the transition seamless for the user.
@vercel
Copy link
Contributor

vercel bot commented Sep 24, 2025

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

Project Deployment Preview Comments Updated (UTC)
qcx Ready Ready Preview Comment Sep 25, 2025 1:32pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

Walkthrough

Map initialization now uses the existing zoom value from currentMapCenterRef.current.zoom instead of computing and assigning initialZoom; the previous assignment of initialZoom into the ref was removed. Center and pitch initialization remain unchanged.

Changes

Cohort / File(s) Summary
Map initialization (zoom change)
components/map/mapbox-map.tsx
On map creation, use currentMapCenterRef.current.zoom as the starting zoom and remove the assignment that wrote initialZoom into currentMapCenterRef.current.zoom. Center and pitch initialization unchanged; minor newline formatting tweaks.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Init as Map init
  participant Ref as currentMapCenterRef
  participant Mapbox as Mapbox Map

  rect #eef8ff
    Init->>Ref: read zoom (currentMapCenterRef.current.zoom)
    Note right of Init: no longer assign initialZoom -> ref
    Init->>Mapbox: setZoom(using ref.zoom), setCenter(ref.center), setPitch(ref.pitch)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

I nibble the refs and keep the old zoom,
No overwrites in my cozy map room.
Center and pitch hold steady and true,
A rabbit-approved start for the view. 🐇🎨

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 title clearly and concisely summarizes the main change by stating that the map view is preserved when entering drawing mode, matching the bug fix implemented in the code.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-map-drawing-zoom

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7fccc9 and ddbc35e.

📒 Files selected for processing (1)
  • components/map/mapbox-map.tsx (2 hunks)

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.

@CLAassistant
Copy link

CLAassistant commented Sep 24, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ ngoiyaeric
❌ google-labs-jules[bot]
You have signed the CLA already but the status is still pending? Let us recheck it.

@qodo-code-review
Copy link
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Null Access

Accessing currentMapCenterRef.current without a guard could throw if the ref is null/undefined or missing expected fields. Consider adding a defensive check and fallback to current map state before calling flyTo.

// Restore the map's view state
const { center, zoom, pitch } = currentMapCenterRef.current
map.current.flyTo({
  center,
  zoom,
  pitch,
  duration: 0 // Fly instantly
})
Animation Jank

Calling flyTo immediately after addControl may run before the map style/render cycle settles, causing a brief jump. Consider wrapping in map.current.once('idle', ...) or requestAnimationFrame to ensure a stable restore.

map.current.flyTo({
  center,
  zoom,
  pitch,
  duration: 0 // Fly instantly
})
Incomplete View Restore

If bearing or padding were part of the previous view, restoring only center/zoom/pitch might still change the perceived view. Consider including bearing (and padding if used) to fully preserve the state.

// Restore the map's view state
const { center, zoom, pitch } = currentMapCenterRef.current
map.current.flyTo({
  center,
  zoom,
  pitch,
  duration: 0 // Fly instantly
})

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Sep 24, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Add null check before destructuring
Suggestion Impact:The commit wrapped the restoration of view state in a setTimeout and, within it, indirectly guarded against unsafe access by checking map.current before using currentMapCenterRef.current. While it still destructures currentMapCenterRef.current without its own explicit null check, the code path suggests safer handling during deferred restoration. Thus, the suggestion's intent to prevent a potential runtime error influenced the change.

code diff:

+    // Restore the map's view state after a brief delay.
+    setTimeout(() => {
+      if (map.current) {
+        const { center, zoom, pitch } = currentMapCenterRef.current;
+        map.current.setZoom(zoom);
+        map.current.setPitch(pitch);
+        map.current.setCenter(center);
+      }
+    }, 0);

Add a null check for currentMapCenterRef.current before destructuring its
properties to prevent a potential runtime error.

components/map/mapbox-map.tsx [272-279]

 // Restore the map's view state
-const { center, zoom, pitch } = currentMapCenterRef.current
-map.current.flyTo({
-  center,
-  zoom,
-  pitch,
-  duration: 0 // Fly instantly
-})
+if (currentMapCenterRef.current) {
+  const { center, zoom, pitch } = currentMapCenterRef.current
+  map.current.flyTo({
+    center,
+    zoom,
+    pitch,
+    duration: 0 // Fly instantly
+  })
+}

[Suggestion processed]

Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential runtime error if currentMapCenterRef.current is not populated and proposes a simple null check, which is a good defensive programming practice that improves code robustness.

Medium
  • Update

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.

  • Using flyTo({ duration: 0 }) is heavier than necessary for an instantaneous camera update; jumpTo is purpose-built and avoids transition semantics.
  • Directly destructuring currentMapCenterRef.current without a null/undefined guard risks a runtime error if the ref is unset.
  • The rest of the change is straightforward and targets the root cause effectively.
Summary of changes

What changed

  • After adding the MapboxDraw control, the code now restores the map view by reading center, zoom, and pitch from currentMapCenterRef.current and calling map.current.flyTo({ duration: 0 }) to make the transition instantaneous.

Affected area

  • components/map/mapbox-map.tsx: Added logic to restore map view state immediately after addControl.

Comment on lines 272 to 279
// Restore the map's view state
const { center, zoom, pitch } = currentMapCenterRef.current
map.current.flyTo({
center,
zoom,
pitch,
duration: 0 // Fly instantly
})

Choose a reason for hiding this comment

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

Restoring the camera is the right move, but using flyTo({ duration: 0 }) still routes through the transition system and emits transition-related events. For an instantaneous, side-effect-minimized update, jumpTo is the canonical Mapbox GL API. Also, the code destructures from currentMapCenterRef.current without a guard; if that ref is ever unset, this will throw at runtime. Guarding the ref and switching to jumpTo will make this more robust and intent-clear.

Suggestion

Consider guarding the ref and using jumpTo instead of flyTo for an instantaneous restore:

// Restore the map's view state
const state = currentMapCenterRef.current
if (state) {
  map.current.jumpTo({
    center: state.center,
    zoom: state.zoom,
    pitch: state.pitch
  })
}

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.

@charliecreates charliecreates bot removed the request for review from CharlieHelps September 24, 2025 11:38
When activating the drawing mode, the map would zoom out to the
globe level instead of maintaining the current zoom and position.

This was caused by the `setupDrawingTools` function not restoring
the map's view state after adding the MapboxDraw control.

This commit fixes the issue by explicitly restoring the map's center,
zoom, and pitch from a stored reference immediately after the
drawing control is added. A `flyTo` with a duration of 0 is used
to make the transition seamless for the user.

This is wrapped in a `setTimeout` to ensure the view restoration
happens after any asynchronous initialization of the MapboxDraw
control, addressing a potential timing issue.
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6df306 and cba65dc.

📒 Files selected for processing (1)
  • components/map/mapbox-map.tsx (1 hunks)

Comment on lines 272 to 279
// Restore the map's view state
const { center, zoom, pitch } = currentMapCenterRef.current
map.current.flyTo({
center,
zoom,
pitch,
duration: 0 // Fly instantly
})
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick

Fully preserve camera and avoid unnecessary animation: include bearing and use jumpTo.

Include bearing in the saved/restored state and prefer jumpTo for an instant, no-anim camera update.

Apply this diff within the shown lines:

-    const { center, zoom, pitch } = currentMapCenterRef.current
-    map.current.flyTo({
-      center,
-      zoom,
-      pitch,
-      duration: 0 // Fly instantly
-    })
+    const { center, zoom, pitch, bearing } = currentMapCenterRef.current
+    map.current.jumpTo({
+      center,
+      zoom,
+      pitch,
+      bearing,
+    })

Additionally update the saved state (outside this hunk):

  • Extend the ref type and initial value (Line 29):
const currentMapCenterRef = useRef<{ center: [number, number]; zoom: number; pitch: number; bearing: number }>({
  center: [position?.longitude ?? 0, position?.latitude ?? 0],
  zoom: 2,
  pitch: 0,
  bearing: 0,
});
  • Capture bearing too (Lines 326–331):
const center = map.current.getCenter();
const zoom = map.current.getZoom();
const pitch = map.current.getPitch();
const bearing = map.current.getBearing();
currentMapCenterRef.current = { center: [center.lng, center.lat], zoom, pitch, bearing };
  • Initialize map bearing from the ref (Line 414):
bearing: currentMapCenterRef.current.bearing,

When activating the drawing mode, the map would zoom out to the
globe level instead of maintaining the current zoom and position.

This was caused by the `setupDrawingTools` function not restoring
the map's view state after adding the MapboxDraw control, likely
due to a timing issue where the control's initialization overrides
the map's view.

This commit fixes the issue by explicitly restoring the map's center,
zoom, and pitch from a stored reference. The restoration is wrapped
in a `setTimeout` to ensure it executes after the drawing control's
initialization is complete. Direct setters (`setZoom`, `setCenter`,
`setPitch`) are used for a more robust state update.
The root cause of the map view resetting upon entering drawing mode
was the continuous destruction and recreation of the MapboxDraw
control. This reset the control's state and triggered a map view
update that was difficult to override.

This commit refactors the component's architecture to address this
fundamental issue. The MapboxDraw instance is now created only once
when the map is initialized and is preserved for the component's
entire lifecycle.

Changes include:
- Moving `new MapboxDraw()` to the main `useEffect` hook.
- Attaching draw-related event listeners only once.
- Simplifying `setupDrawingTools` to only add/remove the existing
  control from the map.
- Retaining the view restoration logic (`setZoom`, `setCenter`) as a
  safeguard for when the control is added back to the map.

This new approach is more efficient and robust, and it definitively
solves the view reset problem.
The root cause of the map view resetting upon entering drawing mode
was the continuous destruction and recreation of the MapboxDraw
control. This reset the control's state and triggered a map view
update that was difficult to override.

This commit refactors the component's architecture to address this
fundamental issue. The MapboxDraw instance is now created only once
when the map is initialized and is preserved for the component's
entire lifecycle. When switching modes, the control is now simply
added or removed from the map.

This robust solution includes:
- Creating the `MapboxDraw` instance only once on map load.
- Correctly managing event listeners by attaching them when the
  control is added and detaching them when it's removed.
- A safeguard `setTimeout` to restore the map's center, zoom, and
  pitch, ensuring the view is preserved even if adding the
  control causes a momentary view reset.

This new approach is more efficient and robust, and it definitively
solves the view reset problem.
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b7bf7ce and d7fccc9.

📒 Files selected for processing (1)
  • components/map/mapbox-map.tsx (5 hunks)
🔇 Additional comments (7)
components/map/mapbox-map.tsx (7)

236-239: Good: add the existing Draw instance once the map is ready.

This preserves the control instance across mode toggles and avoids re-instantiation churn.


241-244: Event wiring looks correct.

Attaching draw events to update labels is appropriate and pairs with the cleanup below.


332-332: Comment-only change is fine.

Keeping the instance and only removing the control matches the reuse strategy.


335-353: Solid teardown: save features, detach events, remove control, and clean labels.

This prevents listener duplication and preserves drawings across mode switches.


419-428: Good: single Draw instance stored in a ref.

This reduces GC pressure and avoids control re-creation bugs on toggles.


477-485: Dependencies look correct.

Including handlers used inside the effect avoids stale closures.


245-254: Replace delayed multi-call camera updates with a single jumpTo (no timeout).

Avoids arbitrary timeouts and reduces event churn from three separate setters. Use one atomic camera update; optionally include bearing to fully preserve state.

Apply this diff within these lines:

-    // Restore the map's view state after a brief delay.
-    setTimeout(() => {
-      if (map.current) {
-        const { center, zoom, pitch } = currentMapCenterRef.current;
-        map.current.setZoom(zoom);
-        map.current.setPitch(pitch);
-        map.current.setCenter(center);
-      }
-    }, 150);
+    // Restore the map's view state instantly with a single camera update.
+    if (map.current && currentMapCenterRef.current) {
+      const { center, zoom, pitch } = currentMapCenterRef.current;
+      map.current.jumpTo({ center, zoom, pitch });
+    }

Additionally (outside this hunk), consider preserving bearing end-to-end:

// Line 29: extend ref type and init
const currentMapCenterRef = useRef<{ center: [number, number]; zoom: number; pitch: number; bearing: number }>({
  center: [position?.longitude ?? 0, position?.latitude ?? 0],
  zoom: 2,
  pitch: 0,
  bearing: 0,
});

// Lines 290–297: capture bearing too
const bearing = map.current.getBearing();
currentMapCenterRef.current = { center: [center.lng, center.lat], zoom, pitch, bearing };

// Line 381: initialize map bearing from ref
bearing: currentMapCenterRef.current.bearing,

// And here (above diff), include bearing:
// map.current.jumpTo({ center, zoom, pitch, bearing: currentMapCenterRef.current.bearing });

Comment on lines 255 to 261
// Restore previous drawings if they exist.
if (drawingFeatures.current) {
drawRef.current.set(drawingFeatures.current);
// Update labels after restoring features.
setTimeout(updateMeasurementLabels, 100);
}
}, [updateMeasurementLabels])
}, [updateMeasurementLabels]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick

Prefer render-synchronized label refresh over setTimeout.

Use map.once('idle') to run after features are applied, eliminating timing guesses.

-    if (drawingFeatures.current) {
-      drawRef.current.set(drawingFeatures.current);
-      // Update labels after restoring features.
-      setTimeout(updateMeasurementLabels, 100);
-    }
+    if (drawingFeatures.current) {
+      drawRef.current.set(drawingFeatures.current);
+      // Update labels after features render.
+      if (map.current) map.current.once('idle', updateMeasurementLabels);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Restore previous drawings if they exist.
if (drawingFeatures.current) {
drawRef.current.set(drawingFeatures.current);
// Update labels after restoring features.
setTimeout(updateMeasurementLabels, 100);
}
}, [updateMeasurementLabels])
}, [updateMeasurementLabels]);
// Restore previous drawings if they exist.
if (drawingFeatures.current) {
drawRef.current.set(drawingFeatures.current);
// Update labels after features render.
if (map.current) map.current.once('idle', updateMeasurementLabels);
}
}, [updateMeasurementLabels]);
🤖 Prompt for AI Agents
In components/map/mapbox-map.tsx around lines 255 to 261, replace the
setTimeout-based label refresh with a render-synchronized approach: after
restoring features via drawRef.current.set(...), register a one-time idle
listener on the Mapbox map (e.g., map.once('idle', ...)) to call
updateMeasurementLabels so labels update after the map has finished rendering;
ensure you use the correct map instance reference (map or mapRef.current) and
remove the setTimeout call.

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