Shipnative
ShipnativeShipnative
Sign in
GuideAugust 2026 · 8 min read

React Native Geolocation: Permissions, Position, Tracking

Location is three separate problems wearing one trench coat: getting permission without being denied, reading a position without draining the battery, and handling the very likely case where the user says no. The code is short — expo-location does the work — but the defaults are wrong for most apps, and the permission wording is a real App Review rejection reason. Here is the whole thing, including what to do when the answer is no.

Step 1 — Install and declare the reason

npx expo install expo-location

The permission string is not boilerplate — iOS shows it verbatim in the system prompt, and App Review rejects vague ones. Write the sentence you would want to read:

{
  "expo": {
    "plugins": [
      [
        "expo-location",
        {
          "locationWhenInUsePermission":
            "We use your location to show jobs near you and pre-fill your address."
        }
      ]
    ]
  }
}

Compare that with “This app needs access to your location.” — which tells the user nothing, converts worse, and is exactly the phrasing that gets flagged. Name the feature and the benefit.

Step 2 — Ask late, not at launch

The most expensive mistake in this whole guide is asking for location on the first screen. The user has no context, denies it, and iOS will not re-prompt — from then on your only recourse is sending them into Settings, which almost nobody does. Ask the moment they tap something that obviously needs it.

import { useState } from 'react';
import { Button, Linking, Text, View } from 'react-native';
import * as Location from 'expo-location';

export default function NearbyScreen() {
  const [status, requestPermission] = Location.useForegroundPermissions();
  const [coords, setCoords] = useState(null);
  const [loading, setLoading] = useState(false);

  const findNearby = async () => {
    const granted = status?.granted
      ? status
      : await requestPermission();          // prompts only if not yet decided

    if (!granted.granted) return;           // denied — the UI below handles it

    setLoading(true);
    // instant, possibly stale — render something immediately
    const cached = await Location.getLastKnownPositionAsync();
    if (cached) setCoords(cached.coords);

    const fresh = await Location.getCurrentPositionAsync({
      accuracy: Location.Accuracy.Balanced,
    });
    setCoords(fresh.coords);
    setLoading(false);
  };

  if (status && !status.granted && !status.canAskAgain) {
    return (
      <View style={{ padding: 24, gap: 12 }}>
        <Text style={{ color: '#fff' }}>
          Location is off for this app. You can turn it on in Settings, or enter
          your area manually.
        </Text>
        <Button title="Open Settings" onPress={() => Linking.openSettings()} />
        <Button title="Enter it manually" onPress={/* … */ undefined} />
      </View>
    );
  }

  return (
    <View style={{ padding: 24, gap: 12 }}>
      <Button
        title={loading ? 'Locating…' : 'Find jobs near me'}
        onPress={findNearby}
        disabled={loading}
      />
      {coords && (
        <Text style={{ color: '#fff' }}>
          {coords.latitude.toFixed(4)}, {coords.longitude.toFixed(4)}
        </Text>
      )}
    </View>
  );
}

Two details do most of the work here. getLastKnownPositionAsync returns a cached fix immediately, so the screen has something to show while the real fix is acquired — without it, a cold GPS lock indoors can leave a spinner up for several seconds and read as a hang. And canAskAgainis what distinguishes “not asked yet” from “permanently denied”; only in the second case is the Settings link the right thing to show.

Step 3 — Pick an accuracy tier on purpose

Accuracy is a battery dial, and most apps leave it turned up for no reason. Roughly:

AccuracyRoughlyBattery costGood for
Lowest / Low~1km – 3kmMinimalCity-level content, weather, currency
Balanced~100mLowNearby listings, "restaurants around me"
High~10mNoticeableMap pin, delivery address confirm
Highest / BestForNavigationBest availableHeavyTurn-by-turn, run tracking

If the feature is “show me what’s nearby”, Balanced is correct and noticeably faster to a first fix. Reserve BestForNavigation for things that genuinely follow a moving person.

Live tracking while the screen is open

For a run tracker or a driver view you want a stream, not a reading. The two things that matter are the throttles and the cleanup:

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

export function useLiveLocation(enabled) {
  const [path, setPath] = useState([]);
  const subRef = useRef(null);

  useEffect(() => {
    if (!enabled) return;
    let cancelled = false;

    (async () => {
      const { granted } = await Location.requestForegroundPermissionsAsync();
      if (!granted || cancelled) return;

      subRef.current = await Location.watchPositionAsync(
        {
          accuracy: Location.Accuracy.High,
          distanceInterval: 10,   // metres moved before an update
          timeInterval: 5000,     // and no more often than this
        },
        ({ coords }) => setPath((prev) => [...prev, coords]),
      );
    })();

    return () => {
      cancelled = true;
      subRef.current?.remove();   // ← forget this and the GPS stays on
      subRef.current = null;
    };
  }, [enabled]);

  return path;
}

subRef.current?.remove()in the cleanup is not optional. A watch that outlives its screen keeps the GPS radio active for the rest of the session, and shows up in the user’s battery report with your app’s name next to it. The cancelled flag covers the case where the screen unmounts while the permission prompt is still open.

Background location: think twice

Tracking location while the app is closed is a different feature with a different cost. It needs expo-task-manager, a separate Always permission with its own usage string, background mode declarations, and — in practice — a written justification when the store reviews it. Both stores treat it as a privacy-sensitive capability and they are right to.

Before you go there, check whether you actually need it. “Track my run” usually means “the screen is on or the app is in the foreground”, which a foreground watch handles. Geofenced reminders and delivery-driver apps are the real cases. If you are adding background tracking because it seemed like the complete version of the feature, you are buying a review risk and a battery complaint for nothing.

Coordinates to something a human reads

A latitude and longitude is rarely what you want on screen. expo-location includes reverse geocoding:

const [place] = await Location.reverseGeocodeAsync({
  latitude: coords.latitude,
  longitude: coords.longitude,
});
// place → { city, region, postalCode, street, country, … }
setLabel([place.city, place.region].filter(Boolean).join(', '));

Fields vary by country — a result with no street or no cityis normal, not an error, so filter before joining rather than interpolating blindly and shipping “undefined, undefined” to a user abroad. From here the natural next step is putting the position on a map, which the maps integration guide covers.

The shortcut

Location is a feature where the boilerplate — permission state, cached-then-fresh reads, denial handling, subscription cleanup — is longer than the part that makes your app yours. Describe the behaviour (“show jobs near me, ask for location only when I tap it, let me type a postcode instead”) and ShipNative generates the permission flow, the fallback UI, and the plugin config as a real Expo project you can preview on your phone and export in full.

Frequently Asked Questions

How do I get the user location in React Native?

Install expo-location, request foreground permission with requestForegroundPermissionsAsync (or the useForegroundPermissions hook), then call getCurrentPositionAsync for a single reading. Permission must be granted first — calling the position API without it throws rather than prompting.

Do I need navigator.geolocation in React Native?

No. React Native has a navigator.geolocation polyfill, but on Expo the maintained path is expo-location, which handles permission prompts, accuracy tiers, background updates, and geocoding in one API. Use it and ignore the web-style calls.

What permission strings does App Review require for location?

iOS shows your NSLocationWhenInUseUsageDescription text in the prompt, and rejects vague ones. Say what the app does with the location and why the user benefits — "to show restaurants near you" rather than "to access your location". If you request Always access you need a separate string and a genuinely convincing reason.

Why is getCurrentPositionAsync slow the first time?

A cold GPS fix at high accuracy can take several seconds, especially indoors. Request Accuracy.Balanced unless you need street-level precision, and consider getLastKnownPositionAsync first — it returns instantly from cache and lets you render something while the fresh fix arrives.

How do I track location in the background?

Background location needs expo-task-manager alongside expo-location, a separate Always permission, and background mode declarations in app.json. Both stores scrutinise it heavily, so only ask if the feature genuinely requires it — most apps that think they need background tracking actually need a foreground watch while the screen is open.

Does location work in Expo Go?

Foreground location generally works in Expo Go for development, but background location and custom permission strings require a development build. Test the real permission flow on a development build before you submit — that is where the wording your users see is baked in.

→

How to Add Maps to a React Native App

The screen your location usually ends up on.

Read guide →
→

Expo EAS Submission Checklist

Permission strings are checked at review — this is the rest of the list.

See checklist →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.