DEV Community

Famitha M A
Famitha M A

Posted on

React Native Maps: Build Location Features That Actually Work in Production

TL;DR

  • Pick react-native-maps over expo-maps for most apps in 2026. The ecosystem (docs, answers, LLM snippets) is the real advantage.
  • A blank map is almost always an unsized parent view or an API key missing from the release build.
  • Request location permission on user action, never on launch. Apple rejects the splash-screen prompt.
  • Always call remove() on watchPositionAsync subscriptions, or you leak GPS listeners and drain battery.
  • Above a few hundred markers, cluster. Custom <Marker> children are the #1 cause of slow maps.

Adding a map to a React Native app looks easy until you ship it. Then you meet API keys that only fail in release builds, permission prompts that fail review, and marker lists that turn your framerate into a slideshow. Here's how react-native-maps actually behaves in production.

What react-native-maps actually is

It's a cross-platform library that renders native map views: Google Maps on Android, and Apple Maps or Google Maps on iOS. You get a declarative MapView plus Marker, Polyline, Polygon, and Circle. Gestures, tiles, and rendering all run natively, not in JS.

Why it's the default: it works in Expo Go, it was originally built at Airbnb, and it has years of production mileage behind it.

react-native-maps vs expo-maps

Expo shipped expo-maps, built on SwiftUI and Jetpack Compose. The honest comparison:

Concern react-native-maps expo-maps
Providers Google + Apple (both on iOS) Apple on iOS, Google on Android
Expo Go Yes No (dev client / EAS build)
Setup Config plugin + API keys Config plugin, cleaner defaults
Community Huge, years of answers Small, growing
iOS target Older versions supported Newer iOS versions
Native feel Good Excellent
Many markers Needs clustering Similar limits

My take: start with react-native-maps unless you need the newest platform look. When you hit a weird bug at 11pm before release, the ecosystem matters more than anything else in this table.

Setup with Expo

npx expo install react-native-maps expo-location
Enter fullscreen mode Exit fullscreen mode

Keys come from the Google Maps Platform and go in app.json:

{
  "expo": {
    "ios": {
      "config": { "googleMapsApiKey": "YOUR_IOS_KEY" },
      "infoPlist": {
        "NSLocationWhenInUseUsageDescription": "We show nearby places on a map.",
        "NSLocationAlwaysAndWhenInUseUsageDescription": "We sync your route in the background so you can see the full trip."
      }
    },
    "android": {
      "config": { "googleMaps": { "apiKey": "YOUR_ANDROID_KEY" } },
      "permissions": ["ACCESS_FINE_LOCATION", "ACCESS_COARSE_LOCATION"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Gotchas, in the order they'll bite you:

  1. Restrict keys per platform. iOS by bundle ID, Android by SHA-1. Unrestricted keys are an abuse risk once you're live.
  2. Blank map in TestFlight but fine in dev? The key isn't wired into the release build. Rebuild after any app.json change.
  3. Every NSLocation* string must explain why in user language. "We need your location" gets rejected.

Rendering a map and markers

import MapView, { Marker, PROVIDER_GOOGLE } from 'react-native-maps';
import { StyleSheet, View } from 'react-native';

export default function LocationScreen() {
  return (
    <View style={{ flex: 1 }}>
      <MapView
        provider={PROVIDER_GOOGLE}
        style={StyleSheet.absoluteFill}
        initialRegion={{
          latitude: 37.78825,
          longitude: -122.4324,
          latitudeDelta: 0.0922,
          longitudeDelta: 0.0421,
        }}
      >
        <Marker
          coordinate={{ latitude: 37.78825, longitude: -122.4324 }}
          title="Union Square"
          description="Downtown San Francisco"
        />
      </MapView>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Things that trip up first builds:

  • initialRegion vs region. Use initialRegion for maps users pan freely. region re-centers on every render and fights gestures.
  • Deltas set zoom, not radius. Roughly 0.01 = city block, 0.1 = neighborhood, 1.0 = metro. There's no zoom prop.
  • PROVIDER_GOOGLE forces Google tiles on iOS. Omit it to fall back to Apple Maps.
  • Unsized parent = zero-height map. This is the #1 "my map is blank" bug.

Location permissions without getting rejected

import * as Location from 'expo-location';

async function getCurrentPosition() {
  const { status } = await Location.requestForegroundPermissionsAsync();
  if (status !== 'granted') {
    return null; // Show a soft explainer, not a hard error.
  }

  const location = await Location.getCurrentPositionAsync({
    accuracy: Location.Accuracy.Balanced,
  });

  return location.coords;
}
Enter fullscreen mode Exit fullscreen mode

Three rules:

  1. Never request on launch. Trigger it from a "Show my location" button or a screen that visibly needs it.
  2. Foreground before background. iOS enforces the order.
  3. Accuracy.Balanced is the right default. BestForNavigation is for driving/running apps and eats battery.

Live tracking, with the cleanup everyone forgets:

import { useEffect, useState } from 'react';
import * as Location from 'expo-location';

export function useLivePosition() {
  const [position, setPosition] = useState<Location.LocationObjectCoords | null>(null);

  useEffect(() => {
    let subscription: Location.LocationSubscription | undefined;
    (async () => {
      subscription = await Location.watchPositionAsync(
        { accuracy: Location.Accuracy.Balanced, timeInterval: 3000, distanceInterval: 5 },
        (location) => setPosition(location.coords),
      );
    })();
    return () => subscription?.remove(); // skip this and you leak GPS listeners
  }, []);

  return position;
}
Enter fullscreen mode Exit fullscreen mode

Routes, polylines, and geofences

import { Polyline, Circle } from 'react-native-maps';

<Polyline
  coordinates={route} // array of { latitude, longitude }
  strokeColor="#2563eb"
  strokeWidth={4}
/>

<Circle
  center={{ latitude, longitude }}
  radius={500} // meters
  fillColor="rgba(37, 99, 235, 0.15)"
  strokeColor="#2563eb"
/>
Enter fullscreen mode Exit fullscreen mode

react-native-maps doesn't compute routes. Call the Google Directions API or Apple MapKit Directions and feed the coordinates into a Polyline. react-native-maps-directions wraps the Google call, but check Google's billing before you scale.

Geofencing lives in expo-location's startGeofencingAsync. It uses OS-level geofence APIs and keeps working when the app is backgrounded.

Marker clustering: the performance cliff

Every Marker is a native view. A hundred is fine. A few hundred gets choppy on mid-range Android. Thousands can crash with out-of-memory errors.

Options, most pragmatic first:

  • react-native-map-clustering: drop-in MapView replacement using supercluster.
  • Server-side clustering: for very large datasets, cluster on the backend and send only the visible viewport.
  • Bitmap markers: an image prop pointing to a PNG is much cheaper than a custom React child.

If you do need custom children, memoize them:

import React, { memo } from 'react';
import { View, Text } from 'react-native';
import { Marker } from 'react-native-maps';

const PriceMarker = memo(function PriceMarker({
  coordinate,
  price,
}: {
  coordinate: { latitude: number; longitude: number };
  price: string;
}) {
  return (
    <Marker coordinate={coordinate} tracksViewChanges={false}>
      <View style={{ backgroundColor: '#2563eb', padding: 4, borderRadius: 6 }}>
        <Text style={{ color: 'white', fontWeight: '600' }}>{price}</Text>
      </View>
    </Marker>
  );
});
Enter fullscreen mode Exit fullscreen mode

A slow map is almost always a re-rendering-markers problem, not a tile problem. And test on a mid-range Android device, not your flagship iPhone.

Skipping the boilerplate

Wiring MapView, markers, permissions, routes, and the "location denied" state is a few hundred lines and half a day of "why is my map blank." It barely changes between a delivery app, a fitness app, and a real-estate app.

That's the kind of scaffold AI builders handle well. If you describe "a food delivery app where couriers see nearby orders on a map" to RapidNative, it generates an Expo project with react-native-maps, expo-location permission strings, and a marker list bound to a data model. It's real React Native code you can export and edit by hand, so you spend your time on the parts that differentiate your app.

FAQ

Why is my map blank? Check in order: parent view has no height, API key not in the release build, key restricted to the wrong bundle ID, config plugin missing in app.json.

Is react-native-maps free? The library is MIT licensed. Google Maps usage is billed through the Google Maps Platform beyond its free usage allowance. Apple Maps on iOS has no usage fee.

Does it work in Expo Go? Yes. expo-maps needs a custom dev client.

Background tracking? NSLocationAlwaysAndWhenInUseUsageDescription + UIBackgroundModes: location on iOS, ACCESS_BACKGROUND_LOCATION on Android, then startLocationUpdatesAsync with a registered task. Foreground permission first, and be ready to justify it in review.

Your turn

What's the worst map bug you've shipped? Blank TestFlight map, marker slideshow, or something weirder? Drop it in the comments, along with what you're building.

Top comments (0)