react-native-maps-skill
React Native Maps — Best Practices & Complete Guide
react-native-maps 1.26.20+ / Expo SDK 54/55 / React Native 0.81.1+ / New Architecture Compatible
Critical Rules
- Always use
npx expo install react-native-maps— ensures version compatibility with your Expo SDK. - Never commit API keys — use environment variables via
app.config.jswithprocess.env. - Always restrict API keys — Android: package name + SHA-1; iOS: bundle identifier; API: Maps SDK only.
- Always run
npx expo prebuild --cleanafter changing map config in app.json/app.config.js. - Use
initialRegionnotregionunless you need controlled map positioning —regioncauses re-renders on every pan. - Memoize marker arrays with
useMemo— recreating marker elements every render causes lag. - Use
React.memofor custom marker components — prevents unnecessary re-renders of all markers. - Use clustering for 50+ markers — install
react-native-map-clusteringto avoid frame drops. - Handle the
nullmap ref — always checkmapRef.currentbefore calling methods likeanimateToRegion. - Request location permissions before enabling
showsUserLocation— otherwise the map silently fails on iOS.
Version Compatibility
| react-native-maps | React Native | Expo SDK | New Architecture |
|---|---|---|---|
| 1.26.1+ | >= 0.81.1 | 54/55 | Supported |
| 1.26.0 | >= 0.76 | 53 | Supported |
| 1.14.0 - 1.20.1 | >= 0.74 | 50-52 | Old Arch only |
| < 1.14.0 | >= 0.64.3 | < 50 | Old Arch only |
Installation
# Expo (recommended — pins compatible version)
npx expo install react-native-maps
# For location features
npx expo install expo-location
TypeScript definitions are bundled — no @types package needed.
Quick Start
import React from 'react';
import MapView from 'react-native-maps';
import { StyleSheet, View } from 'react-native';
export default function MapScreen() {
return (
<View style={styles.container}>
<MapView
style={styles.map}
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1 },
map: { width: '100%', height: '100%' },
});
Map Providers
- Default (no provider prop): Apple Maps on iOS, Google Maps on Android
PROVIDER_GOOGLE: Google Maps on both platforms (requires API keys)
import MapView, { PROVIDER_GOOGLE } from 'react-native-maps';
<MapView provider={PROVIDER_GOOGLE} style={styles.map} />
Apple Maps requires no API key. Google Maps requires API keys for both platforms — see references/google-maps-setup.md.
API Key Configuration (Google Maps)
Configure via the Expo config plugin in app.config.js:
// app.config.js
import 'dotenv/config';
export default {
expo: {
plugins: [
[
'react-native-maps',
{
androidGoogleMapsApiKey: process.env.GOOGLE_MAPS_ANDROID_KEY,
iosGoogleMapsApiKey: process.env.GOOGLE_MAPS_IOS_KEY,
},
],
],
},
};
The old android.config.googleMaps.apiKey syntax is deprecated — always use the plugin config.
After changing config, rebuild:
npx expo prebuild --clean
npx expo run:android # or run:ios
For full setup details (Cloud Console, SHA-1, key restrictions), see references/google-maps-setup.md.
Core Components
Markers
import MapView, { Marker } from 'react-native-maps';
<MapView style={styles.map} initialRegion={region}>
<Marker
coordinate={{ latitude: 37.78825, longitude: -122.4324 }}
title="San Francisco"
description="A beautiful city"
/>
</MapView>
Custom marker views:
<Marker coordinate={coordinate}>
<View style={styles.customMarker}>
<Image source={require('./marker.png')} style={{ width: 40, height: 40 }} />
</View>
</Marker>
Use local images over remote URLs for markers — they load faster and avoid flicker.
Polylines & Polygons
import { Polyline, Polygon } from 'react-native-maps';
<Polyline
coordinates={routeCoords}
strokeColor="#000"
strokeWidth={3}
/>
<Polygon
coordinates={areaCoords}
fillColor="rgba(255, 0, 0, 0.3)"
strokeColor="red"
strokeWidth={2}
/>
Heatmaps, GeoJSON, Circles
See references/advanced-features.md for Heatmap, Geojson, Circle, Callout, Overlay, and custom tile layers.
Map Ref & Programmatic Control
const mapRef = useRef<MapView>(null);
// Animate to a region
mapRef.current?.animateToRegion(targetRegion, 1000);
// Fit map to show all coordinates
mapRef.current?.fitToCoordinates(coordinates, {
edgePadding: { top: 50, right: 50, bottom: 50, left: 50 },
animated: true,
});
User Location
import * as Location from 'expo-location';
// Request permissions first
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return;
// Then enable on map
<MapView
showsUserLocation={true}
followsUserLocation={true}
showsMyLocationButton={true}
/>
For iOS, add to app.json:
{
"expo": {
"ios": {
"infoPlist": {
"NSLocationWhenInUseUsageDescription": "Show your position on the map."
}
}
}
}
For real-time tracking and background location, see references/advanced-features.md.
Performance Optimization
- Memoize markers: Wrap marker arrays in
useMemokeyed on the data source - Use
React.memofor custom marker components - Cluster markers: Use
react-native-map-clusteringfor 50+ markers - Filter visible markers: Only render markers within the current region bounds
- Prefer native marker images over custom React views for large datasets
- Use
initialRegionoverregionto avoid controlled-component re-render overhead
See references/performance.md for detailed patterns and code examples.
Common Issues & Debugging
| Symptom | Likely Cause | Fix |
|---|---|---|
| Blank/gray map | Missing or invalid API key | Check key in plugin config, verify billing enabled |
| Gray map with logo | Wrong SHA-1 or bundle ID restriction | Regenerate key with correct restrictions |
| Crash on load | Missing prebuild after config change | npx expo prebuild --clean then rebuild |
| Custom markers invisible | Image not loaded or wrong size | Use local images, set explicit width/height |
| Dark mode unexpected | Android auto dark mode | Set userInterfaceStyle="light" on MapView |
| Build failure (Fabric) | Incompatible versions | Ensure react-native-maps >= 1.26.1 for SDK 54+ |
| Lag with many markers | Too many rendered components | Use clustering + memoization |
For detailed troubleshooting with step-by-step fixes, see references/troubleshooting.md.
Production Deployment
Pre-deployment checklist:
- API keys restricted to production package name / bundle ID
- Play Store SHA-1 (from App Signing) added to Android key
- Location permissions declared in app.json
- Tested on physical devices (maps don't work well in simulators)
- No hardcoded API keys in source code
For full EAS Build configuration and store submission, see references/production.md.
Reference Files
Read these for detailed guidance on specific topics:
| File | When to read |
|---|---|
references/google-maps-setup.md |
Setting up Google Cloud, API keys, SHA-1, key restrictions |
references/advanced-features.md |
Heatmaps, GeoJSON, location tracking, custom styling, clustering |
references/performance.md |
Marker optimization, clustering, lazy loading, animation patterns |
references/troubleshooting.md |
Debugging blank maps, crashes, build failures, marker issues |
references/production.md |
EAS Build config, store deployment, production API key setup |