Skip to main content
This guide will walk you through creating a fully functional Google Map with markers, info windows, and user interaction in just a few minutes.

What You’ll Build

By the end of this guide, you’ll have a map that:
  • Displays an interactive Google Map
  • Shows multiple markers at different locations
  • Opens info windows when markers are clicked
  • Centers on a specific location
  • Handles loading and error states

Prerequisites

Before starting, ensure you have:
  • Installed @react-google-maps/api (see Installation)
  • A valid Google Maps API key
  • React 16.8 or higher

Step-by-Step Guide

1

Create the map component

Create a new file for your map component. We’ll start with a basic map display.
components/Map.tsx
import { GoogleMap, useJsApiLoader } from '@react-google-maps/api';
import { useMemo } from 'react';

function Map() {
  const { isLoaded, loadError } = useJsApiLoader({
    googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY!,
  });

  const center = useMemo(() => ({ lat: 40.7128, lng: -74.006 }), []);

  const containerStyle = useMemo(() => ({
    width: '100%',
    height: '400px',
  }), []);

  if (loadError) {
    return <div>Error loading maps</div>;
  }

  if (!isLoaded) {
    return <div>Loading maps...</div>;
  }

  return (
    <GoogleMap
      mapContainerStyle={containerStyle}
      center={center}
      zoom={12}
    />
  );
}

export default Map;
We use useMemo for center and containerStyle to maintain stable references and prevent unnecessary re-renders.
2

Add markers to the map

Import the Marker component and add markers for multiple locations.
components/Map.tsx
import { GoogleMap, Marker, useJsApiLoader } from '@react-google-maps/api';
import { useMemo } from 'react';

function Map() {
  const { isLoaded, loadError } = useJsApiLoader({
    googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY!,
  });

  const center = useMemo(() => ({ lat: 40.7128, lng: -74.006 }), []);

  const containerStyle = useMemo(() => ({
    width: '100%',
    height: '400px',
  }), []);

  // Define marker locations
  const locations = useMemo(() => [
    { id: 1, name: 'New York City', position: { lat: 40.7128, lng: -74.006 } },
    { id: 2, name: 'Central Park', position: { lat: 40.7829, lng: -73.9654 } },
    { id: 3, name: 'Times Square', position: { lat: 40.7580, lng: -73.9855 } },
  ], []);

  if (loadError) {
    return <div>Error loading maps</div>;
  }

  if (!isLoaded) {
    return <div>Loading maps...</div>;
  }

  return (
    <GoogleMap
      mapContainerStyle={containerStyle}
      center={center}
      zoom={12}
    >
      {locations.map((location) => (
        <Marker
          key={location.id}
          position={location.position}
        />
      ))}
    </GoogleMap>
  );
}

export default Map;
3

Add interactive info windows

Add InfoWindow components that open when users click markers.
components/Map.tsx
import { GoogleMap, Marker, InfoWindow, useJsApiLoader } from '@react-google-maps/api';
import { useMemo, useState, useCallback } from 'react';

function Map() {
  const { isLoaded, loadError } = useJsApiLoader({
    googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY!,
  });

  const [selectedLocation, setSelectedLocation] = useState<number | null>(null);

  const center = useMemo(() => ({ lat: 40.7128, lng: -74.006 }), []);

  const containerStyle = useMemo(() => ({
    width: '100%',
    height: '400px',
  }), []);

  const locations = useMemo(() => [
    { 
      id: 1, 
      name: 'New York City', 
      position: { lat: 40.7128, lng: -74.006 },
      description: 'The city that never sleeps'
    },
    { 
      id: 2, 
      name: 'Central Park', 
      position: { lat: 40.7829, lng: -73.9654 },
      description: 'An urban park in Manhattan'
    },
    { 
      id: 3, 
      name: 'Times Square', 
      position: { lat: 40.7580, lng: -73.9855 },
      description: 'Major commercial intersection'
    },
  ], []);

  const handleMarkerClick = useCallback((id: number) => {
    setSelectedLocation(id);
  }, []);

  const handleInfoWindowClose = useCallback(() => {
    setSelectedLocation(null);
  }, []);

  if (loadError) {
    return <div>Error loading maps</div>;
  }

  if (!isLoaded) {
    return <div>Loading maps...</div>;
  }

  return (
    <GoogleMap
      mapContainerStyle={containerStyle}
      center={center}
      zoom={12}
    >
      {locations.map((location) => (
        <Marker
          key={location.id}
          position={location.position}
          onClick={() => handleMarkerClick(location.id)}
        />
      ))}

      {selectedLocation && (
        <InfoWindow
          position={locations.find(loc => loc.id === selectedLocation)?.position}
          onCloseClick={handleInfoWindowClose}
        >
          <div style={{ padding: '8px' }}>
            <h3 style={{ margin: '0 0 8px 0' }}>
              {locations.find(loc => loc.id === selectedLocation)?.name}
            </h3>
            <p style={{ margin: 0 }}>
              {locations.find(loc => loc.id === selectedLocation)?.description}
            </p>
          </div>
        </InfoWindow>
      )}
    </GoogleMap>
  );
}

export default Map;
We use useCallback for event handlers to maintain stable function references and prevent unnecessary re-renders.
4

Add map instance tracking

Track the map instance to enable programmatic map control.
components/Map.tsx
import { GoogleMap, Marker, InfoWindow, useJsApiLoader } from '@react-google-maps/api';
import { useMemo, useState, useCallback } from 'react';

function Map() {
  const { isLoaded, loadError } = useJsApiLoader({
    googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY!,
  });

  const [map, setMap] = useState<google.maps.Map | null>(null);
  const [selectedLocation, setSelectedLocation] = useState<number | null>(null);

  const center = useMemo(() => ({ lat: 40.7128, lng: -74.006 }), []);

  const containerStyle = useMemo(() => ({
    width: '100%',
    height: '400px',
  }), []);

  const locations = useMemo(() => [
    { 
      id: 1, 
      name: 'New York City', 
      position: { lat: 40.7128, lng: -74.006 },
      description: 'The city that never sleeps'
    },
    { 
      id: 2, 
      name: 'Central Park', 
      position: { lat: 40.7829, lng: -73.9654 },
      description: 'An urban park in Manhattan'
    },
    { 
      id: 3, 
      name: 'Times Square', 
      position: { lat: 40.7580, lng: -73.9855 },
      description: 'Major commercial intersection'
    },
  ], []);

  const onLoad = useCallback((map: google.maps.Map) => {
    setMap(map);
  }, []);

  const onUnmount = useCallback(() => {
    setMap(null);
  }, []);

  const handleMarkerClick = useCallback((id: number) => {
    setSelectedLocation(id);
    // Pan to the selected location
    const location = locations.find(loc => loc.id === id);
    if (location && map) {
      map.panTo(location.position);
    }
  }, [locations, map]);

  const handleInfoWindowClose = useCallback(() => {
    setSelectedLocation(null);
  }, []);

  if (loadError) {
    return <div>Error loading maps</div>;
  }

  if (!isLoaded) {
    return <div>Loading maps...</div>;
  }

  return (
    <GoogleMap
      mapContainerStyle={containerStyle}
      center={center}
      zoom={12}
      onLoad={onLoad}
      onUnmount={onUnmount}
    >
      {locations.map((location) => (
        <Marker
          key={location.id}
          position={location.position}
          onClick={() => handleMarkerClick(location.id)}
        />
      ))}

      {selectedLocation && (
        <InfoWindow
          position={locations.find(loc => loc.id === selectedLocation)?.position}
          onCloseClick={handleInfoWindowClose}
        >
          <div style={{ padding: '8px' }}>
            <h3 style={{ margin: '0 0 8px 0' }}>
              {locations.find(loc => loc.id === selectedLocation)?.name}
            </h3>
            <p style={{ margin: 0 }}>
              {locations.find(loc => loc.id === selectedLocation)?.description}
            </p>
          </div>
        </InfoWindow>
      )}
    </GoogleMap>
  );
}

export default Map;
5

Use the map in your app

Import and render your map component in your app.
App.tsx
import Map from './components/Map';

function App() {
  return (
    <div>
      <header style={{ padding: '20px', backgroundColor: '#f5f5f5' }}>
        <h1>My Google Maps App</h1>
      </header>
      <main style={{ padding: '20px' }}>
        <Map />
      </main>
    </div>
  );
}

export default App;

Complete Example

Here’s the full working code for reference:
import { GoogleMap, Marker, InfoWindow, useJsApiLoader } from '@react-google-maps/api';
import { useMemo, useState, useCallback } from 'react';

function Map() {
  const { isLoaded, loadError } = useJsApiLoader({
    googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY!,
  });

  const [map, setMap] = useState<google.maps.Map | null>(null);
  const [selectedLocation, setSelectedLocation] = useState<number | null>(null);

  const center = useMemo(() => ({ lat: 40.7128, lng: -74.006 }), []);

  const containerStyle = useMemo(() => ({
    width: '100%',
    height: '400px',
  }), []);

  const locations = useMemo(() => [
    { 
      id: 1, 
      name: 'New York City', 
      position: { lat: 40.7128, lng: -74.006 },
      description: 'The city that never sleeps'
    },
    { 
      id: 2, 
      name: 'Central Park', 
      position: { lat: 40.7829, lng: -73.9654 },
      description: 'An urban park in Manhattan'
    },
    { 
      id: 3, 
      name: 'Times Square', 
      position: { lat: 40.7580, lng: -73.9855 },
      description: 'Major commercial intersection'
    },
  ], []);

  const onLoad = useCallback((map: google.maps.Map) => {
    setMap(map);
  }, []);

  const onUnmount = useCallback(() => {
    setMap(null);
  }, []);

  const handleMarkerClick = useCallback((id: number) => {
    setSelectedLocation(id);
    const location = locations.find(loc => loc.id === id);
    if (location && map) {
      map.panTo(location.position);
    }
  }, [locations, map]);

  const handleInfoWindowClose = useCallback(() => {
    setSelectedLocation(null);
  }, []);

  if (loadError) {
    return <div>Error loading maps</div>;
  }

  if (!isLoaded) {
    return <div>Loading maps...</div>;
  }

  return (
    <GoogleMap
      mapContainerStyle={containerStyle}
      center={center}
      zoom={12}
      onLoad={onLoad}
      onUnmount={onUnmount}
    >
      {locations.map((location) => (
        <Marker
          key={location.id}
          position={location.position}
          onClick={() => handleMarkerClick(location.id)}
        />
      ))}

      {selectedLocation && (
        <InfoWindow
          position={locations.find(loc => loc.id === selectedLocation)?.position}
          onCloseClick={handleInfoWindowClose}
        >
          <div style={{ padding: '8px' }}>
            <h3 style={{ margin: '0 0 8px 0' }}>
              {locations.find(loc => loc.id === selectedLocation)?.name}
            </h3>
            <p style={{ margin: 0 }}>
              {locations.find(loc => loc.id === selectedLocation)?.description}
            </p>
          </div>
        </InfoWindow>
      )}
    </GoogleMap>
  );
}

export default Map;

Understanding the Code

API Loading

const { isLoaded, loadError } = useJsApiLoader({
  googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY!,
});
The useJsApiLoader hook:
  • Loads the Google Maps JavaScript API asynchronously
  • Returns isLoaded when ready
  • Returns loadError if loading fails
  • Only loads the API once, even if the component re-renders

Map Configuration

<GoogleMap
  mapContainerStyle={containerStyle}
  center={center}
  zoom={12}
  onLoad={onLoad}
  onUnmount={onUnmount}
>
Key props:
  • mapContainerStyle: CSS styles for the map container (must include height)
  • center: Initial center coordinates of the map
  • zoom: Initial zoom level (1-20, where 1 is world view)
  • onLoad: Callback when map is ready
  • onUnmount: Cleanup callback when map unmounts

Marker Rendering

{locations.map((location) => (
  <Marker
    key={location.id}
    position={location.position}
    onClick={() => handleMarkerClick(location.id)}
  />
))}
Markers are React components rendered as children of GoogleMap. Each marker:
  • Needs a unique key prop
  • Uses position to set its location
  • Can handle click events with onClick

Info Windows

{selectedLocation && (
  <InfoWindow
    position={locations.find(loc => loc.id === selectedLocation)?.position}
    onCloseClick={handleInfoWindowClose}
  >
    <div>{/* Custom content */}</div>
  </InfoWindow>
)}
Info windows:
  • Display custom React content
  • Use conditional rendering to show/hide
  • Can be anchored to markers or positions
  • Call onCloseClick when the user closes them

Best Practices

Always use useMemo and useCallback for props passed to map components to prevent unnecessary re-renders and API reloads.

Memoization

// ✅ Good - stable reference
const center = useMemo(() => ({ lat: 40.7128, lng: -74.006 }), []);

// ❌ Bad - new object every render
const center = { lat: 40.7128, lng: -74.006 };

Event Handlers

// ✅ Good - stable function reference
const handleClick = useCallback(() => {
  // handle click
}, []);

// ❌ Bad - new function every render
const handleClick = () => {
  // handle click
};

Loading Libraries

// ✅ Good - constant array
const libraries = ['places'];

function App() {
  const { isLoaded } = useJsApiLoader({
    googleMapsApiKey: apiKey,
    libraries: libraries,
  });
}

// ❌ Bad - new array every render
function App() {
  const { isLoaded } = useJsApiLoader({
    googleMapsApiKey: apiKey,
    libraries: ['places'], // Creates new array every render!
  });
}

Common Customizations

Custom Map Styles

Apply custom styling to your map:
const mapOptions = useMemo(() => ({
  styles: [
    {
      featureType: 'water',
      elementType: 'geometry',
      stylers: [{ color: '#193341' }],
    },
  ],
  disableDefaultUI: true,
  zoomControl: true,
}), []);

<GoogleMap
  mapContainerStyle={containerStyle}
  center={center}
  zoom={12}
  options={mapOptions}
/>

Custom Marker Icons

<Marker
  position={location.position}
  icon={{
    url: '/marker-icon.png',
    scaledSize: new google.maps.Size(40, 40),
  }}
/>

Map Click Events

const handleMapClick = useCallback((e: google.maps.MapMouseEvent) => {
  if (e.latLng) {
    console.log('Clicked at:', e.latLng.lat(), e.latLng.lng());
  }
}, []);

<GoogleMap
  mapContainerStyle={containerStyle}
  center={center}
  zoom={12}
  onClick={handleMapClick}
/>

Next Steps

Now that you have a working map, explore more advanced features:

Components

Explore all available components like Polyline, Polygon, Circle, and more

Hooks

Learn about useGoogleMap and other utility hooks

Directions

Add routing and directions to your map

Places

Integrate place search and autocomplete

Build docs developers (and LLMs) love