Skip to main content

How to Add Google Maps to Your React App

·APIScout Team
google mapsreactmaps apitutorialapi integration

How to Add Google Maps to Your React App

Google Maps is the most widely used mapping API. This guide covers the full integration: displaying maps, adding markers, geocoding addresses, showing directions, and adding place autocomplete — all in React with TypeScript.

What You'll Build

  • Interactive Google Map component
  • Custom markers with info windows
  • Address geocoding (text → coordinates)
  • Directions between two points
  • Place autocomplete search input

Prerequisites: React 18+, Google Cloud account, Maps JavaScript API enabled.

1. Setup

Get an API Key

  1. Go to Google Cloud Console
  2. Create a project (or select existing)
  3. Enable these APIs: Maps JavaScript API, Geocoding API, Directions API, Places API
  4. Create an API key under Credentials
  5. Restrict the key to your domain (important for production)

Install the Library

npm install @vis.gl/react-google-maps

This is the official Google-maintained React wrapper for Maps.

Environment Variables

# .env.local
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=AIza...

2. Basic Map

Map Provider

Wrap your app with the API provider:

// app/layout.tsx or providers.tsx
import { APIProvider } from '@vis.gl/react-google-maps';

export function MapProvider({ children }: { children: React.ReactNode }) {
  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY!}>
      {children}
    </APIProvider>
  );
}

Display a Map

// components/BasicMap.tsx
'use client';
import { Map } from '@vis.gl/react-google-maps';

export function BasicMap() {
  return (
    <Map
      style={{ width: '100%', height: '500px' }}
      defaultCenter={{ lat: 37.7749, lng: -122.4194 }}
      defaultZoom={12}
      gestureHandling="greedy"
      disableDefaultUI={false}
    />
  );
}

3. Markers

Add Markers

// components/MapWithMarkers.tsx
'use client';
import { Map, AdvancedMarker, Pin } from '@vis.gl/react-google-maps';

const locations = [
  { id: 1, name: 'Stripe HQ', lat: 37.7900, lng: -122.3932 },
  { id: 2, name: 'GitHub HQ', lat: 37.7822, lng: -122.3912 },
  { id: 3, name: 'Twilio HQ', lat: 37.7862, lng: -122.3937 },
];

export function MapWithMarkers() {
  return (
    <Map
      style={{ width: '100%', height: '500px' }}
      defaultCenter={{ lat: 37.7860, lng: -122.3930 }}
      defaultZoom={15}
      mapId="your-map-id" // Required for AdvancedMarker
    >
      {locations.map((loc) => (
        <AdvancedMarker key={loc.id} position={{ lat: loc.lat, lng: loc.lng }}>
          <Pin background="#4285F4" glyphColor="#fff" borderColor="#2563eb" />
        </AdvancedMarker>
      ))}
    </Map>
  );
}

Markers with Info Windows

'use client';
import { useState } from 'react';
import { Map, AdvancedMarker, InfoWindow } from '@vis.gl/react-google-maps';

export function MapWithInfoWindows() {
  const [selectedId, setSelectedId] = useState<number | null>(null);

  return (
    <Map
      style={{ width: '100%', height: '500px' }}
      defaultCenter={{ lat: 37.7860, lng: -122.3930 }}
      defaultZoom={15}
      mapId="your-map-id"
    >
      {locations.map((loc) => (
        <AdvancedMarker
          key={loc.id}
          position={{ lat: loc.lat, lng: loc.lng }}
          onClick={() => setSelectedId(loc.id)}
        >
          {selectedId === loc.id && (
            <InfoWindow
              position={{ lat: loc.lat, lng: loc.lng }}
              onCloseClick={() => setSelectedId(null)}
            >
              <div>
                <h3>{loc.name}</h3>
                <p>Lat: {loc.lat}, Lng: {loc.lng}</p>
              </div>
            </InfoWindow>
          )}
        </AdvancedMarker>
      ))}
    </Map>
  );
}

4. Geocoding

Convert addresses to coordinates and vice versa:

// lib/geocode.ts
export async function geocodeAddress(address: string) {
  const response = await fetch(
    `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}`
  );
  const data = await response.json();

  if (data.results.length === 0) {
    throw new Error('Address not found');
  }

  const { lat, lng } = data.results[0].geometry.location;
  const formattedAddress = data.results[0].formatted_address;

  return { lat, lng, formattedAddress };
}

// Reverse geocoding (coordinates → address)
export async function reverseGeocode(lat: number, lng: number) {
  const response = await fetch(
    `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}`
  );
  const data = await response.json();
  return data.results[0]?.formatted_address ?? 'Unknown location';
}

Geocoding Component

'use client';
import { useState } from 'react';
import { geocodeAddress } from '@/lib/geocode';

export function AddressSearch({ onLocationFound }: {
  onLocationFound: (lat: number, lng: number) => void;
}) {
  const [address, setAddress] = useState('');

  const handleSearch = async () => {
    const { lat, lng } = await geocodeAddress(address);
    onLocationFound(lat, lng);
  };

  return (
    <div>
      <input
        value={address}
        onChange={(e) => setAddress(e.target.value)}
        placeholder="Enter an address..."
      />
      <button onClick={handleSearch}>Search</button>
    </div>
  );
}

5. Place Autocomplete

'use client';
import { useRef, useEffect, useState } from 'react';
import { useMapsLibrary } from '@vis.gl/react-google-maps';

export function PlaceAutocomplete({ onPlaceSelect }: {
  onPlaceSelect: (place: google.maps.places.PlaceResult) => void;
}) {
  const inputRef = useRef<HTMLInputElement>(null);
  const places = useMapsLibrary('places');

  useEffect(() => {
    if (!places || !inputRef.current) return;

    const autocomplete = new places.Autocomplete(inputRef.current, {
      fields: ['geometry', 'name', 'formatted_address'],
    });

    autocomplete.addListener('place_changed', () => {
      const place = autocomplete.getPlace();
      if (place.geometry?.location) {
        onPlaceSelect(place);
      }
    });
  }, [places, onPlaceSelect]);

  return (
    <input
      ref={inputRef}
      placeholder="Search for a place..."
      style={{ width: '300px', padding: '8px' }}
    />
  );
}

6. Directions

Show a route between two points:

'use client';
import { useEffect, useState } from 'react';
import { Map, useMapsLibrary, useMap } from '@vis.gl/react-google-maps';

function DirectionsRenderer({ origin, destination }: {
  origin: string;
  destination: string;
}) {
  const map = useMap();
  const routesLibrary = useMapsLibrary('routes');
  const [directionsResult, setDirectionsResult] = useState<
    google.maps.DirectionsResult | null
  >(null);

  useEffect(() => {
    if (!routesLibrary || !map) return;

    const directionsService = new routesLibrary.DirectionsService();
    const directionsRenderer = new routesLibrary.DirectionsRenderer({ map });

    directionsService.route(
      {
        origin,
        destination,
        travelMode: google.maps.TravelMode.DRIVING,
      },
      (result, status) => {
        if (status === 'OK' && result) {
          directionsRenderer.setDirections(result);
          setDirectionsResult(result);
        }
      }
    );

    return () => directionsRenderer.setMap(null);
  }, [routesLibrary, map, origin, destination]);

  if (!directionsResult) return null;

  const route = directionsResult.routes[0].legs[0];
  return (
    <div>
      <p>Distance: {route.distance?.text}</p>
      <p>Duration: {route.duration?.text}</p>
    </div>
  );
}

export function DirectionsMap() {
  return (
    <Map
      style={{ width: '100%', height: '500px' }}
      defaultCenter={{ lat: 37.7749, lng: -122.4194 }}
      defaultZoom={12}
    >
      <DirectionsRenderer
        origin="San Francisco, CA"
        destination="San Jose, CA"
      />
    </Map>
  );
}

Pricing

APIFree TierPer 1,000 Requests
Maps JavaScript$200/month credit$7.00 (loads)
Geocoding$200/month credit$5.00
Directions$200/month credit$5.00-$10.00
Places Autocomplete$200/month credit$2.83 per session

The $200/month free credit covers ~28,000 map loads or 40,000 geocoding requests.

Production Checklist

ItemNotes
Restrict API key to your domainPrevents unauthorized usage
Set billing alertsAvoid surprise charges
Enable only needed APIsReduce attack surface
Use server-side geocoding for batchDon't expose key in client for bulk operations
Cache geocoding resultsAddresses don't change — cache aggressively
Lazy load the map componentDon't load Maps JS until user scrolls to map

Alternatives to Google Maps

AlternativePricingBest For
Mapbox50K loads free, then $5/1KCustom map styles, 3D
HERE Maps250K transactions freeEnterprise, fleet management
OpenStreetMap + LeafletFree (open source)Budget projects, no vendor lock-in

Building with maps? Compare Google Maps vs Mapbox vs HERE on APIScout — pricing, features, and developer experience compared.

Comments