Get a Maps Demo Key: Try out select Maps JavaScript API and Places UI Kit features at no cost with a Maps Demo Key—no billing information required.

React Google Maps Library - Place Autocomplete

  • This example demonstrates the integration of the Places Autocomplete widget within a React application to dynamically update a map and marker.

  • It leverages the vis.gl/react-google-maps library, providing React components for interacting with the Google Maps JavaScript API.

  • The provided code snippets include TypeScript, JavaScript, CSS, and HTML to showcase the complete implementation.

  • Although the vis.gl/react-google-maps library is open source and not covered by Google Maps Platform support, the underlying Google Maps services used are still subject to the Google Maps Platform Terms of Service.

This example shows using the Places Autocomplete widget to update a map and marker in a React application. It uses the vis.gl/react-google-maps open source library. The vis.gl/react-google-maps library is a collection of React components and hooks for the Google Maps JavaScript API.

[フレーム]

TypeScript

importReact,{useState,useEffect,useRef}from'react';
import{createRoot}from'react-dom/client';
import{
APIProvider,
Map,
MapControl,
ControlPosition,
AdvancedMarker,
InfoWindow,
useMap,
useMapsLibrary,
useAdvancedMarkerRef,
}from'@vis.gl/react-google-maps';
constAPI_KEY='GOOGLE_MAPS_API_KEY';
declareglobal{
namespaceJSX{
interfaceIntrinsicElements{
'gmp-place-autocomplete':React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement>,
HTMLElement
>;
}
}
}
constPlaceAutocomplete=({
onPlaceSelect,
}:{
onPlaceSelect:(place:google.maps.places.Place|null)=>void;
})=>{
constmap=useMap();
constplacesLibrary=useMapsLibrary('places');
constcontainerRef=useRef<HTMLDivElement>(null);
useEffect(()=>{
if(!map||!placesLibrary||!containerRef.current)return;
// 1. Programmatically instantiate the modern PlaceAutocompleteElement
constautocomplete=newplacesLibrary.PlaceAutocompleteElement();
containerRef.current.appendChild(autocomplete);
// 2. Manually sync the map's bounds to the autocomplete's locationRestriction.
// We use map.getBounds().toJSON() to pass a plain object literal, which safely
// bypasses any cross-context 'instanceof' wipeout issues in React.
constsyncBounds=()=>{
constbounds=map.getBounds();
if(bounds){
autocomplete.locationRestriction=bounds.toJSON();
}
};
// Sync initially and whenever the map moves.
syncBounds();
constboundsListener=map.addListener('bounds_changed',syncBounds);
// 3. Listen for the gmp-select event.
constplaceSelectListener=(e:Event)=>{
constevent=easgoogle.maps.places.PlacePredictionSelectEvent;
constplace=event.placePrediction.toPlace();
voidplace
.fetchFields({
fields:[
'location',
'viewport',
'displayName',
'formattedAddress',
],
})
.then(()=>{
if(place.viewport){
map.fitBounds(place.viewport);
}elseif(place.location){
map.setCenter(place.location);
map.setZoom(13);
}
onPlaceSelect(place);
})
.catch((err:unknown)=>{
console.error(err);
});
};
autocomplete.addEventListener('gmp-select',placeSelectListener);
return()=>{
google.maps.event.removeListener(boundsListener);
autocomplete.removeEventListener('gmp-select',placeSelectListener);
// Clean up the DOM element when unmounting.
if(containerRef.current){
containerRef.current.innerHTML='';
}
};
},[map,placesLibrary,onPlaceSelect]);
return(
<div
className="place-autocomplete-card"
style={{
 backgroundColor: '#fff',
 borderRadius: '5px',
 boxShadow: 'rgba(0, 0, 0, 0.35) 0px 5px 15px',
 margin: '10px',
 padding: '5px',
 fontFamily: 'Roboto, sans-serif',
 fontSize: 'small',
 width: '300px',
 }}>
<divref={containerRef}style={{ width: '100%' }}/>
</div>
);
};
exportdefaultfunctionApp(){
const[selectedPlace,setSelectedPlace]=
useState<google.maps.places.Place|null>(null);
const[markerRef,marker]=useAdvancedMarkerRef();
return(
<APIProviderapiKey={API_KEY}>
<Map
defaultCenter={{ lat: 40.749933, lng: -73.98633 }}
defaultZoom={13}
gestureHandling={'greedy'}
mapId="DEMO_MAP_ID"
disableDefaultUI={true}>
<MapControlposition={ControlPosition.BLOCK_START_INLINE_START}>
<PlaceAutocompleteonPlaceSelect={setSelectedPlace}/>
</MapControl>
{selectedPlace?.location && (
<AdvancedMarker
ref={markerRef}
position={selectedPlace.location}
/>
)}
{selectedPlace?.location && marker && (
<InfoWindowanchor={marker}>
<div>
<spanstyle={{ fontWeight: 'bold' }}>
{selectedPlace.displayName??'No name'}
</span>
<br/>
<span>
{selectedPlace.formattedAddress??'No address'}
</span>
</div>
</InfoWindow>
)}
</Map>
</APIProvider>
);
}
exportfunctionrenderToDom(container:HTMLElement){
constroot=createRoot(container);
root.render(
<React.StrictMode>
<App/>
</React.StrictMode>
);
}

JavaScript

importReact,{useState,useEffect,useRef}from'react';
import{createRoot}from'react-dom/client';
import{APIProvider,Map,MapControl,ControlPosition,AdvancedMarker,InfoWindow,useMap,useMapsLibrary,useAdvancedMarkerRef,}from'@vis.gl/react-google-maps';
constAPI_KEY='GOOGLE_MAPS_API_KEY';
constPlaceAutocomplete=({onPlaceSelect,})=>{
constmap=useMap();
constplacesLibrary=useMapsLibrary('places');
constcontainerRef=useRef(null);
useEffect(()=>{
if(!map||!placesLibrary||!containerRef.current)
return;
// 1. Programmatically instantiate the modern PlaceAutocompleteElement
constautocomplete=newplacesLibrary.PlaceAutocompleteElement();
containerRef.current.appendChild(autocomplete);
// 2. Manually sync the map's bounds to the autocomplete's locationRestriction.
// We use map.getBounds().toJSON() to pass a plain object literal, which safely
// bypasses any cross-context 'instanceof' wipeout issues in React.
constsyncBounds=()=>{
constbounds=map.getBounds();
if(bounds){
autocomplete.locationRestriction=bounds.toJSON();
}
};
// Sync initially and whenever the map moves.
syncBounds();
constboundsListener=map.addListener('bounds_changed',syncBounds);
// 3. Listen for the gmp-select event.
constplaceSelectListener=(e)=>{
constevent=e;
constplace=event.placePrediction.toPlace();
voidplace
.fetchFields({
fields:[
'location',
'viewport',
'displayName',
'formattedAddress',
],
})
.then(()=>{
if(place.viewport){
map.fitBounds(place.viewport);
}
elseif(place.location){
map.setCenter(place.location);
map.setZoom(13);
}
onPlaceSelect(place);
})
.catch((err)=>{
console.error(err);
});
};
autocomplete.addEventListener('gmp-select',placeSelectListener);
return()=>{
google.maps.event.removeListener(boundsListener);
autocomplete.removeEventListener('gmp-select',placeSelectListener);
// Clean up the DOM element when unmounting.
if(containerRef.current){
containerRef.current.innerHTML='';
}
};
},[map,placesLibrary,onPlaceSelect]);
return(React.createElement("div",{className:"place-autocomplete-card",style:{
backgroundColor:'#fff',
borderRadius:'5px',
boxShadow:'rgba(0, 0, 0, 0.35) 0px 5px 15px',
margin:'10px',
padding:'5px',
fontFamily:'Roboto, sans-serif',
fontSize:'small',
width:'300px',
}},
React.createElement("div",{ref:containerRef,style:{width:'100%'}})));
};
exportdefaultfunctionApp(){
const[selectedPlace,setSelectedPlace]=useState(null);
const[markerRef,marker]=useAdvancedMarkerRef();
return(React.createElement(APIProvider,{apiKey:API_KEY},
React.createElement(Map,{defaultCenter:{lat:40.749933,lng:-73.98633},defaultZoom:13,gestureHandling:'greedy',mapId:"DEMO_MAP_ID",disableDefaultUI:true},
React.createElement(MapControl,{position:ControlPosition.BLOCK_START_INLINE_START},
React.createElement(PlaceAutocomplete,{onPlaceSelect:setSelectedPlace})),
selectedPlace?.location && (React.createElement(AdvancedMarker,{ref:markerRef,position:selectedPlace.location})),
selectedPlace?.location && marker && (React.createElement(InfoWindow,{anchor:marker},
React.createElement("div",null,
React.createElement("span",{style:{fontWeight:'bold'}},selectedPlace.displayName??'No name'),
React.createElement("br",null),
React.createElement("span",null,selectedPlace.formattedAddress??'No address')))))));
}
exportfunctionrenderToDom(container){
constroot=createRoot(container);
root.render(React.createElement(React.StrictMode,null,
React.createElement(App,null)));
}

CSS

body{
margin:0;
font-family:sans-serif;
}
#app{
width:100vw;
height:100vh;
}
.autocomplete-containerinput,
.autocomplete-control{
box-sizing:border-box;
}
.autocomplete-control{
margin:24px;
background:#fff;
}
.autocomplete-container{
width:300px;
}
.autocomplete-containerinput{
width:100%;
height:40px;
padding:012px;
font-size:18px;
}
.autocomplete-container.custom-list{
width:100%;
list-style:none;
padding:0;
margin:0;
}
.autocomplete-container.custom-list-item{
padding:8px;
}
.autocomplete-container.custom-list-item:hover{
background:lightgrey;
cursor:pointer;
}

HTML

<html lang="en">
 <head>
 <meta charset="utf-8" />
 <meta
 name="viewport"
 content="width=device-width, initial-scale=1.0, user-scalable=no" />
 <title>React - react place autocomplete map</title>
 <style>
 body {
 margin: 0;
 font-family: sans-serif;
 }
 #app {
 width: 100vw;
 height: 100vh;
 }
 </style>
 <script type="module">
 import { renderToDom } from './src/app';
 renderToDom(document.querySelector('#app'));
 </script>
 </head>
 <body>
 <div id="app"></div>
 </body>
</html>

Clone Sample

Git and Node.js are required to run this sample locally. Follow these instructions to install Node.js and NPM. The following commands clone, install dependencies and start the sample application.

gitclonehttps://github.com/googlemaps-samples/js-api-samples.git
cdsamples/rgm-autocomplete
npmi
npmstart

Integration notes

When integrating Google Maps Place Autocomplete within a React application, this sample implements several key best practices and addresses common issues developers encounter with the new Places API.

1. Programmatic Instantiation

Instead of rendering the <gmp-place-autocomplete> Web Component directly in JSX, this sample programmatically instantiates it using new placesLibrary.PlaceAutocompleteElement() and appends it to a React ref.

  • Issue: React's synthetic event system doesn't always seamlessly handle custom Web Component events (like gmp-select). Instantiating the element programmatically and attaching standard DOM event listeners ensures events are captured reliably.

2. Location Restriction & Cross-Context Objects

When placing the autocomplete Web Component outside the main DOM tree of the map, it may lose automatic context of the map's viewport. To ensure that search predictions are strictly biased or restricted to the map's current bounds, this sample manually syncs the map's bounds to the autocomplete's locationRestriction property.

  • Issue: Passing complex Google Maps objects (like LatLngBounds) directly across the React boundary can sometimes fail due to cross-context instanceof checks. Always use .toJSON() (e.g., map.getBounds().toJSON()) when assigning bounds to bypass these issues. This ensures users see search predictions relevant to their map view.

3. Handling Selections: toPlace() and fetchFields()

When a user selects an item from the autocomplete drop-down, the component fires a gmp-select event containing a placePrediction.

  • Issue: The prediction is not a fully populated Place object. You must convert it using placePrediction.toPlace() and then explicitly request the data you need by calling place.fetchFields({ fields: ['location', 'displayName', 'formattedAddress'] }).
  • If you attempt to access a property on the Place object that hasn't been fetched, it will be undefined or throw an error. This is a core design principle of the new Places API to ensure you only request (and pay for) the data you use.

4. Event Cleanup

Always remove standard DOM event listeners (e.g., autocomplete.removeEventListener) and Maps event listeners (google.maps.event.removeListener) in your useEffect cleanup function to prevent memory leaks when the React component unmounts.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026年09月01日 UTC.