Integrating Maps with React


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:
Subscribe to my newsletter
Read articles from Gourav Nath directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by