Integrating Maps with React

Gourav NathGourav Nath
8 min read

Maps are an essential feature in modern web applications, whether you're helping users find nearby locations, plot routes, or display key points of interest. In a recent project, I integrated interactive map features into a web application using React — and it turned out to be a really fun!

In this post, I’ll show you how I implemented:

👽User location detection

🔍Search with autocomplete

📍Displaying markers

🧭Get Place Details by Coordinates

Initial Setup: API Key & Library

To make this work, you'll need an API key for the Google Maps JavaScript API. For the examples to run correctly, this key should be set using an environment variable called API_KEY. The easiest way to do this is by adding your API key to a .env file in your project’s root directory.

API_KEY=<your API key here>

then run:

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

We use@vis.gl/react-google-maps because it’s a modern, lightweight, and well-maintained wrapper around the official Google Maps JavaScript API.

Rendering the Map in React

To render a map in our React app, we start by using the APIProvider and Map components provided by @vis.gl/react-google-maps. The APIProvider wraps our map and provides the API key needed to access Google Maps services.

In this basic setup:

  • The map takes up the full width and height of the viewport.

  • We set a default center location (latitude and longitude).

  • defaultZoom defines the initial zoom level.

  • gestureHandling="greedy" allows users to scroll and zoom smoothly inside the map.

This gives us a full-screen, interactive Google Map ready to be extended with more features like markers, user location, and search.

import { APIProvider, Map } from "@vis.gl/react-google-maps";

function App() {
  return (
    <div>
      <APIProvider apiKey={process.env.API_KEY}>
        <Map
          style={{ width: "100vw", height: "100vh" }}
          defaultCenter={{ lat: 22.54992, lng: 0 }}
          defaultZoom={3}
          gestureHandling={"greedy"}
          disableDefaultUI={true}
        />
      </APIProvider>
    </div>
  );
}

export default App;

👽Detecting and Showing User Location

To make the map more interactive and personalized, we can detect the user’s current location using the browser’s Geolocation API. This allows us to center the map on their real-time position and even drop a marker if needed.
The following function wraps the geolocation call in a Promise, making it easy to use with async/await or .then() syntax. It ensures high accuracy, handles timeouts, and gracefully manages errors if location access is denied or unsupported.

// Function to get the user's current location using the Geolocation API
const getUserLocation = () => {
  return new Promise((resolve, reject) => {
    // Check if geolocation is supported by the browser
    if (navigator.geolocation) {
      // Request the current position
      navigator.geolocation.getCurrentPosition(
        (position) => {
          // Extract latitude and longitude from the position object
          const { latitude, longitude } = position.coords;
          // Resolve the Promise with a simplified object format
          resolve({ lat: latitude, lng: longitude });
        },
        (error) => {
          // Log and reject in case of an error (e.g. permission denied, timeout)
          console.error("Error getting user location:", error);
          reject(error);
        },
        {
          enableHighAccuracy: true, // Use GPS for more accurate results
          maximumAge: 0,            // Don't use a cached position
          timeout: 30000,           // Wait up to 30 seconds before timing out
        }
      );
    } else {
      // If geolocation is not supported, reject with a custom error
      const error = new Error("Geolocation is not supported by this browser.");
      console.error(error.message);
      reject(error);
    }
  });
};

🔍Search with autocomplete

One of the coolest features in the project is a search bar — allowing users to start typing a place and instantly see smart suggestions pop up. It makes the experience faster and more interactive.

Behind the scenes, a custom hook handles all the autocomplete logic using the Google Places API. This hook is used inside a dedicated Autocomplete component that takes care of the input UI and displaying the suggestions.

// Custom hook to fetch place suggestions using Google Places Autocomplete
export function useAutocompleteSuggestions(inputString, requestOptions = {}) {
  // Load the "places" library from Google Maps
  const placesLib = useMapsLibrary("places");

  // Stores the current Autocomplete session token
  const sessionTokenRef = useRef(null);

  // Stores the autocomplete suggestions returned from the API
  const [suggestions, setSuggestions] = useState([]);

  // Indicates whether a request is currently in progress
  const [isLoading, setIsLoading] = useState(false);

  // Fetch autocomplete suggestions whenever input changes
  useEffect(() => {
    if (!placesLib) return;

    const { AutocompleteSessionToken, AutocompleteSuggestion } = placesLib;

    // Generate a new session token if one doesn’t exist
    if (!sessionTokenRef.current) {
      sessionTokenRef.current = new AutocompleteSessionToken();
    }

    const request = {
      ...requestOptions,
      input: inputString,
      sessionToken: sessionTokenRef.current,
    };

    // Clear suggestions if input is empty
    if (inputString === "") {
      if (suggestions.length > 0) setSuggestions([]);
      return;
    }

    // Start loading and fetch suggestions from the API
    setIsLoading(true);
    AutocompleteSuggestion.fetchAutocompleteSuggestions(request).then((res) => {
      setSuggestions(res.suggestions);
      setIsLoading(false);
    });
  }, [placesLib, inputString]);

  // Expose suggestions, loading state, and a way to reset the session
  return {
    suggestions,         // array of autocomplete suggestion results
    isLoading,           // whether the API request is in progress
    resetSession: () => {
      sessionTokenRef.current = null;  // reset the session token
      setSuggestions([]);              // clear the suggestions
    },
  };
}

Now that the hook is set up, it can be used inside a component to power an input field and display live location suggestions as the user types.

import React, { useState } from "react";
import { useAutocompleteSuggestions } from "./useAutocompleteSuggestions";

function Autocomplete() {
  const [inputValue, setInputValue] = useState("");

  // Use the custom hook to get suggestions and loading state
  const { suggestions, isLoading, resetSession } = useAutocompleteSuggestions(inputValue);

  const handleSelect = (suggestion) => {
    // You can handle what happens when a suggestion is selected here
    console.log("Selected place:", suggestion);
    resetSession(); // Optional: reset session after a selection
    setInputValue({suggestion?.placePrediction?.text?.text} || ""); // Update input
  };

  return (
    <div>
      <input
        type="text"
        placeholder="Search a location"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />

      {isLoading && <p>Loading suggestions...</p>}

      {suggestions.length > 0 && (
        <ul>
          {suggestions.map((suggestion, index) => (
            <li
              key={index}
              onClick={() => handleSelect(suggestion)}
            >
              {suggestion?.placePrediction?.text?.text}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default Autocomplete;

📍Displaying markers

Markers help show key spots on the map — like where the user is or where a searched location lands. In this setup, we’re dropping two types of markers: one for the user’s current location and another for the selected search result. Clicking on either marker opens a little info window with more details, like coordinates or an address.

const LocationMarkers = ({ userLocation, searchLocation }) => {
  const [activeMarker, setActiveMarker] = useState(null);

  const toggleInfoWindow = (markerId) => {
    setActiveMarker(activeMarker === markerId ? null : markerId);
  };

  return (
    <>
      {/* User Location Marker */}
      {userLocation && (
        <>
          <AdvancedMarker 
            position={userLocation}
            onClick={() => toggleInfoWindow("user")}
          >
            <div 
                style={{ 
                width: "16px", 
                height: "16px", 
                backgroundColor: "blue", 
                borderRadius: "50%" 
             }}>
             </div>
          </AdvancedMarker>

          {activeMarker === "user" && (
            <InfoWindow
              position={userLocation}
              onClose={() => toggleInfoWindow(null)}
            >
              <div>
                <h5>Your Location</h5>
                <p>Latitude: {userLocation.lat.toFixed(6)}</p>
                <p>Longitude: {userLocation.lng.toFixed(6)}</p>
              </div>
            </InfoWindow>
          )}
        </>
      )}

      {/* Search Location Marker */}
      {searchLocation && searchLocation.lat && searchLocation.lng && (
        <>
          <AdvancedMarker 
            position={searchLocation}
            onClick={() => toggleInfoWindow("search")}
          >
            <div style={{ width: "20px", height: "20px", color: "red" }}>📍</div>
          </AdvancedMarker>

          {activeMarker === "search" && (
            <InfoWindow
              position={searchLocation}
              onClose={() => toggleInfoWindow(null)}
            >
              <div>
                <h5>Search Location</h5>
                {searchLocation.address && <p>{searchLocation.address}</p>}
                <p>Latitude: {searchLocation.lat.toFixed(6)}</p>
                <p>Longitude: {searchLocation.lng.toFixed(6)}</p>
              </div>
            </InfoWindow>
          )}
        </>
      )}
    </>
  );
};

🧭Get Place Details by Coordinates

Sometimes, all you have are latitude and longitude values — like when a user taps on a map or shares their current location. To turn those raw coordinates into something more meaningful (like a full address, city, state, or postal code), we can use the Google Maps Geocoding API.

The function below handles that for you. It takes in coordinates and an API key, sends a request to the API, and returns a clean object with all the useful location details extracted.

// Function to get location details (like address, city, state, postal code) from latitude and longitude
const getLocationDataFromCoordinates = (lat, lng, apiKey) => {
  return new Promise((resolve, reject) => {
    // Construct the URL for the Google Geocoding API using provided coordinates and API key
    const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${lat},${lng}&key=${apiKey}`;

    // Make a request to the Geocoding API
    fetch(url)
      .then((response) => response.json())
      .then((data) => {
        // If no results found, reject with an error
        if (!data.results || data.results.length === 0) {
          reject(new Error("No address found for the given coordinates"));
          return;
        }

        // Get the full formatted address and place ID from the first result
        const formattedAddress = data.results[0]?.formatted_address;
        const placeId = data.results[0]?.place_id;

        // Extract specific components from the address (like city, state, postal code)
        const addressComponents = data.results[0]?.address_components;
        let postalCode = "";
        let state = "";
        let city = "";

        // Loop through the components to find desired details
        for (const component of addressComponents) {
          if (component.types.includes("postal_code")) {
            postalCode = component.long_name;
          }
          if (component.types.includes("administrative_area_level_1")) {
            state = component.long_name;
          }
          if (component.types.includes("locality")) {
            city = component.long_name;
          }
        }

        // Create a structured object with all the extracted location data
        const locationData = {
          address: formattedAddress,
          postalCode: postalCode,
          state: state,
          city: city,
          placeId: placeId,
          latlng: {
            lat: lat,
            lng: lng,
          },
        };

        // Resolve the promise with the location data
        resolve(locationData);
      })
      .catch((error) => {
        // If any error occurs during the fetch, reject the promise
        reject(error);
      });
  });
};

🚀 Wrapping Up

Integrating maps into a web application might seem complex at first, but with the right tools and APIs, it becomes a really enjoyable experience. In this project, we explored several key features that are both practical and user-friendly — detecting user location, implementing a smart autocomplete search, displaying interactive markers, and fetching detailed place information from coordinates.

Using @vis.gl/react-google-maps provided a modern and React-friendly way to work with the Google Maps JavaScript API. Its simplicity, TypeScript support, and lightweight nature made development smoother and more maintainable.

These features aren’t just bells and whistles — they create meaningful interactions that improve the user experience, whether it’s helping someone find the nearest warehouse or letting them search a location with ease.

This integration lays a solid foundation for any map-based functionality you might want to add in the future — from routing and directions to custom overlays and analytics.

Thanks for following along! Hope this walkthrough gave you a good starting point for adding dynamic map features to your own projects. If you have any questions or want to share how you’re using maps in your app, feel free to drop a comment or reach out!

Github Gist:

0
Subscribe to my newsletter

Read articles from Gourav Nath directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Gourav Nath
Gourav Nath