By the end of this lesson, you will:
The difference between a basic map and a viral app often comes down to how you visualize data. Custom markers transform lat/lng coordinates into meaningful experiences. Think about apps like Airbnb showing property photos as markers, or Snapchat's friend locations with Bitmojis.
In this lesson, we'll learn to create markers that users want to tap, share, and explore. Well-designed markers make your map data instantly understandable and visually appealing.
The Marker component displays a pin at a specific coordinate:
import MapView, { Marker } from 'react-native-maps';
export default function BasicMarkersScreen() {
return (
<MapView
style={styles.map}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.1,
longitudeDelta: 0.1,
}}
>
<Marker
coordinate={{
latitude: 37.78825,
longitude: -122.4324,
}}
title="Golden Gate Bridge"
description="Iconic San Francisco landmark"
/>
<Marker
coordinate={{
latitude: 37.8199,
longitude: -122.4783,
}}
title="Alcatraz Island"
description="Historic federal prison"
pinColor="blue"
/>
</MapView>
);
}
💡 Tip: Use
pinColorprop for quick color changes on default markers (red, blue, green).
Replace default pins with custom images for branded experiences:
import MapView, { Marker } from 'react-native-maps';
import { Image } from 'react-native';
export default function CustomIconsScreen() {
return (
<MapView style={styles.map} initialRegion={region}>
<Marker
coordinate={{ latitude: 37.78825, longitude: -122.4324 }}
title="Coffee Shop"
>
<Image
source={require('./assets/coffee-icon.png')}
style={{ width: 40, height: 40 }}
/>
</Marker>
<Marker
coordinate={{ latitude: 37.7749, longitude: -122.4194 }}
title="Restaurant"
>
<Image
source={{ uri: 'https://example.com/restaurant-icon.png' }}
style={{ width: 40, height: 40 }}
/>
</Marker>
</MapView>
);
}
Best Practices for Custom Icons:
<10KB per icon)Render multiple markers from an array:
import { useState } from 'react';
import MapView, { Marker } from 'react-native-maps';
export default function DynamicMarkersScreen() {
const [locations, setLocations] = useState([
{
id: 1,
title: 'Starbucks',
coordinate: { latitude: 37.78825, longitude: -122.4324 },
type: 'coffee',
},
{
id: 2,
title: 'Blue Bottle',
coordinate: { latitude: 37.7849, longitude: -122.4294 },
type: 'coffee',
},
{
id: 3,
title: 'Philz Coffee',
coordinate: { latitude: 37.7899, longitude: -122.4394 },
type: 'coffee',
},
]);
return (
<MapView style={styles.map} initialRegion={region}>
{locations.map((location) => (
<Marker
key={location.id}
coordinate={location.coordinate}
title={location.title}
onPress={() => console.log(`Tapped ${location.title}`)}
/>
))}
</MapView>
);
}
💡 Tip: Always use unique
keyprops when rendering markers from arrays to optimize re-renders.
Create rich callouts with custom components:
import MapView, { Marker, Callout } from 'react-native-maps';
import { View, Text, Image, StyleSheet } from 'react-native';
export default function CustomCalloutScreen() {
return (
<MapView style={styles.map} initialRegion={region}>
<Marker coordinate={{ latitude: 37.78825, longitude: -122.4324 }}>
<Callout>
<View style={styles.callout}>
<Image
source={{ uri: 'https://example.com/shop.jpg' }}
style={styles.calloutImage}
/>
<Text style={styles.calloutTitle}>Blue Bottle Coffee</Text>
<Text style={styles.calloutRating}>⭐ 4.8 (324 reviews)</Text>
<Text style={styles.calloutDistance}>0.3 miles away</Text>
</View>
</Callout>
</Marker>
</MapView>
);
}
const styles = StyleSheet.create({
callout: {
width: 200,
padding: 10,
},
calloutImage: {
width: 180,
height: 100,
borderRadius: 8,
marginBottom: 8,
},
calloutTitle: {
fontSize: 16,
fontWeight: 'bold',
},
calloutRating: {
fontSize: 14,
color: '#666',
marginTop: 4,
},
calloutDistance: {
fontSize: 12,
color: '#999',
marginTop: 2,
},
});
Callout Customization Options:
onPress on Callout for navigationShow areas of interest or coverage zones:
import MapView, { Marker, Circle } from 'react-native-maps';
export default function CircleOverlayScreen() {
return (
<MapView style={styles.map} initialRegion={region}>
{/* Delivery zone */}
<Circle
center={{ latitude: 37.78825, longitude: -122.4324 }}
radius={1000} // meters
fillColor="rgba(0, 150, 255, 0.2)"
strokeColor="rgba(0, 150, 255, 0.8)"
strokeWidth={2}
/>
<Marker
coordinate={{ latitude: 37.78825, longitude: -122.4324 }}
title="Restaurant"
description="1km delivery radius"
/>
{/* Search radius */}
<Circle
center={{ latitude: 37.7749, longitude: -122.4194 }}
radius={500}
fillColor="rgba(255, 0, 0, 0.15)"
strokeColor="rgba(255, 0, 0, 0.6)"
strokeWidth={2}
/>
</MapView>
);
}
Use Cases for Circles:
Draw lines connecting multiple points:
import MapView, { Polyline, Marker } from 'react-native-maps';
export default function PolylineScreen() {
const routeCoordinates = [
{ latitude: 37.78825, longitude: -122.4324 },
{ latitude: 37.7849, longitude: -122.4294 },
{ latitude: 37.7899, longitude: -122.4394 },
{ latitude: 37.7949, longitude: -122.4424 },
];
return (
<MapView style={styles.map} initialRegion={region}>
<Polyline
coordinates={routeCoordinates}
strokeColor="#FF0000"
strokeWidth={4}
lineDashPattern={[0]} // Solid line
/>
{/* Start marker */}
<Marker
coordinate={routeCoordinates[0]}
title="Start"
pinColor="green"
/>
{/* End marker */}
<Marker
coordinate={routeCoordinates[routeCoordinates.length - 1]}
title="End"
pinColor="red"
/>
</MapView>
);
}
Polyline Styling:
strokeWidth: Line thickness (1-10 recommended)strokeColor: Hex or RGBA colorlineDashPattern: [dash, gap] for dashed linesgeodesic: true for curved lines following Earth's surfaceDefine custom shapes and zones:
import MapView, { Polygon } from 'react-native-maps';
export default function PolygonScreen() {
const parkCoordinates = [
{ latitude: 37.78825, longitude: -122.4324 },
{ latitude: 37.7849, longitude: -122.4294 },
{ latitude: 37.7899, longitude: -122.4294 },
{ latitude: 37.7899, longitude: -122.4324 },
];
return (
<MapView style={styles.map} initialRegion={region}>
<Polygon
coordinates={parkCoordinates}
fillColor="rgba(0, 200, 0, 0.3)"
strokeColor="rgba(0, 200, 0, 0.8)"
strokeWidth={3}
tappable={true}
onPress={() => console.log('Park tapped')}
/>
</MapView>
);
}
Use Cases for Polygons:
Handle large datasets efficiently:
import MapView, { Marker } from 'react-native-maps';
import MapViewClusterer from 'react-native-maps-super-cluster';
import { useState } from 'react';
export default function ClusteredMarkersScreen() {
const [locations] = useState([
// Imagine 1000+ markers here
{ id: 1, location: { latitude: 37.78825, longitude: -122.4324 } },
{ id: 2, location: { latitude: 37.78835, longitude: -122.4325 } },
// ... many more
]);
return (
<MapView
style={styles.map}
initialRegion={region}
>
{/* Manual clustering logic */}
{locations.map((loc) => (
<Marker
key={loc.id}
coordinate={loc.location}
/>
))}
</MapView>
);
}
Clustering Best Practices:
react-native-maps-super-cluster💡 Tip: Start clustering when you have
>50markers visible. It dramatically improves performance and user experience.
Complete map with multiple overlay types:
import { useState } from 'react';
import MapView, { Marker, Circle, Polyline, Callout } from 'react-native-maps';
import { View, Text, Image, StyleSheet } from 'react-native';
export default function CompleteOverlayScreen() {
const [selectedPlace, setSelectedPlace] = useState(null);
const places = [
{
id: 1,
name: 'Coffee Shop',
coordinate: { latitude: 37.78825, longitude: -122.4324 },
rating: 4.5,
image: 'https://example.com/coffee.jpg',
},
{
id: 2,
name: 'Restaurant',
coordinate: { latitude: 37.7849, longitude: -122.4294 },
rating: 4.8,
image: 'https://example.com/restaurant.jpg',
},
];
const walkingRoute = [
{ latitude: 37.78825, longitude: -122.4324 },
{ latitude: 37.7849, longitude: -122.4294 },
];
return (
<MapView
style={styles.map}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}}
>
{/* Places */}
{places.map((place) => (
<Marker
key={place.id}
coordinate={place.coordinate}
onPress={() => setSelectedPlace(place)}
>
<View style={styles.customMarker}>
<Image
source={{ uri: place.image }}
style={styles.markerImage}
/>
<Text style={styles.markerRating}>⭐ {place.rating}</Text>
</View>
<Callout>
<View style={styles.callout}>
<Text style={styles.calloutTitle}>{place.name}</Text>
<Text>Tap for details</Text>
</View>
</Callout>
</Marker>
))}
{/* Walking route */}
<Polyline
coordinates={walkingRoute}
strokeColor="#4A90E2"
strokeWidth={4}
lineDashPattern={[1, 10]}
/>
{/* Search radius */}
<Circle
center={places[0].coordinate}
radius={300}
fillColor="rgba(74, 144, 226, 0.2)"
strokeColor="rgba(74, 144, 226, 0.6)"
strokeWidth={2}
/>
</MapView>
);
}
const styles = StyleSheet.create({
map: {
flex: 1,
},
customMarker: {
alignItems: 'center',
},
markerImage: {
width: 50,
height: 50,
borderRadius: 25,
borderWidth: 3,
borderColor: '#fff',
},
markerRating: {
backgroundColor: '#fff',
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 10,
fontSize: 10,
fontWeight: 'bold',
marginTop: -10,
},
callout: {
width: 150,
padding: 10,
},
calloutTitle: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 5,
},
});
| Pitfall | Solution |
|---|---|
| Too many markers cause lag | Use clustering for datasets >100 markers |
| Custom marker images not loading | Use require() for local images, cache remote images |
| Callouts not showing | Ensure <Callout> is direct child of <Marker> |
| Markers disappearing on zoom | Check coordinate validity, avoid NaN values |
| Overlays not tappable | Add tappable={true} and onPress handler |
In the next lesson, we'll explore Places API and Autocomplete, learning how to search for businesses, add place details, and implement powerful location search in your app.