React Native Maps and Geolocation: A Complete Guide

by Didin J. on Nov 19, 2025 React Native Maps and Geolocation: A Complete Guide

Learn React Native Maps and Geolocation with this complete guide. Build live tracking, markers, routes, and location features using Expo or Bare React Native.

Location-based features have become an essential part of modern mobile applications — from ride-hailing and delivery apps to social platforms and travel services. Whether you’re displaying nearby points of interest, tracking movement in real time, or showing directions between two coordinates, React Native provides powerful tools to integrate maps and geolocation into your applications.

In this tutorial, you’ll learn everything you need to build map-powered mobile apps using React Native, including:

  • Installing and configuring React Native Maps

  • Choosing the best geolocation library (Expo or Bare RN)

  • Requesting and handling location permissions

  • Displaying interactive maps

  • Showing the user’s current location

  • Adding markers and callouts

  • Drawing routes with polylines

  • Implementing live location tracking

  • Understanding background location considerations

  • Avoiding common pitfalls on Android & iOS

We will cover both Expo and Bare React Native workflows, so you can confidently build location features no matter which environment you prefer.

By the end of this guide, you will have a fully working map application that displays the user’s current location and allows you to add your own map features with ease.


Prerequisites

Before building map and location features in React Native, make sure your development environment is properly set up. Here’s what you’ll need:

1. Node.js and Package Manager

Install the latest LTS version of Node.js, which includes npm.
You may also use Yarn or pnpm depending on your preference.

Check your versions:

 
node -v
npm -v

 

2. React Native Development Environment

This tutorial supports two workflows:

  1. Expo Managed Workflow
    Ideal for beginners, rapid prototyping, and simpler projects.
    Requires:

     
    npm install -g expo-cli

     

  2. Bare React Native Workflow
    Recommended for advanced or production-ready apps that need native modules and customizations.

If you're using Expo

  • Install the Expo CLI.

  • An Android/iOS simulator or Expo Go app on your device.

If you're using Bare React Native

Make sure you’ve installed:

  • Android Studio (SDK + Emulator)

  • Xcode (for iOS development, macOS only)

  • CocoaPods (for iOS dependencies)

3. Android & iOS Setup Requirements

Working with maps requires some platform-level setup:

Android Requirements

  • Google Play Services installed on emulator/device

  • A Google Maps API Key (we’ll configure this later)

iOS Requirements

  • Minimum supported version: iOS 11 (recommended: iOS 12+)

  • CocoaPods installed:

     
    sudo gem install cocoapods

     

4. Basic JavaScript & React Native Knowledge

You should be comfortable with:

  • React hooks (useState, useEffect)

  • Components & props

  • Basic navigation (optional)

Nothing too advanced — we will guide you step-by-step.


Choosing the Right Packages (Expo vs Bare React Native)

React Native offers multiple options when working with maps and geolocation. The package you choose will depend on whether you’re building with Expo or using the Bare React Native CLI.

In this section, we’ll break down the recommended packages for each workflow and explain when to choose each one.

1. Mapping Library: react-native-maps

No matter which workflow you use, the primary library for displaying maps in React Native is:

react-native-maps

This library supports:

  • Google Maps (Android & iOS)

  • Apple Maps (iOS)

  • Marker & Callout components

  • Polylines, Circles, Polygons

  • Custom map styling

  • Map events (region change, tap, long press, etc.)

It is widely used, actively maintained, and compatible with both Expo and Bare React Native.

2. Geolocation Options

React Native does not include built-in geolocation support in newer versions. You must install a separate geolocation package.

Here are the best options:

A. Expo (Managed Workflow)

Use:

  1. expo-location
    Provides:

    • Current location

    • Continuous location updates

    • Geofencing (limited)

    • Reverse geocoding

    • Permission handling

  2. react-native-maps
    Supports:

    • Standard and Google-provided maps

    • Full compatibility with Expo

Recommended for beginners or lightweight apps.

B. Bare React Native Workflow

You have two strong options:

  1. react-native-geolocation-service (Recommended)

    • Highly accurate

    • Supports background mode with optional configuration

    • More reliable on Android devices

    • Works well with Google Play Services

    • Supports newer React Native architectures

  2. @react-native-community/geolocation

    • Community-driven

    • Good for basic foreground needs

If you need:

  • Background tracking

  • High accuracy

  • Production-grade movement tracking
    → Use react-native-geolocation-service.

3. Summary: Which One Should You Choose?

Workflow Maps Library Geolocation Library Difficulty Best For
Expo Managed react-native-maps expo-location ★☆☆ (Easy) Beginners, rapid prototyping
Bare RN react-native-maps react-native-geolocation-service ★★☆ (Moderate) Production apps needing accuracy
Bare RN (Simple) react-native-maps @react-native-community/geolocation ★★☆ Basic apps without heavy location requirements

4. What We Will Use in This Tutorial

To ensure every developer can follow along, this tutorial will include both approaches:

  • Expo Version (for fast setup)

  • Bare React Native Version (for deeper native-level control)

Each main section will include commands and code snippets for both where applicable.


Installing React Native Maps & Geolocation

In this section, we’ll set up the required packages for both Expo and Bare React Native environments. Follow the path that matches your workflow. After installation, we’ll handle platform-specific configuration for Android and iOS.

1. Installing in Expo (Managed Workflow)

Expo makes installation straightforward since native dependencies are precompiled.

Step 1: Create a New Expo Project

npx create-expo-app rn-maps-demo
cd rn-maps-demo

Step 2: Install Map and Location Packages

npx expo install react-native-maps expo-location

Expo will automatically install versions compatible with your current Expo SDK.

2. Installing in Bare React Native

If you're using the React Native CLI, you must install all native modules manually and run pod install for iOS.

Step 1: Create a New Project

npx @react-native-community/cli init RNMapsDemo
cd RNMapsDemo

Step 2: Install Required Packages

Install the map library:

yarn add react-native-maps

Install geolocation library (recommended):

yarn add react-native-geolocation-service react-native-permissions

or, for a simpler setup:

yarn add @react-native-community/geolocation

Step 3: Install iOS Pods

If you’re on macOS:

cd ios && pod install && cd ..

This ensures native dependencies like GoogleMaps and CoreLocation are properly linked.

3. Android Configuration (Bare React Native Only)

Expo does this automatically — skip if using Expo.

Step 1: Add Google Maps API Key

Open:

android/app/src/main/AndroidManifest.xml

Add inside the <application> tag:

<meta-data
  android:name="com.google.android.geo.API_KEY"
  android:value="YOUR_GOOGLE_MAPS_API_KEY"/>

Ensure that you replace the placeholder with a valid API key.

Step 2: Add Required Permissions

Inside the same file:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

If you plan to support background tracking:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

4. iOS Configuration (Bare React Native Only)

Step 1: Add Location Permission Messages

Open:

ios/RNMapsDemo/Info.plist

Add:

<key>NSLocationWhenInUseUsageDescription</key>
<string>This app requires your location to display it on the map.</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need your location to track movement even in background.</string>

At a minimum, NSLocationWhenInUseUsageDescription is required.

Step 2: Configure Google Maps (Optional)

If you want Google Maps on iOS instead of Apple Maps:

  1. Open AppDelegate.m or AppDelegate.mm.

  2. Add:

#import <GoogleMaps/GoogleMaps.h>

Add in didFinishLaunchingWithOptions:

[GMSServices provideAPIKey:@"YOUR_GOOGLE_MAPS_API_KEY"];

5. Verifying the Setup

Run your project:

Expo:

npx expo start

Use:

  • Expo Go app

  • Web

  • iOS simulator

  • Android emulator

Bare React Native:

npx react-native run-android
# or
npx react-native run-ios

If the app builds and launches successfully, you're ready to move on to showing your first map.


Displaying Your First Map (MapView Basics)

With all dependencies installed and your environment configured, it’s time to render your first map. In this section, you’ll learn how to:

  • Import and use the MapView component

  • Set an initial region (latitude, longitude, zoom)

  • Display a full-screen map

  • Use a simple marker

We’ll provide examples for both Expo and Bare React Native, but the code is nearly identical.

1. Understanding the MapView Component

MapView is the core component of react-native-maps. It supports:

  • Standard map view

  • Google Maps provider

  • Custom coordinates

  • User location display

  • Zoom, tilt, scroll interactions

  • Map events (onPress, onRegionChange, etc.)

The most important props are:

Prop Description
initialRegion Sets the map's initial location and zoom
region Makes the map controlled (syncs latitude/longitude with state)
showsUserLocation Enables the blue dot for user location
provider Choose Google Maps explicitly (PROVIDER_GOOGLE)

2. Creating a Simple Full-Screen Map

Create a new screen or edit App.tsx.

Example (Works in Both Expo & Bare RN)

import React from "react";
import { StyleSheet, View } from "react-native";
import MapView, { Region } from "react-native-maps";

export default function App() {
  const initialRegion: Region = {
    latitude: -6.200000,
    longitude: 106.816666,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  };

  return (
    <View style={styles.container}>
      <MapView
        style={styles.map}
        initialRegion={initialRegion}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  map: { flex: 1 },
});

3. Using Google Maps Provider (Optional)

By default:

  • Android → Google Maps

  • iOS → Apple Maps

If you want to force Google Maps on iOS:

import { PROVIDER_GOOGLE } from 'react-native-maps';

<MapView
  provider={PROVIDER_GOOGLE}
  style={styles.map}
  initialRegion={initialRegion}
/>

You must have configured your iOS AppDelegate with a Google Maps API key (covered in Section 4).

4. Adding a Simple Marker

Let’s drop a pin at a specific coordinate.

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

<MapView style={styles.map} initialRegion={initialRegion}>
  <Marker
    coordinate={{ latitude: -6.200000, longitude: 106.816666 }}
    title="Jakarta"
    description="Capital of Indonesia"
  />
</MapView>

You should now see a pin on the map with a callout when tapped.

5. Handling Basic Map Events

You can capture user interactions:

Tap Event

<MapView
  style={styles.map}
  initialRegion={initialRegion}
  onPress={(e) => {
    const { latitude, longitude } = e.nativeEvent.coordinate;
    console.log("Map pressed at:", latitude, longitude);
  }}
/>

This will log coordinates where the user taps.

6. Using a Controlled Region (Optional)

If you want to move the map programmatically:

const [region, setRegion] = useState(initialRegion);

<MapView
  style={styles.map}
  region={region}
  onRegionChangeComplete={setRegion}
/>

This makes the map behave like a controlled component in React.

You now have a fully working map on your screen.


Showing the User’s Current Location (TSX)

Displaying the user’s current position is one of the most common requirements for map-based apps. In React Native, you’ll typically do this by:

  1. Requesting location permissions

  2. Getting the user’s coordinates

  3. Updating the map region

  4. Enabling the built-in blue location dot (optional)

In this section, you’ll learn how to do this in both Expo (expo-location) and Bare React Native (react-native-geolocation-service) — all in TypeScript (.tsx).

1. Using Expo (expo-location) — TypeScript Example

Expo provides the simplest way to access the device’s location.

Installation Reminder (if needed)

npx expo install expo-location react-native-maps

Full Example: Get and Show Current User Location

Create or edit App.tsx:

import React, { useEffect, useState } from "react";
import { StyleSheet, View, ActivityIndicator, Text } from "react-native";
import MapView, { Marker, Region } from "react-native-maps";
import * as Location from "expo-location";

export default function App() {
  const [region, setRegion] = useState<Region | null>(null);
  const [errorMsg, setErrorMsg] = useState<string | null>(null);

  useEffect(() => {
    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();

      if (status !== "granted") {
        setErrorMsg("Permission to access location was denied");
        return;
      }

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

      setRegion({
        latitude: location.coords.latitude,
        longitude: location.coords.longitude,
        latitudeDelta: 0.01,
        longitudeDelta: 0.01,
      });
    })();
  }, []);

  if (errorMsg) {
    return (
      <View style={styles.center}>
        <Text>{errorMsg}</Text>
      </View>
    );
  }

  if (!region) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <MapView
      style={styles.map}
      initialRegion={region}
      showsUserLocation={true}
    >
      <Marker coordinate={region} title="You are here" />
    </MapView>
  );
}

const styles = StyleSheet.create({
  map: { flex: 1 },
  center: { flex: 1, justifyContent: "center", alignItems: "center" },
});

What this does:

✔ Requests permission
✔ Gets current GPS coordinates
✔ Moves the map to the user’s location
✔ Shows a custom marker AND the native blue dot

2. Using Bare React Native (react-native-geolocation-service) — TypeScript Example

For non-Expo apps, react-native-geolocation-service is the most reliable option.

Installation Reminder

yarn add react-native-geolocation-service react-native-permissions
cd ios && pod install && cd ..

You MUST request permissions manually (as shown in Section 4).

3. Full Example (TSX): Getting User Location

Create App.tsx:

import React, { useEffect, useState } from "react";
import { PermissionsAndroid, Platform, StyleSheet, View, ActivityIndicator, Text } from "react-native";
import MapView, { Marker, Region } from "react-native-maps";
import Geolocation, { GeoPosition } from "react-native-geolocation-service";

export default function App() {
  const [region, setRegion] = useState<Region | null>(null);
  const [errorMsg, setErrorMsg] = useState<string | null>(null);

  const requestPermission = async (): Promise<boolean> => {
    if (Platform.OS === "ios") {
      return true; // iOS handled via Info.plist
    }

    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
    );

    return granted === PermissionsAndroid.RESULTS.GRANTED;
  };

  useEffect(() => {
    (async () => {
      const hasPermission = await requestPermission();

      if (!hasPermission) {
        setErrorMsg("Location permission denied");
        return;
      }

      Geolocation.getCurrentPosition(
        (pos: GeoPosition) => {
          const { latitude, longitude } = pos.coords;
          setRegion({
            latitude,
            longitude,
            latitudeDelta: 0.01,
            longitudeDelta: 0.01,
          });
        },
        (error) => setErrorMsg(error.message),
        { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
      );
    })();
  }, []);

  if (errorMsg) {
    return (
      <View style={styles.center}>
        <Text>{errorMsg}</Text>
      </View>
    );
  }

  if (!region) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <MapView
      style={styles.map}
      initialRegion={region}
      showsUserLocation={true}
    >
      <Marker coordinate={region} title="You are here" />
    </MapView>
  );
}

const styles = StyleSheet.create({
  map: { flex: 1 },
  center: { flex: 1, justifyContent: "center", alignItems: "center" },
});

4. Showing User Location with the Native Blue Dot

The following works in both Expo and Bare RN:

<MapView
  style={styles.map}
  showsUserLocation={true}
  followsUserLocation={true}  // optional: auto-center on user
/>

5. Tip: Extracting Logic Into a Custom Hook (Recommended)

To keep components clean, you can create a custom hook:

/src/hooks/useUserLocation.ts

import { useEffect, useState } from "react";
import * as Location from "expo-location";
import { Region } from "react-native-maps";

export const useUserLocation = () => {
    const [region, setRegion] = useState<Region | null>(null);

    useEffect(() => {
        (async () => {
            const { status } = await Location.requestForegroundPermissionsAsync();
            if (status !== "granted") return;

            const loc = await Location.getCurrentPositionAsync({});
            setRegion({
                latitude: loc.coords.latitude,
                longitude: loc.coords.longitude,
                latitudeDelta: 0.01,
                longitudeDelta: 0.01,
            });
        })();
    }, []);

    return region;
};

Then use:

const region = useUserLocation();

Cleaner, reusable, and scalable.

Summary

You learned how to:

  • Request location permissions (Expo + Bare RN)

  • Get current location in TypeScript

  • Display a map centered on the user

  • Show a marker and the blue location dot

  • Implement optional tracking and hooks


Markers, Callouts, and Clustering (TSX)

Now that your map is rendering and centered on the user’s location, the next step is to add markers, callouts, and optionally marker clustering. These features let you display points of interest, show custom icons, and manage large datasets efficiently.

All examples in this section are written in TypeScript (.tsx) and work in both Expo and Bare React Native unless otherwise stated.

1. Adding Basic Markers (TSX)

Markers represent a single point on the map.

Example: A Simple Marker

import MapView, { Marker } from "react-native-maps";

<MapView style={{ flex: 1 }} initialRegion={initialRegion}>
  <Marker
    coordinate={{ latitude: -6.2, longitude: 106.816666 }}
    title="Jakarta"
    description="Capital of Indonesia"
  />
</MapView>

Marker Properties:

  • coordinate: latitude + longitude

  • title: callout title

  • description: callout subtitle

  • pinColor: change pin color

  • image: custom marker icon

  • draggable: allow marker dragging

2. Using a Custom Marker Icon

You can replace the default pin with your own image.

Example structure:

/assets/
   custom-pin.png

TSX Example:

<Marker
  coordinate={{ latitude: -6.2, longitude: 106.816666 }}
  title="Custom Pin"
  image={require("../assets/custom-pin.png")}
/>

Make sure your image is a small PNG (48x48 or 64x64 recommended).

3. Interactive Callouts (TSX)

Callouts appear when a marker is tapped. You can fully customize them.

Example: Custom Callout Content

import { Callout } from "react-native-maps";
import { View, Text, StyleSheet } from "react-native";

<Marker coordinate={{ latitude: -6.2, longitude: 106.816666 }}>
  <Callout tooltip>
    <View style={styles.callout}>
      <Text style={styles.title}>Jakarta</Text>
      <Text style={styles.subtitle}>Capital of Indonesia</Text>
    </View>
  </Callout>
</Marker>

Styles:

const styles = StyleSheet.create({
  callout: {
    backgroundColor: "#fff",
    padding: 10,
    borderRadius: 8,
    elevation: 4,
  },
  title: {
    fontWeight: "bold",
    marginBottom: 4,
  },
  subtitle: {
    color: "#555",
  },
});

Using tooltip gives a cleaner pop-up without the default bubble pointer.

4. Multiple Markers from an Array

In real apps, you’ll load pin data from APIs or local files.

TypeScript Interface:

interface Place {
  id: string;
  title: string;
  latitude: number;
  longitude: number;
}

Marker List Example:

const places: Place[] = [
  { id: "1", title: "Monas", latitude: -6.1754, longitude: 106.8272 },
  { id: "2", title: "Kota Tua", latitude: -6.1352, longitude: 106.8133 },
];

<MapView style={{ flex: 1 }} initialRegion={initialRegion}>
  {places.map((place) => (
    <Marker
      key={place.id}
      coordinate={{
        latitude: place.latitude,
        longitude: place.longitude,
      }}
      title={place.title}
    />
  ))}
</MapView>

5. Draggable Markers

Useful for allowing users to select a location (e.g., choosing a delivery drop-off).

<Marker
  coordinate={markerPosition}
  draggable
  onDragEnd={(e) => {
    const { latitude, longitude } = e.nativeEvent.coordinate;
    setMarkerPosition({ latitude, longitude });
  }}
/>

6. Marker Clustering (Highly Recommended for Many Points)

When you have 50+ markers, clustering is essential for performance and usability.

The most popular library is:

react-native-map-clustering

Installation

Expo:

npx expo install react-native-map-clustering

Bare RN:

yarn add react-native-map-clustering
cd ios && pod install && cd ..

Usage (TSX)

import MapViewCluster from "react-native-map-clustering";
import { Marker } from "react-native-maps";

<MapViewCluster
  style={{ flex: 1 }}
  initialRegion={initialRegion}
  radius={40} // cluster radius
>
  {places.map((place) => (
    <Marker
      key={place.id}
      coordinate={{
        latitude: place.latitude,
        longitude: place.longitude,
      }}
      title={place.title}
    />
  ))}
</MapViewCluster>

Features:

✔ Automatic clustering
✔ Smooth animations
✔ Configurable cluster size and appearance
✔ Works with TypeScript

7. Custom Cluster Rendering

If you want to style the cluster icons:

<MapViewCluster
  style={{ flex: 1 }}
  initialRegion={initialRegion}
  renderCluster={(cluster) => (
    <Marker coordinate={cluster.geometry.coordinates}>
      <View style={styles.cluster}>
        <Text style={styles.clusterText}>{cluster.pointCount}</Text>
      </View>
    </Marker>
  )}
>
  {/* markers */}
</MapViewCluster>

Styles:

const styles = StyleSheet.create({
  cluster: {
    width: 40,
    height: 40,
    borderRadius: 20,
    backgroundColor: "#ff5722",
    justifyContent: "center",
    alignItems: "center",
  },
  clusterText: {
    color: "#fff",
    fontWeight: "bold",
  },
});

Summary

You learned how to:

  • Add markers (simple + custom icon)

  • Build custom callouts

  • Render multiple markers from an array

  • Drag markers to update coordinates

  • Use clustering to handle large datasets

Your map is now more interactive and ready for advanced features like routing or live tracking.


Drawing Routes with Polylines (TSX)

Routes are essential in many location-based apps — whether you're showing directions, mapping a delivery path, visualizing user movement, or connecting multiple points of interest.

In React Native Maps, routes are drawn using the Polyline component. With TypeScript, you’ll benefit from strong coordinate typing and safer rendering.

This section covers:

  • How to draw a simple polyline

  • Styling your polyline

  • Connecting multiple locations

  • Using Google Directions API to draw real routes

  • Decoding Google polyline strings

  • Rendering the final route on the map

Everything is written in TypeScript (.tsx) and works in both Expo and Bare RN.

1. Importing and Using Polyline

Polyline is included in react-native-maps.

Basic TSX imports

import MapView, { Polyline, LatLng, Region } from "react-native-maps";

2. Drawing a Simple Polyline Between Two Points

TypeScript Example

const points: LatLng[] = [
  { latitude: -6.1754, longitude: 106.8272 }, // Monas
  { latitude: -6.1352, longitude: 106.8133 }, // Kota Tua
];

<MapView style={{ flex: 1 }} initialRegion={initialRegion}>
  <Polyline
    coordinates={points}
    strokeWidth={4}
    strokeColor="#FF0000"
  />
</MapView>

Notes:

  • coordinates must be an array of { latitude: number; longitude: number }

  • strokeWidth determines thickness

  • strokeColor sets line color

3. Drawing More Complex Routes (Multiple Points)

You can connect multiple points easily:

const routeCoordinates: LatLng[] = [
  { latitude: -6.2, longitude: 106.8 },
  { latitude: -6.21, longitude: 106.82 },
  { latitude: -6.23, longitude: 106.85 },
  { latitude: -6.25, longitude: 106.87 },
];

<Polyline
  coordinates={routeCoordinates}
  strokeWidth={5}
  strokeColor="#1E90FF"
/>

4. Styling Your Polyline

Available Styling Props

Prop Description
strokeColor Line color
strokeWidth Line thickness
lineDashPattern Dotted or dashed patterns
lineCap Shape of line ends (butt / round / square)
geodesic Makes the line follow the curvature of Earth

Example: Dashed Route

<Polyline
  coordinates={routeCoordinates}
  strokeWidth={4}
  strokeColor="#4CAF50"
  lineDashPattern={[10, 5]}  // dash 10px, gap 5px
/>

5. Drawing Real Directions Using Google Directions API

This is the most powerful and widely used approach.

Step 1: Build a Directions API URL

Replace YOUR_API_KEY with your real Google Maps API key.

const buildDirectionsUrl = (
  origin: LatLng,
  destination: LatLng
) =>
  `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=YOUR_API_KEY`;

6. Decoding Google's Encoded Polyline

Google returns routes as encoded strings. Use a decoder to convert them into coordinates.

Install the decoder:

yarn add @mapbox/polyline

Import:

import polyline from "@mapbox/polyline";

7. Full Example: Fetch & Draw Google Directions Route (TSX)

import React, { useEffect, useState } from "react";
import { View, StyleSheet, ActivityIndicator } from "react-native";
import MapView, { Polyline, LatLng, Region } from "react-native-maps";
import polyline from "@mapbox/polyline";

const origin: LatLng = { latitude: -6.1754, longitude: 106.8272 }; // Monas
const destination: LatLng = { latitude: -6.1352, longitude: 106.8133 }; // Kota Tua

export default function DirectionsExample() {
  const [routeCoords, setRouteCoords] = useState<LatLng[]>([]);
  const [loading, setLoading] = useState(true);

  const initialRegion: Region = {
    latitude: origin.latitude,
    longitude: origin.longitude,
    latitudeDelta: 0.05,
    longitudeDelta: 0.05,
  };

  useEffect(() => {
    async function fetchRoute() {
      try {
        const url = `https://maps.googleapis.com/maps/api/directions/json?origin=${origin.latitude},${origin.longitude}&destination=${destination.latitude},${destination.longitude}&key=YOUR_API_KEY`;

        const response = await fetch(url);
        const json = await response.json();

        if (!json.routes.length) return;

        // Decode polyline
        const points = polyline.decode(
          json.routes[0].overview_polyline.points
        );

        const coords: LatLng[] = points.map(([lat, lng]) => ({
          latitude: lat,
          longitude: lng,
        }));

        setRouteCoords(coords);
      } catch (error) {
        console.error("Directions error:", error);
      } finally {
        setLoading(false);
      }
    }

    fetchRoute();
  }, []);

  if (loading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <MapView style={styles.map} initialRegion={initialRegion}>
      <Polyline
        coordinates={routeCoords}
        strokeWidth={5}
        strokeColor="#2196F3"
      />
    </MapView>
  );
}

const styles = StyleSheet.create({
  map: { flex: 1 },
  center: { flex: 1, justifyContent: "center", alignItems: "center" },
});

8. Drawing Real-Time Movement (e.g., Delivery Apps)

If you’re tracking a moving user/device:

  • Continuously update the polyline with new coordinates

  • Append incoming GPS points into an array

  • Re-render the Polyline

Minimal Example (TSX):

const [path, setPath] = useState<LatLng[]>([]);

useEffect(() => {
  const watchId = Geolocation.watchPosition(
    (position) => {
      const { latitude, longitude } = position.coords;
      setPath((prev) => [...prev, { latitude, longitude }]);
    },
    console.log,
    { enableHighAccuracy: true, distanceFilter: 5 }
  );

  return () => Geolocation.clearWatch(watchId);
}, []);

Then render:

<Polyline coordinates={path} strokeWidth={4} strokeColor="#FF5722" />

Perfect for:

  • Jogging apps

  • Delivery/tracking apps

  • Vehicle monitoring

Summary

You now know how to:

  • Draw simple and complex polylines

  • Style routes with color, width, and dash patterns

  • Fetch and decode Google Directions API routes

  • Render real-time movement routes

Your map can now display rich navigation features.


Real-Time Location Updates & Background Tracking (TSX)

In many real-world apps—delivery tracking, fitness tracking, transportation, on-demand services—you need continuous, real-time, and sometimes background location updates. This section teaches you how to:

  • Watch the user’s location in real-time

  • Track user movement and update polyline routes

  • Handle battery-efficient tracking

  • Enable background location (Expo & Bare RN)

  • Understand platform limitations (iOS vs Android)

All examples are written in TypeScript (.tsx) and compatible with both Expo and Bare React Native.

1. Real-Time Location Updates (Foreground Tracking)

Real-time tracking means listening to location changes using either:

  • Expo

    • Location.watchPositionAsync()

  • Bare RN

    • Geolocation.watchPosition()

Below are TSX examples for both.

2. Real-Time Tracking in Expo (TSX)

Example: Watch and Display User Movement on Map

import React, { useEffect, useState } from "react";
import { StyleSheet, View, ActivityIndicator } from "react-native";
import MapView, { Polyline, Marker, LatLng, Region } from "react-native-maps";
import * as Location from "expo-location";

export default function LiveTracking() {
  const [path, setPath] = useState<LatLng[]>([]);
  const [region, setRegion] = useState<Region | null>(null);

  useEffect(() => {
    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== "granted") return;

      const subscription = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.High,
          timeInterval: 2000,     // update every 2 seconds
          distanceInterval: 5,    // or every 5 meters
        },
        (location) => {
          const newPoint = {
            latitude: location.coords.latitude,
            longitude: location.coords.longitude,
          };

          setPath((prev) => [...prev, newPoint]);

          if (!region) {
            setRegion({
              latitude: newPoint.latitude,
              longitude: newPoint.longitude,
              latitudeDelta: 0.01,
              longitudeDelta: 0.01,
            });
          }
        }
      );

      return () => subscription.remove();
    })();
  }, []);

  if (!region) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <MapView style={styles.map} initialRegion={region} showsUserLocation={true}>
      <Polyline coordinates={path} strokeWidth={4} strokeColor="#e91e63" />
      {path.length > 0 && <Marker coordinate={path[path.length - 1]} title="Current Position" />}
    </MapView>
  );
}

const styles = StyleSheet.create({
  map: { flex: 1 },
  center: { flex: 1, justifyContent: "center", alignItems: "center" },
});

✔ Tracks movement
✔ Builds a live polyline path
✔ Maps auto-updates the user position

3. Real-Time Tracking in Bare React Native (react-native-geolocation-service)

TSX Example

import React, { useEffect, useState } from "react";
import { StyleSheet, View, ActivityIndicator } from "react-native";
import MapView, { Polyline, Marker, LatLng, Region } from "react-native-maps";
import Geolocation, { GeoPosition } from "react-native-geolocation-service";

export default function LiveTracking() {
  const [path, setPath] = useState<LatLng[]>([]);
  const [region, setRegion] = useState<Region | null>(null);

  useEffect(() => {
    const watchId = Geolocation.watchPosition(
      (pos: GeoPosition) => {
        const { latitude, longitude } = pos.coords;

        const newPoint = { latitude, longitude };

        setPath((prev) => [...prev, newPoint]);

        if (!region) {
          setRegion({
            latitude,
            longitude,
            latitudeDelta: 0.01,
            longitudeDelta: 0.01,
          });
        }
      },
      console.warn,
      {
        enableHighAccuracy: true,
        distanceFilter: 5,
        interval: 3000,
        fastestInterval: 2000,
      }
    );

    return () => Geolocation.clearWatch(watchId);
  }, []);

  if (!region) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <MapView style={styles.map} initialRegion={region} showsUserLocation={true}>
      <Polyline coordinates={path} strokeWidth={4} strokeColor="#2196f3" />
    </MapView>
  );
}

const styles = StyleSheet.create({
  map: { flex: 1 },
  center: { flex: 1, justifyContent: "center", alignItems: "center" },
});

✔ Best accuracy
✔ Works extremely well on Android
✔ Supports background tracking with additional setup

4. Improving Accuracy & Battery Efficiency

Recommended tracking settings:

High accuracy (more battery):

enableHighAccuracy: true,
distanceInterval: 1,

Balanced (recommended for delivery apps):

enableHighAccuracy: true,
distanceInterval: 5,

Battery saver mode:

5. Background Location (IMPORTANT)

Background location allows:

  • Tracking movement when the app is minimized

  • Delivery drivers to keep the route updated

  • Fitness apps to log running while the screen is off

But…

⚠️ Background location is heavily restricted by both iOS and Android.
⚠️ You MUST declare permissions properly, or your app may be rejected.

6. Background Tracking in Expo

Expo supports background location using:

import * as TaskManager from "expo-task-manager";
import * as Location from "expo-location";

Step 1: Define a Background Task

TaskManager.defineTask("location-tracking", ({ data, error }) => {
  if (error) return;

  const { locations } = data as any;
  console.log("Background location:", locations[0]);
});

Step 2: Start Background Updates

await Location.startLocationUpdatesAsync("location-tracking", {
  accuracy: Location.Accuracy.Highest,
  distanceInterval: 5,
  showsBackgroundLocationIndicator: true,
  foregroundService: {
    notificationTitle: "Tracking Your Location",
    notificationBody: "Location services active.",
  },
});

Step 3: Required App Config (app.json)

{
  "expo": {
    "plugins": [
      [
        "expo-location",
        {
          "locationAlwaysAndWhenInUsePermission": "Allow location access."
        }
      ]
    ]
  }
}

7. Background Tracking in Bare React Native

The easiest full-featured solution is:

react-native-background-geolocation

It provides:

  • Background mode

  • Geofencing

  • Headless tasks

  • Activity detection

  • Battery optimization

But in this tutorial, we will show the built-in OS background approach.

Android Setup

Add permission to AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

Foreground service declaration:

<service
  android:name=".LocationService"
  android:foregroundServiceType="location" />

iOS Setup

Add to Info.plist:

<key>UIBackgroundModes</key>
<array>
  <string>location</string>
</array>

iOS will show a blue indicator while tracking.

8. Best Practices for Background Tracking

✔ Inform users why location is needed
✔ Provide an in-app toggle to disable tracking
✔ Turn off tracking when not needed
✔ Avoid super-frequent updates—battery will drain fast
✔ Store location data efficiently (SQLite/local storage/server)

9. Combining Real-Time + Background Tracking

A robust implementation usually:

  1. Listens to real-time foreground updates

  2. Switches to background updates automatically

  3. Stores location into a state manager or backend

  4. Draws polyline route whenever app is opened

This is how apps like Gojek, Grab, Uber, and Strava operate.

Summary

You now know how to:

  • Track real-time position updates (foreground)

  • Build dynamic movement paths

  • Enable background location (Expo & Bare RN)

  • Configure iOS & Android permissions correctly

  • Handle accuracy + battery trade-offs

  • Build production-ready tracking systems

Your app is now capable of continuous movement tracking, both foreground and background.


Debugging & Common Gotchas

Working with maps and geolocation in React Native can sometimes be tricky due to native configurations, permissions, device limitations, and platform-specific behavior. This section covers the most common issues developers face—and how to fix them quickly.

Use this as your troubleshooting reference when something "just doesn't work."

1. The Map is Not Showing (Blank / Gray Screen)

This is the most common issue with react-native-maps.

Common Causes & Fixes

1. Google Maps API Key is Missing (Android)

If you see a blank map on Android:

✔ Check your AndroidManifest.xml:

<meta-data
  android:name="com.google.android.geo.API_KEY"
  android:value="YOUR_API_KEY"/>

❗ Without this, Android renders a blank grid.

2. No iOS API Key When Using PROVIDER_GOOGLE

If using Google Maps on iOS:

Check AppDelegate.mm:

[GMSServices provideAPIKey:@"YOUR_API_KEY"];

Otherwise, the map may not load.

3. Simulator Doesn’t Support Maps Properly

Sometimes Android emulators lack Google Play Services.

✔ Create an emulator using an image labeled "Google Play Image"
✔ Avoid AOSP images—they do not support Maps

4. Region Settings Missing

If you don't pass initialRegion or region, the map may not center properly.

Add:

initialRegion={initialRegion}

5. Offline Device or No Internet

Google Maps requires the internet for initial tile load.

2. User Location Not Loading

Symptoms:

  • Blue dot does not appear

  • Location stays on “loading”

  • Permission denied errors

Fixes:

1. Missing iOS Permissions (Info.plist)

Add:

<key>NSLocationWhenInUseUsageDescription</key>
<string>We need your location to show the map.</string>

2. Missing Android Permissions

Add to AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

For background tracking:

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

3. Don't Forget to Request Runtime Permission (Android)

Bare RN needs:

await PermissionsAndroid.request(
  PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
);

Expo requests permission automatically via:

Location.requestForegroundPermissionsAsync();

4. Simulator Has No Location

iOS Simulator:
✔ Debug → Location → Custom Location
✔ Or choose “City Bicycle Ride”

Android Emulator:
✔ Click "..." → "Location" → Set coordinates

3. Markers Not Appearing

Reasons:

1. Coordinates are undefined

Check that you log your coordinates:

 
console.log(latitude, longitude);

 

2. Marker is outside the visible region

Use fitToCoordinates():

 
mapRef.current.fitToCoordinates(points);

 

4. Polyline Not Showing

1. Coordinates array is empty

Verify length:

 
console.log(routeCoords.length);

 

2. Incorrect type

Ensure TypeScript uses:

 
LatLng[] = [{ latitude: number, longitude: number }]

 

3. Polyline out of map region

Adjust your region or call fitToCoordinates().

5. Google Directions API Not Working

Common errors:

1. REQUEST_DENIED

Your API key might be missing proper APIs:

✔ Enable these in Google Cloud Console:

  • Maps SDK for Android

  • Maps SDK for iOS

  • Directions API

2. Billing Not Enabled

Google Directions API requires billing, even for the free tier.

3. Invalid Polyline Error

Make sure you decode correctly:

polyline.decode(json.routes[0].overview_polyline.points);

6. Slow Map Performance

Tips:

✔ Use marker clustering
✔ Avoid rendering 100+ <Marker> elements directly
✔ Debounce calls to onRegionChange
✔ Use static map tiles where possible
✔ Ensure images for markers are optimized (small PNGs)

7. Location Accuracy Issues

1. Android Power Saving Mode

Some Android devices restrict background updates unless:

  • Battery optimization is disabled

  • App has location permission “Allow all the time”

2. iOS Simulator

iOS Simulator often has inaccurate or static GPS data.

3. enableHighAccuracy

Set:

 
enableHighAccuracy: true

 

But note this increases battery usage.

8. App Crashes on iOS After Adding Maps

Most common cause:

Missing CocoaPods Reinstall

After installation:

cd ios && pod install && cd ..

9. Location Updates Stop in Background

Fixes:

Expo:

✔ Must register a task via TaskManager.defineTask()
✔ Must enable background mode in app.json

Bare RN:

✔ Add UIBackgroundModes: location (iOS)
✔ Add foreground service (Android)
✔ Use high-quality libraries like react-native-background-geolocation

10. Android Foreground Service Crash

If using background tracking on Android:

Missing service in AndroidManifest.xml:

<service
  android:name=".LocationService"
  android:foregroundServiceType="location" />

Summary

In this section, you learned how to fix:

  • Blank maps

  • Missing API keys

  • Permission failures

  • Invisible markers

  • Polyline rendering problems

  • Poor tracking accuracy

  • Background location issues

  • Android/iOS platform quirks

With these fixes, you can debug nearly any issue in React Native Maps or geolocation.


Example App (Full TSX Code)

This section provides a complete, ready-to-run React Native Maps + Geolocation app written entirely in TypeScript (.tsx). It includes:

  • Permission handling

  • Fetching the user’s current location

  • Real-time movement tracking

  • A polyline showing the movement path

  • A marker for the current position

  • Universal compatibility (Expo & Bare RN)

You can drop this into an Expo project or adapt it for Bare React Native with only small permission changes.

1. Folder Structure (Recommended)

 
/src
  /hooks
    useUserLocation.ts
  /components
    MapTracker.tsx
App.tsx

 

This keeps the app modular and scalable.
Below is the complete code for each piece.

2. /src/hooks/useUserLocation.ts (TSX Custom Hook)

Works for Expo. For Bare RN, a variant is provided below.

import { useEffect, useState } from "react";
import * as Location from "expo-location";
import { Region, LatLng } from "react-native-maps";

interface UseUserLocation {
  region: Region | null;
  path: LatLng[];
}

export function useUserLocation(): UseUserLocation {
  const [region, setRegion] = useState<Region | null>(null);
  const [path, setPath] = useState<LatLng[]>([]);

  useEffect(() => {
    (async () => {
      const { status } = await Location.requestForegroundPermissionsAsync();
      if (status !== "granted") return;

      const subscription = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.High,
          timeInterval: 2000,
          distanceInterval: 5,
        },
        (location) => {
          const { latitude, longitude } = location.coords;

          const newPoint: LatLng = {
            latitude,
            longitude,
          };

          setPath((prev) => [...prev, newPoint]);

          if (!region) {
            setRegion({
              latitude,
              longitude,
              latitudeDelta: 0.01,
              longitudeDelta: 0.01,
            });
          }
        }
      );

      return () => subscription.remove();
    })();
  }, []);

  return { region, path };
}

3. /src/components/MapTracker.tsx

A reusable map component that displays:

  • Current user marker

  • Polyline movement path

  • Real-time location updates

import React from "react";
import { StyleSheet, View, ActivityIndicator } from "react-native";
import MapView, { Polyline, Marker } from "react-native-maps";
import { useUserLocation } from "../hooks/useUserLocation";

export default function MapTracker() {
  const { region, path } = useUserLocation();

  if (!region) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <MapView
      style={styles.map}
      initialRegion={region}
      showsUserLocation={true}
      followsUserLocation={true}
    >
      {path.length > 1 && (
        <Polyline
          coordinates={path}
          strokeWidth={4}
          strokeColor="#e91e63"
        />
      )}

      {path.length > 0 && (
        <Marker
          coordinate={path[path.length - 1]}
          title="Current Position"
        />
      )}
    </MapView>
  );
}

const styles = StyleSheet.create({
  map: { flex: 1 },
  center: { flex: 1, justifyContent: "center", alignItems: "center" },
});

4. App.tsx — Main Entry Point (Expo)

import React from "react";
import { StatusBar } from "expo-status-bar";
import { SafeAreaView, StyleSheet } from "react-native";
import MapTracker from "./src/components/MapTracker";

export default function App() {
  return (
    <SafeAreaView style={styles.container}>
      <StatusBar style="auto" />
      <MapTracker />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
});

5. Bare React Native Version (Hook Variant)

If you’re not using Expo, replace the hook with this version.

import { useEffect, useState } from "react";
import { PermissionsAndroid, Platform } from "react-native";
import Geolocation, { GeoPosition } from "react-native-geolocation-service";
import { Region, LatLng } from "react-native-maps";

export function useUserLocationBare(): {
  region: Region | null;
  path: LatLng[];
} {
  const [region, setRegion] = useState<Region | null>(null);
  const [path, setPath] = useState<LatLng[]>([]);

  const requestPermission = async () => {
    if (Platform.OS === "ios") return true;

    const granted = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
    );
    return granted === PermissionsAndroid.RESULTS.GRANTED;
  };

  useEffect(() => {
    (async () => {
      const hasPermission = await requestPermission();
      if (!hasPermission) return;

      const watchId = Geolocation.watchPosition(
        (pos: GeoPosition) => {
          const { latitude, longitude } = pos.coords;
          const newPoint = { latitude, longitude };

          setPath((prev) => [...prev, newPoint]);

          if (!region) {
            setRegion({
              latitude,
              longitude,
              latitudeDelta: 0.01,
              longitudeDelta: 0.01,
            });
          }
        },
        console.log,
        {
          enableHighAccuracy: true,
          distanceFilter: 5,
          interval: 2000,
        }
      );

      return () => Geolocation.clearWatch(watchId);
    })();
  }, []);

  return { region, path };
}

Everything else remains the same.

src/components/MapTracker.tsx

import React from 'react';
import { StyleSheet, View, ActivityIndicator } from 'react-native';
import MapView, { Polyline, Marker } from 'react-native-maps';
import { useUserLocationBare } from '../hooks/useUserLocation';

export default function MapTracker() {
  const { region, path } = useUserLocationBare();

  if (!region) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <MapView
      style={styles.map}
      initialRegion={region}
      showsUserLocation={true}
      followsUserLocation={true}
    >
      {path.length > 1 && (
        <Polyline coordinates={path} strokeWidth={4} strokeColor="#e91e63" />
      )}

      {path.length > 0 && (
        <Marker coordinate={path[path.length - 1]} title="Current Position" />
      )}
    </MapView>
  );
}

const styles = StyleSheet.create({
  map: { flex: 1 },
  center: { flex: 1, justifyContent: 'center', alignItems: 'center' },
});

6. Example App Features

✔ Map initializes at the user’s current location
✔ Shows blue dot (native RN maps feature)
✔ Custom marker for the latest location
✔ Real-time tracking using watchPosition
✔ Path drawn as a polyline
✔ Works with both Expo and Bare React Native
✔ Modular and scalable folder structure
✔ 100% TypeScript

This example app forms the core foundation for any map or geolocation-based RN app, including:

  • Delivery tracking apps

  • Running/fitness path trackers

  • Location check-in apps

  • Taxi/ride-sharing apps

  • Logistics and fleet tracking


Conclusion

React Native makes it incredibly powerful and flexible to build location-aware mobile applications, and with just a few key libraries—react-native-maps, expo-location, or react-native-geolocation-service—you can implement everything from simple map displays to full real-time movement tracking.

In this tutorial, you learned how to:

  • Install and configure mapping and geolocation packages for both Expo and Bare React Native

  • Display your first map with MapView

  • Add markers, callouts, and custom icons

  • Implement marker clustering for large datasets

  • Draw routes with Polyline, including real directions using Google’s Directions API

  • Track real-time movement with watchPosition

  • Enable background location services for long-running tracking

  • Debug common issues such as blank maps, missing API keys, and permission problems

  • Build a complete TypeScript-based example app with clean, modular architecture

With these skills, you’re now fully equipped to build map-powered apps ranging from:

  • Delivery and ride-sharing platforms

  • Fitness tracking apps

  • Real-time navigation and routing tools

  • Location-based social features

  • Logistics and fleet monitoring systems

You can extend this project even further by adding features like geofencing, reverse geocoding, offline map tiles, or server-side route optimization.

Whether you're building a hobby project or a production-grade app, the patterns and examples in this guide provide a solid foundation for any React Native mapping experience.

You can find the full source code on our GitHub.

That's just the basics. If you need more deep learning about React Native, you can take the following cheap course:

Thanks!