DEV Community

Cover image for React Native Maps in Production: 7 Gotchas Nobody Warns You About
Hugo Rus
Hugo Rus

Posted on Originally published at rapidnative.com

React Native Maps in Production: 7 Gotchas Nobody Warns You About

Every tutorial makes react-native-maps look easy. npx expo install, drop a <MapView>, done. Then you ship, and you meet the real map. The one with API keys that fail only in TestFlight, permission prompts that Apple rejects, and marker lists that turn your framerate into a slideshow.

Here are the seven things that would have saved me a week of debugging on my first production map screen.

1. The blank map is almost always a height problem

// Won't render. Parent has no explicit height.
<View>
  <MapView style={{ flex: 1 }} />
</View>

// Works.
<View style={{ flex: 1 }}>
  <MapView style={StyleSheet.absoluteFill} />
</View>
Enter fullscreen mode Exit fullscreen mode

MapView needs an ancestor with a resolved height. flex: 1 inside a zero-height parent silently fails. StyleSheet.absoluteFill is the safest pattern.

2. initialRegion vs region: pick one and don't switch

Use initialRegion for uncontrolled maps where the user pans freely. Use region only if you have a genuine reason to re-center on every render. Otherwise you're fighting user gestures and the map feels broken.

3. API keys must be restricted per platform

An unrestricted "any bundle ID" key works in dev and gets rotated by Google abuse detection within a week of launch. Restrict:

  • iOS: by bundle identifier
  • Android: by package name + SHA-1 fingerprint

Do this on day 1. Rotating a key that's already in the wild is painful.

4. Location permission ordering matters

const fg = await Location.requestForegroundPermissionsAsync();
if (fg.status !== 'granted') return;
const bg = await Location.requestBackgroundPermissionsAsync();
Enter fullscreen mode Exit fullscreen mode

You cannot get background permission on iOS without foreground first. And do not request permission on app launch. Ask when the user taps a "show my location" button. Apple reviewers will reject an unexplained cold prompt.

5. Accuracy.BestForNavigation will kill your battery

Location.getCurrentPositionAsync({
  accuracy: Location.Accuracy.Balanced,  // 👈 default for 99% of apps
});
Enter fullscreen mode Exit fullscreen mode

BestForNavigation is for driving and running apps that literally need meter-level precision. It's roughly 10x the battery drain of Balanced. Apple reviewers do notice.

6. watchPositionAsync leaks if you don't clean it up

useEffect(() => {
  let sub: Location.LocationSubscription | undefined;
  (async () => {
    sub = await Location.watchPositionAsync(
      { accuracy: Location.Accuracy.Balanced, distanceInterval: 5 },
      (loc) => setPosition(loc.coords),
    );
  })();
  return () => sub?.remove();   // 👈 THIS line
}, []);
Enter fullscreen mode Exit fullscreen mode

Forget the sub?.remove() and every screen visit adds a permanent GPS listener. Users notice the battery drain within a day.

7. Every <Marker> is a native view

Above roughly 200 to 300 markers on Android, frames drop. Above 2,000, iOS runs out of memory. Two fixes:

  1. Cluster with react-native-map-clustering (a drop-in MapView replacement built on supercluster).
  2. Bitmap markers. Set image={require('./pin.png')} instead of rendering React children. Native views for markers are cheap; React trees inside markers are expensive.

If you must render custom React markers, wrap them in React.memo and hoist all callbacks. Otherwise every pan re-renders every marker.

Bonus: expo-maps vs react-native-maps in 2026

Short version:

  • react-native-maps: huge community, works in Expo Go, needs a config plugin. The default choice.
  • expo-maps: SwiftUI and Jetpack Compose under the hood, faster startup, iOS 17+ only, no Expo Go. Good for greenfield apps.

For most teams shipping today, react-native-maps is still the safer bet, because every LLM, every Stack Overflow answer, and every AI code assistant still assumes it.

Speaking of which: the entire scaffold above (maps, permissions, markers, backend) can also be generated end to end from a prompt on RapidNative, if you'd rather skip the 400 lines of boilerplate and go straight to the differentiated part of your app.

Top comments (0)