Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ <h1>Maps JSAPI Samples</h1>
<li><a href='/samples/react-ui-kit-search-nearby/dist'>react-ui-kit-search-nearby</a></li>
<li><a href='/samples/react-ui-kit-search-text/dist'>react-ui-kit-search-text</a></li>
<li><a href='/samples/rectangle-event/dist'>rectangle-event</a></li>
<li><a href='/samples/rgm-autocomplete/dist'>rgm-autocomplete</a></li>
<li><a href='/samples/rgm-basic-map/dist'>rgm-basic-map</a></li>
<li><a href='/samples/routes-compute-routes/dist'>routes-compute-routes</a></li>
<li><a href='/samples/routes-get-alternatives/dist'>routes-get-alternatives</a></li>
Expand Down
13 changes: 13 additions & 0 deletions dist/samples/rgm-autocomplete/app/.eslintsrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": [
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"rules": {
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-this-alias": 1,
"@typescript-eslint/no-empty-function": 1,
"@typescript-eslint/explicit-module-boundary-types": 1,
"@typescript-eslint/no-unused-vars": 1
}
}
70 changes: 70 additions & 0 deletions dist/samples/rgm-autocomplete/app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Google Maps JavaScript Sample

## rgm-autocomplete

React Google Maps Library - Place Autocomplete

This sample demonstrates using the Place Autocomplete Element within a React application using the open source vis.gl/react-google-maps library.

## Setup

### Before starting run:

`npm i`

### Run an example on a local web server

`cd samples/rgm-autocomplete`
`npm start`

### Build an individual example

`cd samples/rgm-autocomplete`
`npm run build`

From 'samples':

`npm run build --workspace=rgm-autocomplete/`

### Build all of the examples.

From 'samples':

`npm run build-all`

### Run lint to check for problems

`cd samples/rgm-autocomplete`
`npx eslint index.ts`

## Feedback

For feedback related to this sample, please open a new issue on
[GitHub](https://github.com/googlemaps-samples/js-api-samples/issues).

## Integrating Place Autocomplete & The New Place Class

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 guarantee 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 dropdown, 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.
35 changes: 35 additions & 0 deletions dist/samples/rgm-autocomplete/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!doctype html>
<!--
@license
Copyright 2026 Google LLC. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
-->
<!-- [START maps_react_place_autocomplete_map] -->
<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>
<!-- [END maps_react_place_autocomplete_map] -->
13 changes: 13 additions & 0 deletions dist/samples/rgm-autocomplete/app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "@js-api-samples/rgm-autocomplete",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "bash ../build-single.sh",
"test": "tsc && npm run build:vite --workspace=.",
"start": "tsc && vite build --config ../../vite.config.js --base './' && vite --config ../../vite.config.js",
"build:vite": "vite build --config ../../vite.config.js --base './'",
"preview": "vite preview --config ../../vite.config.js"
},
"author": "Google LLC"
}
54 changes: 54 additions & 0 deletions dist/samples/rgm-autocomplete/app/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @license
* Copyright 2024 Google LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
/* [START maps_rgm_autocomplete] */
body {
margin: 0;
font-family: sans-serif;
}

#app {
width: 100vw;
height: 100vh;
}

.autocomplete-container input,
.autocomplete-control {
box-sizing: border-box;
}

.autocomplete-control {
margin: 24px;
background: #fff;
}

.autocomplete-container {
width: 300px;
}

.autocomplete-container input {
width: 100%;
height: 40px;
padding: 0 12px;
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;
}

/* [END maps_rgm_autocomplete] */
8 changes: 8 additions & 0 deletions dist/samples/rgm-autocomplete/app/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.react-base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["./src/**/*.ts*"]
}
101 changes: 101 additions & 0 deletions dist/samples/rgm-autocomplete/dist/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* @license
* Copyright 2026 Google LLC. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
// [START maps_rgm_autocomplete]
import React, { 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';
const API_KEY = 'GOOGLE_MAPS_API_KEY';
const PlaceAutocomplete = ({ onPlaceSelect, }) => {
const map = useMap();
const placesLibrary = useMapsLibrary('places');
const containerRef = useRef(null);
useEffect(() => {
if (!map || !placesLibrary || !containerRef.current)
return;
// 1. Programmatically instantiate the modern PlaceAutocompleteElement
const autocomplete = new placesLibrary.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.
const syncBounds = () => {
const bounds = map.getBounds();
if (bounds) {
autocomplete.locationRestriction = bounds.toJSON();
}
};
// Sync initially and whenever the map moves.
syncBounds();
const boundsListener = map.addListener('bounds_changed', syncBounds);
// 3. Listen for the gmp-select event.
const placeSelectListener = (e) => {
const event = e;
const place = event.placePrediction.toPlace();
void place
.fetchFields({
fields: [
'location',
'viewport',
'displayName',
'formattedAddress',
],
})
.then(() => {
if (place.viewport) {
map.fitBounds(place.viewport);
}
else if (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%' } })));
};
export default function App() {
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')))))));
}
export function renderToDom(container) {
const root = createRoot(container);
root.render(React.createElement(React.StrictMode, null,
React.createElement(App, null)));
}
// [END maps_rgm_autocomplete]
13 changes: 13 additions & 0 deletions dist/samples/rgm-autocomplete/dist/assets/index-CoTzf8m8.js

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions dist/samples/rgm-autocomplete/dist/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<!--
@license
Copyright 2026 Google LLC. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
-->
<!-- [START maps_react_place_autocomplete_map] -->
<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" crossorigin src="./assets/index-CoTzf8m8.js"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>
<!-- [END maps_react_place_autocomplete_map] -->
Loading