Skip to main content

Markers

Single marker

The anchor prop controls where the marker view attaches to the coordinate:

  • { x: 0.5, y: 1.0 } — bottom center (pin style)
  • { x: 0.5, y: 0.5 } — center (default)
<MarkerView coordinate={[90.364159, 23.823724]} anchor={{ x: 0.5, y: 1.0 }}>
<View style={{ width: 16, height: 16, borderRadius: 8, backgroundColor: '#2e8555' }} />
</MarkerView>

Multiple interactive markers with bottom sheet

import { Camera, MapView, MarkerView } from '@maplibre/maplibre-react-native';
import React, { useCallback, useRef, useState } from 'react';
import { ActivityIndicator, Animated, Pressable, StyleSheet, Text, View } from 'react-native';
import { BARIKOI_COLORS, useBarikoiMapStyle } from '../utils/mapUtils';

const markers = [
{ id: '1', coordinate: [90.389709, 23.874577] as [number, number], title: 'Uttara', description: 'Modern Township' },
{ id: '2', coordinate: [90.415482, 23.793059] as [number, number], title: 'Gulshan', description: 'Business District' },
{ id: '3', coordinate: [90.367456, 23.747431] as [number, number], title: 'Dhanmondi', description: 'Cultural Hub' },
{ id: '4', coordinate: [90.399452, 23.869585] as [number, number], title: 'Airport', description: 'Hazrat Shahjalal International' },
{ id: '5', coordinate: [90.364159, 23.823724] as [number, number], title: 'Barikoi Head Office', description: 'Main office location' },
];

export default function MarkerScreen() {
const { styleJson, loading, error } = useBarikoiMapStyle();
const [selectedMarker, setSelectedMarker] = useState<string | null>(null);
const bottomSheetAnim = useRef(new Animated.Value(0)).current;

const showBottomSheet = useCallback(() => {
Animated.spring(bottomSheetAnim, { toValue: 1, useNativeDriver: true }).start();
}, [bottomSheetAnim]);

const hideBottomSheet = useCallback(() => {
Animated.spring(bottomSheetAnim, { toValue: 0, useNativeDriver: true }).start();
setSelectedMarker(null);
}, [bottomSheetAnim]);

const handleMarkerPress = useCallback((markerId: string) => {
setSelectedMarker(markerId);
showBottomSheet();
}, [showBottomSheet]);

if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color={BARIKOI_COLORS.primary} /></View>;
if (error) return <View style={styles.centered}><Text>{error}</Text></View>;

const selected = markers.find(m => m.id === selectedMarker);

return (
<View style={styles.container}>
<MapView style={styles.map} attributionEnabled={false} zoomEnabled compassEnabled mapStyle={styleJson}>
<Camera centerCoordinate={[90.389709, 23.824577]} zoomLevel={11.5} animationDuration={1000} animationMode="linearTo" />
{markers.map((marker) => (
<MarkerView key={marker.id} coordinate={marker.coordinate} anchor={{ x: 0.5, y: 1.0 }}>
<Pressable onPress={() => handleMarkerPress(marker.id)}>
<View style={[
styles.markerDot,
selectedMarker === marker.id && styles.markerDotSelected
]} />
</Pressable>
</MarkerView>
))}
</MapView>

{selected && (
<Animated.View style={[styles.bottomSheet, {
transform: [{
translateY: bottomSheetAnim.interpolate({ inputRange: [0, 1], outputRange: [200, 0] })
}]
}]}>
<View style={styles.sheetContent}>
<View style={styles.sheetHeader}>
<View style={{ flex: 1 }}>
<Text style={styles.sheetTitle}>{selected.title}</Text>
<Text style={styles.sheetDesc}>{selected.description}</Text>
</View>
<Pressable onPress={hideBottomSheet} style={styles.closeBtn}>
<Text style={styles.closeText}></Text>
</Pressable>
</View>
<View style={styles.coordBox}>
<View style={{ marginBottom: 8 }}>
<Text style={styles.coordLabel}>Latitude</Text>
<Text style={styles.coordValue}>{selected.coordinate[1].toFixed(6)}</Text>
</View>
<View style={{ height: 1, backgroundColor: 'rgba(0,0,0,0.1)', marginVertical: 8 }} />
<View>
<Text style={styles.coordLabel}>Longitude</Text>
<Text style={styles.coordValue}>{selected.coordinate[0].toFixed(6)}</Text>
</View>
</View>
</View>
</Animated.View>
)}
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white' },
map: { flex: 1 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center' },
markerDot: { width: 16, height: 16, borderRadius: 8, backgroundColor: BARIKOI_COLORS.primary },
markerDotSelected: { width: 20, height: 20, borderRadius: 10, backgroundColor: BARIKOI_COLORS.secondary },
bottomSheet: { position: 'absolute', bottom: 0, left: 0, right: 0, backgroundColor: 'white', borderTopLeftRadius: 20, borderTopRightRadius: 20, shadowColor: '#000', shadowOffset: { width: 0, height: -2 }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, paddingBottom: 20 },
sheetContent: { padding: 16 },
sheetHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16 },
sheetTitle: { fontSize: 18, fontWeight: 'bold', color: BARIKOI_COLORS.text, marginBottom: 4 },
sheetDesc: { fontSize: 14, color: BARIKOI_COLORS.text, opacity: 0.7 },
closeBtn: { width: 30, height: 30, alignItems: 'center', justifyContent: 'center', borderRadius: 15, backgroundColor: BARIKOI_COLORS.background },
closeText: { fontSize: 16, color: BARIKOI_COLORS.text },
coordBox: { backgroundColor: BARIKOI_COLORS.background, borderRadius: 12, padding: 16 },
coordLabel: { fontSize: 12, color: BARIKOI_COLORS.text, opacity: 0.7, marginBottom: 4 },
coordValue: { fontSize: 16, color: BARIKOI_COLORS.text, fontWeight: '500' },
});
Performance at scale

MarkerView renders native Android/iOS views, which is fine for up to ~50 markers. For 100+ markers, use ShapeSource with a SymbolLayer instead — it renders markers as map tiles and handles thousands efficiently.


Draggable marker

Let users drag a marker or tap the map to pick a location. Use PointAnnotation (not MarkerView) for draggable markers.

import { Camera, MapView, PointAnnotation } from '@maplibre/maplibre-react-native';
import type { Feature, Geometry, Point } from 'geojson';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native';
import { BARIKOI_COLORS, useBarikoiMapStyle } from '../utils/mapUtils';

type DragFeature = Feature<Point>;
const formatCoord = (v: number) => `${v.toFixed(6)} deg`;

export default function DraggableMarkerScreen() {
const { styleJson, loading, error } = useBarikoiMapStyle();
const cameraRef = useRef<any>(null);
const [markerCoord, setMarkerCoord] = useState<[number, number]>([90.364159, 23.823724]);
const [zoomLevel, setZoomLevel] = useState(16);

const handleDrag = useCallback((payload: DragFeature) => {
const [lng, lat] = payload.geometry.coordinates as number[];
setMarkerCoord([lng, lat]);
}, []);

const handleMapPress = useCallback((payload: Feature<Geometry>) => {
if (payload.geometry?.type === 'Point') {
setMarkerCoord(payload.geometry.coordinates as [number, number]);
}
}, []);

const formatted = useMemo(() => ({
lat: formatCoord(markerCoord[1]),
lng: formatCoord(markerCoord[0]),
}), [markerCoord]);

if (loading) return <View style={styles.centered}><ActivityIndicator size="large" color={BARIKOI_COLORS.primary} /></View>;
if (error) return <View style={styles.centered}><Text>{error}</Text></View>;

return (
<View style={styles.container}>
<MapView
style={styles.map}
attributionEnabled={false}
zoomEnabled scrollEnabled rotateEnabled compassEnabled
mapStyle={styleJson}
onPress={handleMapPress}
>
<Camera ref={cameraRef} centerCoordinate={markerCoord} zoomLevel={zoomLevel} animationDuration={300} animationMode="easeTo" />
<PointAnnotation
id="draggable"
coordinate={markerCoord}
draggable
anchor={{ x: 0.5, y: 1.0 }}
onDragStart={handleDrag}
onDragEnd={handleDrag}
>
<View style={{ width: 30, height: 30, backgroundColor: '#FF5748', borderRadius: 15, borderWidth: 2, borderColor: 'white' }} />
</PointAnnotation>
</MapView>

<View style={styles.infoPanel}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<View style={styles.coordCard}>
<Text style={styles.coordLabel}>Latitude</Text>
<Text style={styles.coordValue}>{formatted.lat}</Text>
</View>
<View style={styles.coordCard}>
<Text style={styles.coordLabel}>Longitude</Text>
<Text style={styles.coordValue}>{formatted.lng}</Text>
</View>
</View>
</View>

<View style={styles.zoomControls}>
<Pressable style={styles.zoomBtn} onPress={() => {
const z = Math.min(zoomLevel + 1, 22);
setZoomLevel(z);
cameraRef.current?.setCamera({ zoomLevel: z, animationDuration: 300 });
}}>
<Text style={styles.zoomText}>+</Text>
</Pressable>
<View style={styles.zoomLabel}>
<Text style={{ fontSize: 12, fontWeight: '600' }}>{Math.round(zoomLevel)}x</Text>
</View>
<Pressable style={styles.zoomBtn} onPress={() => {
const z = Math.max(zoomLevel - 1, 0);
setZoomLevel(z);
cameraRef.current?.setCamera({ zoomLevel: z, animationDuration: 300 });
}}>
<Text style={styles.zoomText}></Text>
</Pressable>
</View>
</View>
);
}

const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white' },
map: { flex: 1 },
centered: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 24 },
infoPanel: { position: 'absolute', bottom: 24, left: 16, right: 16, backgroundColor: 'white', borderRadius: 16, padding: 16, elevation: 10, shadowColor: '#000', shadowOpacity: 0.1, shadowOffset: { width: 0, height: 10 }, shadowRadius: 20 },
coordCard: { flex: 1, marginHorizontal: 4, borderWidth: 1, borderColor: '#e1e1e1', borderRadius: 12, padding: 12, backgroundColor: BARIKOI_COLORS.background },
coordLabel: { fontSize: 12, color: BARIKOI_COLORS.text, opacity: 0.7 },
coordValue: { fontSize: 16, fontWeight: '600', color: BARIKOI_COLORS.text },
zoomControls: { position: 'absolute', right: 16, top: 40, backgroundColor: 'white', borderRadius: 8, elevation: 5, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 3.84 },
zoomBtn: { width: 44, height: 44, justifyContent: 'center', alignItems: 'center' },
zoomText: { fontSize: 22, color: BARIKOI_COLORS.primary, lineHeight: 26 },
zoomLabel: { paddingVertical: 8, alignItems: 'center', borderTopWidth: 1, borderBottomWidth: 1, borderColor: '#e1e1e1' },
});

Key points:

  • Use PointAnnotation (not MarkerView) for draggable markers
  • Set the draggable prop and handle both onDragStart and onDragEnd
  • Handle onPress on MapView to move the marker on tap
  • The drag callback receives a GeoJSON Feature<Point> — coordinates are [lng, lat]