Map: pin current view before 'Search this area' to defeat camera stop-revert

The native MapLibre camera re-applies its last setCamera() stop on the search
re-render (confirmed via trace: revert to prior target with userInteraction=false).
Track the live viewport and, before searching the current area, setCamera() to the
current center (0ms) so the re-applied stop is a no-op instead of a jump-back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hank 2026-07-06 11:22:47 -07:00
parent a4150555ae
commit 7321453337

View file

@ -59,6 +59,11 @@ export function MapScreen() {
// We position the camera exactly once, then never auto-recenter — otherwise // We position the camera exactly once, then never auto-recenter — otherwise
// re-renders (from searching) would snap the map back and fight the user's pan. // re-renders (from searching) would snap the map back and fight the user's pan.
const positioned = useRef(false); const positioned = useRef(false);
// The live viewport, tracked from region changes. MapLibre's native camera
// re-applies its last setCamera() stop on any re-render, so before a search we
// overwrite that stop with the current view (zero-duration) — otherwise the
// results re-render snaps the map back to the previous programmatic target.
const viewRef = useRef<{ center: [number, number]; zoom: number } | null>(null);
// The native UserLocation dot has its own GPS feed — capture it so "My // The native UserLocation dot has its own GPS feed — capture it so "My
// location" works even when expo-location can't get a fix (e.g. indoors). // location" works even when expo-location can't get a fix (e.g. indoors).
const nativeFix = useRef<Coords | null>(null); const nativeFix = useRef<Coords | null>(null);
@ -109,15 +114,16 @@ export function MapScreen() {
const searchThisArea = async () => { const searchThisArea = async () => {
try { try {
const center = await mapRef.current?.getCenter?.(); // [lng, lat] const center = await mapRef.current?.getCenter?.(); // [lng, lat]
console.log('[CAM] searchThisArea getCenter=', JSON.stringify(center));
if (center && center.length === 2) { if (center && center.length === 2) {
// Commit the current view as the camera's stop so the results re-render
// doesn't revert to the last programmatic (e.g. "My location") target.
cameraRef.current?.setCamera?.({ centerCoordinate: center, animationDuration: 0 });
await searchAt({ latitude: center[1], longitude: center[0] }, 'this area'); await searchAt({ latitude: center[1], longitude: center[0] }, 'this area');
return; return;
} }
} catch (err) { } catch {
console.log('[CAM] getCenter threw', String(err)); /* fall through */
} }
console.log('[CAM] searchThisArea FALLBACK coords=', JSON.stringify(coords));
if (coords) await searchAt(coords, 'this area'); if (coords) await searchAt(coords, 'this area');
}; };
@ -212,14 +218,19 @@ export function MapScreen() {
console.log('[CAM] onDidFinishLoadingMap (mapReady already', mapReady, ')'); console.log('[CAM] onDidFinishLoadingMap (mapReady already', mapReady, ')');
setMapReady(true); setMapReady(true);
}} }}
onRegionDidChange={(f: any) => onRegionDidChange={(f: any) => {
const c = f?.geometry?.coordinates;
const z = f?.properties?.zoomLevel;
if (Array.isArray(c) && c.length === 2) {
viewRef.current = { center: [c[0], c[1]], zoom: typeof z === 'number' ? z : 14 };
}
console.log( console.log(
'[CAM] regionDidChange center=', '[CAM] regionDidChange center=',
JSON.stringify(f?.geometry?.coordinates), JSON.stringify(c),
'userInteraction=', 'ui=',
f?.properties?.isUserInteraction, f?.properties?.isUserInteraction,
) );
} }}
> >
{cameraEl} {cameraEl}
{userLocationEl} {userLocationEl}