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
- Go to Google Cloud Console
- Create a project (or select existing)
- Enable these APIs: Maps JavaScript API, Geocoding API, Directions API, Places API
- Create an API key under Credentials
- 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
| API | Free Tier | Per 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
| Item | Notes |
|---|---|
| Restrict API key to your domain | Prevents unauthorized usage |
| Set billing alerts | Avoid surprise charges |
| Enable only needed APIs | Reduce attack surface |
| Use server-side geocoding for batch | Don't expose key in client for bulk operations |
| Cache geocoding results | Addresses don't change — cache aggressively |
| Lazy load the map component | Don't load Maps JS until user scrolls to map |
Alternatives to Google Maps
| Alternative | Pricing | Best For |
|---|---|---|
| Mapbox | 50K loads free, then $5/1K | Custom map styles, 3D |
| HERE Maps | 250K transactions free | Enterprise, fleet management |
| OpenStreetMap + Leaflet | Free (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.