ScreenshotNeo

BlogHow-to

How to Get a ZIP Code with Geolocation in React

Get a browser location in React, reverse geocode latitude and longitude, and handle HTTPS, permissions, provider differences, errors, and privacy.

By the ScreenshotNeo team1 October 20268 min read

Direct answer: Browser geolocation gives you latitude and longitude, not a ZIP code. In React, request the position after a user action, send the coordinates to a reverse-geocoding service, then read that provider’s postal-code field. The page must run in a secure context (HTTPS), the user must grant permission, and your code must handle denial, timeout, unavailable positions, and providers that return no postal code.

1. The complete flow

  1. Ask the user to click a button such as Use my location.
  2. Call navigator.geolocation.getCurrentPosition().
  3. Read position.coords.latitude, longitude, and optionally accuracy.
  4. Send coordinates to your own backend endpoint.
  5. Have the backend call a reverse-geocoding provider so API keys are not exposed in browser code.
  6. Normalize the provider response to a field such as postalCode.
  7. Render loading, success, missing-result, and error states.

MDN documents that getCurrentPosition() is available only in secure contexts, requires explicit permission, and can be blocked by the geolocation Permissions-Policy. MDN getCurrentPosition reference.

2. React component

This component is provider-agnostic. It calls /api/reverse-geocode, which you implement on your server.

import { useState } from 'react';

const options = {
  enableHighAccuracy: true,
  timeout: 10000,
  maximumAge: 0,
};

export default function ZipFromLocation() {
  const [zip, setZip] = useState('');
  const [accuracy, setAccuracy] = useState(null);
  const [status, setStatus] = useState('idle');
  const [error, setError] = useState('');

  function getZipCode() {
    setZip('');
    setError('');

    if (!('geolocation' in navigator)) {
      setStatus('error');
      setError('Geolocation is not supported by this browser.');
      return;
    }

    setStatus('locating');
    navigator.geolocation.getCurrentPosition(
      async ({ coords }) => {
        setAccuracy(coords.accuracy);
        setStatus('looking-up');

        try {
          const params = new URLSearchParams({
            lat: String(coords.latitude),
            lon: String(coords.longitude),
          });
          const response = await fetch(`/api/reverse-geocode?${params}`);
          const data = await response.json();
          if (!response.ok) throw new Error(data.error || 'Reverse geocoding failed');

          setZip(data.postalCode || 'ZIP code unavailable');
          setStatus('done');
        } catch (err) {
          setStatus('error');
          setError(err instanceof Error ? err.message : 'Address lookup failed.');
        }
      },
      (err) => {
        const messages = {
          1: 'Location permission was denied.',
          2: 'Your location could not be determined.',
          3: 'Location lookup timed out. Try again.',
        };
        setStatus('error');
        setError(messages[err.code] || err.message || 'Unable to get your location.');
      },
      options
    );
  }

  return (
    <section>
      <button type="button" onClick={getZipCode} disabled={status === 'locating' || status === 'looking-up'}>
        {status === 'locating' ? 'Finding location…' : status === 'looking-up' ? 'Finding ZIP code…' : 'Use my location'}
      </button>
      {status === 'done' && <p>ZIP code: <strong>{zip}</strong></p>}
      {accuracy !== null && <p>Estimated accuracy: {Math.round(accuracy)} metres</p>}
      {status === 'error' && <p role="alert">{error}</p>}
    </section>
  );
}

enableHighAccuracy may improve the position but can increase delay and battery use. timeout bounds the wait. maximumAge: 0 requests a fresh position; a larger value can make repeat lookups faster by allowing a cached position.

3. Backend reverse-geocoding endpoint

Keep provider credentials on the server. Google says its Geocoding API v4 is designed for server-to-server use because browser calls expose keys to theft and misuse. See the Google reverse-geocoding documentation and Google Geocoding API documentation.

Node.js and Express example

import express from 'express';

const app = express();
const port = process.env.PORT || 3000;

app.get('/api/reverse-geocode', async (req, res) => {
  const lat = Number(req.query.lat);
  const lon = Number(req.query.lon);
  if (!Number.isFinite(lat) || !Number.isFinite(lon) || lat < -90 || lat > 90 || lon < -180 || lon > 180) {
    return res.status(400).json({ error: 'Invalid coordinates' });
  }

  const url = new URL('https://geocode.googleapis.com/v4/geocode/location');
  url.searchParams.set('location.latitude', String(lat));
  url.searchParams.set('location.longitude', String(lon));
  url.searchParams.set('key', process.env.GOOGLE_MAPS_API_KEY);

  try {
    const response = await fetch(url);
    const data = await response.json();
    if (!response.ok) return res.status(response.status).json({ error: 'Geocoder request failed' });

    const result = data.results?.[0];
    const postal = result?.addressComponents?.find((component) =>
      component.types?.includes('postal_code')
    );
    res.json({ postalCode: postal?.longText || postal?.shortText || null });
  } catch {
    res.status(502).json({ error: 'Geocoder is temporarily unavailable' });
  }
});

app.listen(port, () => console.log(`Listening on ${port}`));

Provider response names differ. Google may return address components and address types; select the component whose type is postal_code. A reverse-geocoding result is an estimate and can be empty or represent a nearby address.

Python example with Nominatim

Nominatim returns an address object when OpenStreetMap has suitable coverage. Its manual warns that reverse geocoding finds the closest suitable OSM object and does not exactly compute an address. Follow the current Nominatim reverse API documentation, usage policy, rate limits, and attribution requirements.

import os
import requests
from flask import Flask, jsonify, request

app = Flask(__name__)

@app.get('/api/reverse-geocode')
def reverse_geocode():
    try:
        lat = float(request.args['lat'])
        lon = float(request.args['lon'])
    except (KeyError, ValueError):
        return jsonify(error='Invalid coordinates'), 400
    if not (-90 <= lat <= 90 and -180 <= lon <= 180):
        return jsonify(error='Invalid coordinates'), 400

    response = requests.get(
        'https://nominatim.openstreetmap.org/reverse',
        params={'lat': lat, 'lon': lon, 'format': 'jsonv2', 'addressdetails': 1},
        headers={'User-Agent': os.environ.get('GEOCODER_USER_AGENT', 'my-react-app/1.0')},
        timeout=10,
    )
    if response.status_code != 200:
        return jsonify(error='Geocoder request failed'), 502
    data = response.json()
    return jsonify(postalCode=data.get('address', {}).get('postcode'))

if __name__ == '__main__':
    app.run(port=3000)

4. cURL and direct provider requests

Use cURL to inspect a provider response while developing. Do not put a secret Google key in frontend JavaScript.

curl -G 'https://nominatim.openstreetmap.org/reverse' \
  --data-urlencode 'lat=40.7484' \
  --data-urlencode 'lon=-73.9857' \
  --data-urlencode 'format=jsonv2' \
  --data-urlencode 'addressdetails=1' \
  -H 'User-Agent: my-react-app/1.0'

Google’s documented v4 request shape is:

GET https://geocode.googleapis.com/v4/geocode/location?location.latitude=<LAT>&location.longitude=<LON>

Authenticate this request on your server and request only the fields your provider supports and your application needs.

5. Provider differences and normalization

Concern Google Geocoding Nominatim/OpenStreetMap
Key exposure Proxy through your backend; Google describes v4 as server-to-server. Public endpoint, but your client must follow usage policy.
Result meaning Estimated address with components, types, Place IDs, and possibly Plus Codes. Closest suitable OSM object; not an exact mathematical address.
Postal field Find an address component with type postal_code. Usually address.postcode.
No result Reverse geocoding may return zero results. Returns one result or an error when no OSM data covers the coordinate.
Operations Check billing, quotas, key restrictions, and response fields. Respect attribution, rate limits, and the current usage policy; use managed or self-hosted infrastructure at higher volume.

Create an adapter so changing providers does not change your React component:

function normalizePostalCode(provider, data) {
  if (provider === 'nominatim') return data.address?.postcode ?? null;
  const component = data.results?.[0]?.addressComponents?.find((c) => c.types?.includes('postal_code'));
  return component?.longText ?? component?.shortText ?? null;
}

Postal codes vary by country and may be absent, formatted differently, or represented by a local equivalent. Treat the value as a string; do not parse it as an integer or assume a fixed length.

6. Secure-context, permission, and policy requirements

  • Production pages should use HTTPS. Browsers generally allow geolocation on localhost during development, but a custom HTTP hostname may fail.
  • Call geolocation in response to a clear user action and explain why you need it.
  • A user can deny permission permanently; provide a manual ZIP-code input fallback.
  • An iframe may need an explicit Permissions-Policy such as geolocation=(self) and an iframe allow="geolocation", subject to your deployment policy.
  • Do not store precise coordinates longer than necessary. Send them over HTTPS and avoid logging them with user identifiers.

7. Accuracy, caching, and performance

  • Accuracy: The browser’s coords.accuracy is an estimate in metres. A precise coordinate can still map to a nearby or wrong postal boundary because reverse geocoding itself is approximate.
  • Latency: High-accuracy positioning may wait for GPS or other sensors. Use a timeout and show progress for both location and address lookup.
  • Battery: Request one position for a ZIP lookup instead of using watchPosition().
  • Caching: A short client cache keyed by rounded coordinates can reduce repeat lookups. Do not cache so aggressively that a user crossing a postal boundary sees stale data.
  • Rate limits: Debounce retries, cache successful results, and use a managed or self-hosted geocoder for sustained traffic.
  • Cost: Provider pricing, quotas, and policy terms change. Set server-side limits and monitor usage rather than reverse geocoding every render.

8. Troubleshooting

Symptom Likely cause Fix
Geolocation is not supported Old browser, restricted web view, or unavailable API. Offer manual ZIP entry and check the runtime.
Permission denied User denied access or browser/site permission is blocked. Explain the setting, allow retry, and keep the manual fallback.
Works on localhost but not staging Staging uses HTTP or an invalid certificate. Serve staging over HTTPS and verify the exact origin.
Position unavailable Device sensors, OS location services, or browser policy failed. Retry, reduce accuracy requirements, and show manual entry.
Timeout GPS or network lookup exceeded your limit. Increase timeout, allow a cached value with maximumAge, or retry.
Reverse endpoint returns 400 Missing, nonnumeric, or out-of-range coordinates. Validate latitude and longitude on both client and server.
ZIP is null No postal component, rural coordinate, incomplete provider data, or country-specific format. Display “ZIP code unavailable,” let the user enter it, and preserve the raw result for diagnosis without exposing private data.
CORS error Browser is calling a provider that does not permit your origin. Call the provider from your backend and return a small normalized response.
Unexpected nearby address Reverse geocoding is an estimate or nearest-object lookup. Explain the limitation, inspect accuracy, and consider a user confirmation step for important transactions.

9. Testing checklist

  • Test permission allow, deny, dismiss, and previously blocked states.
  • Test HTTPS, localhost, an HTTP hostname, and an embedded iframe.
  • Test desktop without a location sensor, mobile Wi-Fi, GPS, and poor connectivity.
  • Mock success, timeout, unavailable position, provider 4xx/5xx, empty results, and malformed responses.
  • Test countries with different postal-code formats and coordinates near a postal boundary.
  • Verify that no API secret appears in the browser bundle or network requests.

10. Or skip the browser setup

If your goal is to capture a page that displays a ZIP code or location-dependent result, ScreenshotNeo can return a screenshot or PDF from one request. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and the response identifies the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

11. FAQ

Can React get a ZIP code without an API?

No. Browser geolocation supplies coordinates. You need a reverse-geocoding dataset or service to map them to a postal code.

Should I use watchPosition()?

Usually no for a one-time ZIP lookup. It continuously tracks movement, uses more battery, and adds privacy complexity.

Can I trust the ZIP code for billing or tax decisions?

Use it as a convenience signal, not unquestioned proof. Confirm the address or ZIP with the user when accuracy has financial or legal consequences.

Why is the nearest address sometimes wrong?

Reverse geocoding estimates an address or selects a nearby mapped object. Boundaries, sparse mapping, and coordinate accuracy can all affect the result.

How do I support users who refuse location access?

Keep a normal ZIP-code field, explain that it is optional, and let users continue without granting browser permission.