From ab82827596be46f94bccd6f8713d68290566a80b Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 07:50:34 -0700 Subject: [PATCH 01/16] experiment: Creates React versions of some basic samples. Change-Id: Ibd4d5bc42aa695d03d54b69ad16cd21e1fc64ea8 --- package-lock.json | 16 ++ samples/new-react-sample.sh | 125 ++++++++++++ samples/react-circle-simple/README.md | 41 ++++ samples/react-circle-simple/index.html | 53 ++++++ samples/react-circle-simple/package.json | 13 ++ samples/react-circle-simple/src/app.tsx | 179 ++++++++++++++++++ samples/react-circle-simple/tsconfig.json | 8 + .../react-place-autocomplete-map/index.html | 35 ++++ .../react-place-autocomplete-map/package.json | 13 ++ .../react-place-autocomplete-map/src/app.tsx | 152 +++++++++++++++ .../test-script.tsx | 1 + .../tsconfig.json | 8 + .../react-web-components-markers/README.md | 41 ++++ .../react-web-components-markers/index.html | 38 ++++ .../react-web-components-markers/package.json | 13 ++ .../react-web-components-markers/src/app.tsx | 39 ++++ .../tsconfig.json | 8 + samples/rgm-autocomplete/README.md | 41 ++++ samples/rgm-autocomplete/index.html | 35 ++++ samples/rgm-autocomplete/package.json | 13 ++ samples/rgm-autocomplete/src/app.tsx | 121 ++++++++++++ samples/rgm-autocomplete/style.css | 54 ++++++ samples/rgm-autocomplete/tsconfig.json | 8 + 23 files changed, 1055 insertions(+) create mode 100755 samples/new-react-sample.sh create mode 100644 samples/react-circle-simple/README.md create mode 100644 samples/react-circle-simple/index.html create mode 100644 samples/react-circle-simple/package.json create mode 100644 samples/react-circle-simple/src/app.tsx create mode 100644 samples/react-circle-simple/tsconfig.json create mode 100644 samples/react-place-autocomplete-map/index.html create mode 100644 samples/react-place-autocomplete-map/package.json create mode 100644 samples/react-place-autocomplete-map/src/app.tsx create mode 100644 samples/react-place-autocomplete-map/test-script.tsx create mode 100644 samples/react-place-autocomplete-map/tsconfig.json create mode 100644 samples/react-web-components-markers/README.md create mode 100644 samples/react-web-components-markers/index.html create mode 100644 samples/react-web-components-markers/package.json create mode 100644 samples/react-web-components-markers/src/app.tsx create mode 100644 samples/react-web-components-markers/tsconfig.json create mode 100644 samples/rgm-autocomplete/README.md create mode 100644 samples/rgm-autocomplete/index.html create mode 100644 samples/rgm-autocomplete/package.json create mode 100644 samples/rgm-autocomplete/src/app.tsx create mode 100644 samples/rgm-autocomplete/style.css create mode 100644 samples/rgm-autocomplete/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 78daf4356..ab6fb7f87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1359,6 +1359,10 @@ "resolved": "samples/polyline-utility", "link": true }, + "node_modules/@js-api-samples/react-circle-simple": { + "resolved": "samples/react-circle-simple", + "link": true + }, "node_modules/@js-api-samples/react-places-ui-kit-search-nearby": { "resolved": "samples/react-ui-kit-search-nearby", "link": true @@ -1383,6 +1387,10 @@ "resolved": "samples/react-ui-kit-place-details-latlng-compact", "link": true }, + "node_modules/@js-api-samples/react-web-components-markers": { + "resolved": "samples/react-web-components-markers", + "link": true + }, "node_modules/@js-api-samples/rectangle-event": { "resolved": "samples/rectangle-event", "link": true @@ -6931,6 +6939,10 @@ "name": "@js-api-samples/polyline-utility", "version": "1.0.0" }, + "samples/react-circle-simple": { + "name": "@js-api-samples/react-circle-simple", + "version": "1.0.0" + }, "samples/react-ui-kit-place-details": { "name": "@js-api-samples/react-ui-kit-place-details", "version": "1.0.0" @@ -6964,6 +6976,10 @@ "@vitejs/plugin-react": "^6.0.2" } }, + "samples/react-web-components-markers": { + "name": "@js-api-samples/react-web-components-markers", + "version": "1.0.0" + }, "samples/rectangle-event": { "name": "@js-api-samples/rectangle-event", "version": "1.0.0" diff --git a/samples/new-react-sample.sh b/samples/new-react-sample.sh new file mode 100755 index 000000000..a0bdfa805 --- /dev/null +++ b/samples/new-react-sample.sh @@ -0,0 +1,125 @@ +#!/bin/bash + +# A script to generate boilerplate for a new Google Maps React sample. +# Usage: ./new-react-sample.sh + +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +NAME=$1 +# Replace hyphens with underscores for the region tag +REGION_TAG="maps_${NAME//-/_}" +TITLE="React - ${NAME//-/ }" + +# Create the directory +mkdir -p "$NAME/src" + +# Create src/app.tsx +cat > "$NAME/src/app.tsx" << EOF +/** + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START ${REGION_TAG}_app] +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { APIProvider, Map } from '@vis.gl/react-google-maps'; + +const API_KEY = 'GOOGLE_MAPS_API_KEY'; + +export default function App() { + return ( + + + {/* Add map components here */} + + + ); +} + +export function renderToDom(container: HTMLElement) { + const root = createRoot(container); + root.render( + + + + ); +} +// [END ${REGION_TAG}_app] +EOF + +# Create index.html +cat > "$NAME/index.html" << EOF + + + + + + + + $TITLE + + + + +
+ + + +EOF + +# Create package.json +cat > "$NAME/package.json" << EOF +{ + "name": "@js-api-samples/$NAME", + "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" +} +EOF + +# Create tsconfig.json +cat > "$NAME/tsconfig.json" << EOF +{ + "extends": "../../tsconfig.react-base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts*"] +} +EOF + +echo "Created React sample '$NAME' successfully!" diff --git a/samples/react-circle-simple/README.md b/samples/react-circle-simple/README.md new file mode 100644 index 000000000..e68d1b300 --- /dev/null +++ b/samples/react-circle-simple/README.md @@ -0,0 +1,41 @@ +# Google Maps JavaScript Sample + +## react-circle-simple + +Add a meaningful description for react-circle-simple here... + +## Setup + +### Before starting run: + +`npm i` + +### Run an example on a local web server + +`cd samples/react-circle-simple` +`npm start` + +### Build an individual example + +`cd samples/react-circle-simple` +`npm run build` + +From 'samples': + +`npm run build --workspace=react-circle-simple/` + +### Build all of the examples. + +From 'samples': + +`npm run build-all` + +### Run lint to check for problems + +`cd samples/react-circle-simple` +`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). diff --git a/samples/react-circle-simple/index.html b/samples/react-circle-simple/index.html new file mode 100644 index 000000000..f84b7c32d --- /dev/null +++ b/samples/react-circle-simple/index.html @@ -0,0 +1,53 @@ + + + + + + + + React - Simple Circle + + + + + +
+ + + diff --git a/samples/react-circle-simple/package.json b/samples/react-circle-simple/package.json new file mode 100644 index 000000000..571c2c4c1 --- /dev/null +++ b/samples/react-circle-simple/package.json @@ -0,0 +1,13 @@ +{ + "name": "@js-api-samples/react-circle-simple", + "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" +} diff --git a/samples/react-circle-simple/src/app.tsx b/samples/react-circle-simple/src/app.tsx new file mode 100644 index 000000000..10a5d76ba --- /dev/null +++ b/samples/react-circle-simple/src/app.tsx @@ -0,0 +1,179 @@ +/** + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START maps_react_circle_simple_app] +import React, { useEffect, useState, useRef } from 'react'; +import { createRoot } from 'react-dom/client'; +import { APIProvider, Map, AdvancedMarker, useMap, MapMouseEvent } from '@vis.gl/react-google-maps'; + +const API_KEY = 'GOOGLE_MAPS_API_KEY'; + +// ----------------------------------------------------------------------------- +// The @vis.gl/react-google-maps library does not provide a native component. +// Users are expected to build their own wrapper component using the useMap() hook +// and the imperative google.maps.Circle class. +// ----------------------------------------------------------------------------- +type CircleProps = google.maps.CircleOptions & { + onCenterChanged?: (center: google.maps.LatLngLiteral) => void; +}; + +const Circle = ({ onCenterChanged, ...options }: CircleProps) => { + const map = useMap(); + const circleRef = useRef(null); + + useEffect(() => { + if (!map) return; + + // Instantiate the circle only once when the map is ready + circleRef.current = new google.maps.Circle({ + map, + ...options + }); + + return () => { + if (circleRef.current) { + circleRef.current.setMap(null); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [map]); + + // Update the circle imperatively when React props (options) change. + // We explicitly extract radius and center to avoid passing a new options object + // reference on every render, which causes infinite loops! + const { radius, center } = options; + useEffect(() => { + if (circleRef.current) { + circleRef.current.setOptions({ radius, center }); + } + }, [radius, center]); + + // Handle events + useEffect(() => { + if (!circleRef.current || !onCenterChanged) return; + + const listener = circleRef.current.addListener('center_changed', () => { + const newCenter = circleRef.current?.getCenter(); + if (newCenter) { + const lat = newCenter.lat(); + const lng = newCenter.lng(); + + // CRITICAL FIX: The Maps API has floating point precision loss when it + // projects coordinates. Calling setOptions({center}) slightly changes the float, + // which fires center_changed again, resulting in an infinite loop! + // We only update React state if the drag distance was meaningful. + if ( + !center || + Math.abs(lat - (center.lat as number)) > 0.000001 || + Math.abs(lng - (center.lng as number)) > 0.000001 + ) { + onCenterChanged({ lat, lng }); + } + } + }); + + return () => { + google.maps.event.removeListener(listener); + }; + }, [onCenterChanged, center]); + + return null; // A circle has no DOM representation, so we return null +}; + +// ----------------------------------------------------------------------------- +// Component to handle map clicks and imperatively pan the map +// ----------------------------------------------------------------------------- +const MapClickHandler = ({ onMapClick }: { onMapClick: (latLng: google.maps.LatLngLiteral) => void }) => { + const map = useMap(); + + useEffect(() => { + if (!map) return; + const listener = map.addListener('click', (e: google.maps.MapMouseEvent) => { + if (e.latLng) { + const center = { lat: e.latLng.lat(), lng: e.latLng.lng() }; + onMapClick(center); + map.panTo(center); // Imperatively pan the map + } + }); + + return () => google.maps.event.removeListener(listener); + }, [map, onMapClick]); + + return null; +}; + +// ----------------------------------------------------------------------------- +// Main Application +// ----------------------------------------------------------------------------- +export default function App() { + const initialCenter = { lat: 34.98956821576194, lng: 135.74239981260283 }; + const [circleCenter, setCircleCenter] = useState(initialCenter); + const [radius, setRadius] = useState(400); + + return ( + +
+ Radius + + + +
+ + + {/* Handles clicks and pans the map imperatively */} + + + + + +
+ + + +
+
+
+
+ ); +} + +export function renderToDom(container: HTMLElement) { + const root = createRoot(container); + root.render( + + + + ); +} +// [END maps_react_circle_simple_app] diff --git a/samples/react-circle-simple/tsconfig.json b/samples/react-circle-simple/tsconfig.json new file mode 100644 index 000000000..b7054a4ed --- /dev/null +++ b/samples/react-circle-simple/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.react-base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts*"] +} diff --git a/samples/react-place-autocomplete-map/index.html b/samples/react-place-autocomplete-map/index.html new file mode 100644 index 000000000..577474987 --- /dev/null +++ b/samples/react-place-autocomplete-map/index.html @@ -0,0 +1,35 @@ + + + + + + + + React - react place autocomplete map + + + + +
+ + + diff --git a/samples/react-place-autocomplete-map/package.json b/samples/react-place-autocomplete-map/package.json new file mode 100644 index 000000000..ebaee0be1 --- /dev/null +++ b/samples/react-place-autocomplete-map/package.json @@ -0,0 +1,13 @@ +{ + "name": "@js-api-samples/react-place-autocomplete-map", + "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" +} diff --git a/samples/react-place-autocomplete-map/src/app.tsx b/samples/react-place-autocomplete-map/src/app.tsx new file mode 100644 index 000000000..424a37dc5 --- /dev/null +++ b/samples/react-place-autocomplete-map/src/app.tsx @@ -0,0 +1,152 @@ +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'; + +declare global { + namespace JSX { + interface IntrinsicElements { + 'gmp-place-autocomplete': React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + >; + } + } +} + +const PlaceAutocomplete = ({ + onPlaceSelect, +}: { + onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; +}) => { + const map = useMap(); + const placesLibrary = useMapsLibrary('places'); + const inputRef = useRef(null); + + useEffect(() => { + if (!map || !placesLibrary || !inputRef.current) return; + + const options = { + fields: ['geometry', 'name', 'formatted_address'], + strictBounds: true, // This enforces the same strict bounds constraint as locationRestriction + }; + + const autocomplete = new placesLibrary.Autocomplete(inputRef.current, options); + + // The traditional API has a built-in bindTo method that flawlessly syncs to the map's internal bounds state + autocomplete.bindTo('bounds', map); + + const listener = autocomplete.addListener('place_changed', () => { + const place = autocomplete.getPlace(); + + if (!place.geometry || !place.geometry.location) { + onPlaceSelect(null); + return; + } + + if (place.geometry.viewport) { + map.fitBounds(place.geometry.viewport); + } else { + map.setCenter(place.geometry.location); + map.setZoom(17); + } + + onPlaceSelect(place); + }); + + return () => { + google.maps.event.removeListener(listener); + }; + }, [map, placesLibrary, onPlaceSelect]); + + return ( +
+ +
+ ); +}; + +export default function App() { + const [selectedPlace, setSelectedPlace] = + useState(null); + const [markerRef, marker] = useAdvancedMarkerRef(); + + return ( + + + + + + + {selectedPlace?.geometry?.location && ( + + )} + + {selectedPlace?.geometry?.location && marker && ( + +
+ + {selectedPlace.name ?? 'No name'} + +
+ + {selectedPlace.formatted_address ?? 'No address'} + +
+
+ )} +
+
+ ); +} + +export function renderToDom(container: HTMLElement) { + const root = createRoot(container); + root.render( + + + + ); +} diff --git a/samples/react-place-autocomplete-map/test-script.tsx b/samples/react-place-autocomplete-map/test-script.tsx new file mode 100644 index 000000000..f0fb474fa --- /dev/null +++ b/samples/react-place-autocomplete-map/test-script.tsx @@ -0,0 +1 @@ +// I will not replace the app.tsx code, I will just write a small test component to print to console diff --git a/samples/react-place-autocomplete-map/tsconfig.json b/samples/react-place-autocomplete-map/tsconfig.json new file mode 100644 index 000000000..b7054a4ed --- /dev/null +++ b/samples/react-place-autocomplete-map/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.react-base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts*"] +} diff --git a/samples/react-web-components-markers/README.md b/samples/react-web-components-markers/README.md new file mode 100644 index 000000000..73f6380c5 --- /dev/null +++ b/samples/react-web-components-markers/README.md @@ -0,0 +1,41 @@ +# Google Maps JavaScript Sample + +## react-web-components-markers + +Add a meaningful description for react-web-components-markers here... + +## Setup + +### Before starting run: + +`npm i` + +### Run an example on a local web server + +`cd samples/react-web-components-markers` +`npm start` + +### Build an individual example + +`cd samples/react-web-components-markers` +`npm run build` + +From 'samples': + +`npm run build --workspace=react-web-components-markers/` + +### Build all of the examples. + +From 'samples': + +`npm run build-all` + +### Run lint to check for problems + +`cd samples/react-web-components-markers` +`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). diff --git a/samples/react-web-components-markers/index.html b/samples/react-web-components-markers/index.html new file mode 100644 index 000000000..4143a21bc --- /dev/null +++ b/samples/react-web-components-markers/index.html @@ -0,0 +1,38 @@ + + + + + + + + React - Web Components Markers + + + + + +
+ + + diff --git a/samples/react-web-components-markers/package.json b/samples/react-web-components-markers/package.json new file mode 100644 index 000000000..0dcaf438d --- /dev/null +++ b/samples/react-web-components-markers/package.json @@ -0,0 +1,13 @@ +{ + "name": "@js-api-samples/react-web-components-markers", + "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" +} diff --git a/samples/react-web-components-markers/src/app.tsx b/samples/react-web-components-markers/src/app.tsx new file mode 100644 index 000000000..a302ed210 --- /dev/null +++ b/samples/react-web-components-markers/src/app.tsx @@ -0,0 +1,39 @@ +/** + * @license + * Copyright 2026 Google LLC. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// [START maps_react_web_components_markers_app] +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { APIProvider, Map, AdvancedMarker } from '@vis.gl/react-google-maps'; + +const API_KEY = 'GOOGLE_MAPS_API_KEY'; + +export default function App() { + return ( + + + + + + ); +} + +export function renderToDom(container: HTMLElement) { + const root = createRoot(container); + root.render( + + + + ); +} +// [END maps_react_web_components_markers_app] diff --git a/samples/react-web-components-markers/tsconfig.json b/samples/react-web-components-markers/tsconfig.json new file mode 100644 index 000000000..b7054a4ed --- /dev/null +++ b/samples/react-web-components-markers/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.react-base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts*"] +} diff --git a/samples/rgm-autocomplete/README.md b/samples/rgm-autocomplete/README.md new file mode 100644 index 000000000..0fd5209af --- /dev/null +++ b/samples/rgm-autocomplete/README.md @@ -0,0 +1,41 @@ +# Google Maps JavaScript Sample + +## rgm-autocomplete + +React Google Maps Library - Place Autocomplete + +## 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). diff --git a/samples/rgm-autocomplete/index.html b/samples/rgm-autocomplete/index.html new file mode 100644 index 000000000..577474987 --- /dev/null +++ b/samples/rgm-autocomplete/index.html @@ -0,0 +1,35 @@ + + + + + + + + React - react place autocomplete map + + + + +
+ + + diff --git a/samples/rgm-autocomplete/package.json b/samples/rgm-autocomplete/package.json new file mode 100644 index 000000000..e79e02971 --- /dev/null +++ b/samples/rgm-autocomplete/package.json @@ -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" +} diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx new file mode 100644 index 000000000..7512d55f8 --- /dev/null +++ b/samples/rgm-autocomplete/src/app.tsx @@ -0,0 +1,121 @@ +/* + * Copyright 2024 Google LLC. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// [START maps_rgm_autocomplete] +import React, { useState, useEffect, useRef } from 'react'; +import { createRoot } from 'react-dom/client'; +import { + APIProvider, + ControlPosition, + MapControl, + AdvancedMarker, + Map, + useMap, + useMapsLibrary, + useAdvancedMarkerRef, + AdvancedMarkerRef +} from '@vis.gl/react-google-maps'; + +const API_KEY = + globalThis.GOOGLE_MAPS_API_KEY ?? ("YOUR_API_KEY"); + +const App = () => { + const [selectedPlace, setSelectedPlace] = + useState(null); + const [markerRef, marker] = useAdvancedMarkerRef(); + + return ( + + + + + +
+ +
+
+ +
+ ); +}; + +interface MapHandlerProps { + place: google.maps.places.PlaceResult | null; + marker: google.maps.marker.AdvancedMarkerElement | null; +} + +const MapHandler = ({ place, marker }: MapHandlerProps) => { + const map = useMap(); + + useEffect(() => { + if (!map || !place || !marker) return; + + if (place.geometry?.viewport) { + map.fitBounds(place.geometry?.viewport); + } + marker.position = place.geometry?.location; + }, [map, place, marker]); + + return null; +}; + +interface PlaceAutocompleteProps { + onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; +} + +const PlaceAutocomplete = ({ onPlaceSelect }: PlaceAutocompleteProps) => { + const [placeAutocomplete, setPlaceAutocomplete] = + useState(null); + const inputRef = useRef(null); + const places = useMapsLibrary('places'); + + useEffect(() => { + if (!places || !inputRef.current) return; + + const options = { + fields: ['geometry', 'name', 'formatted_address'] + }; + + setPlaceAutocomplete(new places.Autocomplete(inputRef.current, options)); + }, [places]); + + useEffect(() => { + if (!placeAutocomplete) return; + + placeAutocomplete.addListener('place_changed', () => { + onPlaceSelect(placeAutocomplete.getPlace()); + }); + }, [onPlaceSelect, placeAutocomplete]); + + return ( +
+ +
+ ); +}; + +const root = createRoot(document.getElementById('app')!); +root.render(); + +export default App; +// [END maps_rgm_autocomplete] diff --git a/samples/rgm-autocomplete/style.css b/samples/rgm-autocomplete/style.css new file mode 100644 index 000000000..62c35098f --- /dev/null +++ b/samples/rgm-autocomplete/style.css @@ -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] */ \ No newline at end of file diff --git a/samples/rgm-autocomplete/tsconfig.json b/samples/rgm-autocomplete/tsconfig.json new file mode 100644 index 000000000..b7054a4ed --- /dev/null +++ b/samples/rgm-autocomplete/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.react-base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["./src/**/*.ts*"] +} From 4303db8cccf23f2c04939ca5cdb6d593906773d5 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 10:43:15 -0700 Subject: [PATCH 02/16] feat: migrate rgm-autocomplete to use updated React practices Change-Id: I0ef57901fd67ae7152217516f956e4b59b9207e3 --- samples/rgm-autocomplete/src/app.tsx | 233 ++++++++++++++++----------- 1 file changed, 141 insertions(+), 92 deletions(-) diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index 7512d55f8..c4709b169 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -17,105 +17,154 @@ import React, { useState, useEffect, useRef } from 'react'; import { createRoot } from 'react-dom/client'; import { - APIProvider, - ControlPosition, - MapControl, - AdvancedMarker, - Map, - useMap, - useMapsLibrary, - useAdvancedMarkerRef, - AdvancedMarkerRef + APIProvider, + Map, + MapControl, + ControlPosition, + AdvancedMarker, + InfoWindow, + useMap, + useMapsLibrary, + useAdvancedMarkerRef, } from '@vis.gl/react-google-maps'; -const API_KEY = - globalThis.GOOGLE_MAPS_API_KEY ?? ("YOUR_API_KEY"); - -const App = () => { - const [selectedPlace, setSelectedPlace] = - useState(null); - const [markerRef, marker] = useAdvancedMarkerRef(); - - return ( - - - - - -
- -
-
- -
- ); -}; - -interface MapHandlerProps { - place: google.maps.places.PlaceResult | null; - marker: google.maps.marker.AdvancedMarkerElement | null; -} - -const MapHandler = ({ place, marker }: MapHandlerProps) => { - const map = useMap(); +const API_KEY = 'GOOGLE_MAPS_API_KEY'; - useEffect(() => { - if (!map || !place || !marker) return; - - if (place.geometry?.viewport) { - map.fitBounds(place.geometry?.viewport); +declare global { + namespace JSX { + interface IntrinsicElements { + 'gmp-place-autocomplete': React.DetailedHTMLProps< + React.HTMLAttributes, + HTMLElement + >; + } } - marker.position = place.geometry?.location; - }, [map, place, marker]); - - return null; -}; - -interface PlaceAutocompleteProps { - onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; } -const PlaceAutocomplete = ({ onPlaceSelect }: PlaceAutocompleteProps) => { - const [placeAutocomplete, setPlaceAutocomplete] = - useState(null); - const inputRef = useRef(null); - const places = useMapsLibrary('places'); - - useEffect(() => { - if (!places || !inputRef.current) return; - - const options = { - fields: ['geometry', 'name', 'formatted_address'] - }; - - setPlaceAutocomplete(new places.Autocomplete(inputRef.current, options)); - }, [places]); - - useEffect(() => { - if (!placeAutocomplete) return; - - placeAutocomplete.addListener('place_changed', () => { - onPlaceSelect(placeAutocomplete.getPlace()); - }); - }, [onPlaceSelect, placeAutocomplete]); - - return ( -
- -
- ); +const PlaceAutocomplete = ({ + onPlaceSelect, +}: { + onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; +}) => { + const map = useMap(); + const placesLibrary = useMapsLibrary('places'); + const inputRef = useRef(null); + + useEffect(() => { + if (!map || !placesLibrary || !inputRef.current) return; + + const options = { + fields: ['geometry', 'name', 'formatted_address'], + strictBounds: true, // This enforces the same strict bounds constraint as locationRestriction + }; + + const autocomplete = new placesLibrary.Autocomplete(inputRef.current, options); + + // The traditional API has a built-in bindTo method that flawlessly syncs to the map's internal bounds state + autocomplete.bindTo('bounds', map); + + const listener = autocomplete.addListener('place_changed', () => { + const place = autocomplete.getPlace(); + + if (!place.geometry || !place.geometry.location) { + onPlaceSelect(null); + return; + } + + if (place.geometry.viewport) { + map.fitBounds(place.geometry.viewport); + } else { + map.setCenter(place.geometry.location); + map.setZoom(17); + } + + onPlaceSelect(place); + }); + + return () => { + google.maps.event.removeListener(listener); + }; + }, [map, placesLibrary, onPlaceSelect]); + + return ( +
+ +
+ ); }; -const root = createRoot(document.getElementById('app')!); -root.render(); +export default function App() { + const [selectedPlace, setSelectedPlace] = + useState(null); + const [markerRef, marker] = useAdvancedMarkerRef(); + + return ( + + + + + + + {selectedPlace?.geometry?.location && ( + + )} + + {selectedPlace?.geometry?.location && marker && ( + +
+ + {selectedPlace.name ?? 'No name'} + +
+ + {selectedPlace.formatted_address ?? 'No address'} + +
+
+ )} +
+
+ ); +} -export default App; +export function renderToDom(container: HTMLElement) { + const root = createRoot(container); + root.render( + + + + ); +} // [END maps_rgm_autocomplete] From 44fea5bac9e1789dad7c8f6ef80e2b57f237970c Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 10:57:11 -0700 Subject: [PATCH 03/16] refactor: hydrate traditional Autocomplete to modern Place class Change-Id: I0cce1dda39c055dfbac6ccce8958f4f4d226562f --- samples/rgm-autocomplete/src/app.tsx | 69 ++++++++++++++-------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index c4709b169..96ec1bf8b 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -1,17 +1,7 @@ -/* - * Copyright 2024 Google LLC. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. +/** + * @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'; @@ -44,7 +34,7 @@ declare global { const PlaceAutocomplete = ({ onPlaceSelect, }: { - onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; + onPlaceSelect: (place: google.maps.places.Place | null) => void; }) => { const map = useMap(); const placesLibrary = useMapsLibrary('places'); @@ -54,8 +44,9 @@ const PlaceAutocomplete = ({ if (!map || !placesLibrary || !inputRef.current) return; const options = { - fields: ['geometry', 'name', 'formatted_address'], - strictBounds: true, // This enforces the same strict bounds constraint as locationRestriction + // Only request the ID, as we will use it to construct the modern Place object + fields: ['place_id'], + strictBounds: true, }; const autocomplete = new placesLibrary.Autocomplete(inputRef.current, options); @@ -63,19 +54,29 @@ const PlaceAutocomplete = ({ // The traditional API has a built-in bindTo method that flawlessly syncs to the map's internal bounds state autocomplete.bindTo('bounds', map); - const listener = autocomplete.addListener('place_changed', () => { - const place = autocomplete.getPlace(); + const listener = autocomplete.addListener('place_changed', async () => { + const placeResult = autocomplete.getPlace(); - if (!place.geometry || !place.geometry.location) { + if (!placeResult.place_id) { onPlaceSelect(null); return; } - if (place.geometry.viewport) { - map.fitBounds(place.geometry.viewport); - } else { - map.setCenter(place.geometry.location); - map.setZoom(17); + // Construct the modern Place object using the ID + const place = new placesLibrary.Place({ + id: placeResult.place_id, + }); + + // Fetch the modern fields + await place.fetchFields({ + fields: ['location', 'viewport', 'displayName', 'formattedAddress'], + }); + + if (place.viewport) { + map.fitBounds(place.viewport); + } else if (place.location) { + map.setCenter(place.location); + map.setZoom(13); } onPlaceSelect(place); @@ -119,37 +120,37 @@ const PlaceAutocomplete = ({ export default function App() { const [selectedPlace, setSelectedPlace] = - useState(null); + useState(null); const [markerRef, marker] = useAdvancedMarkerRef(); return ( - {selectedPlace?.geometry?.location && ( + {selectedPlace?.location && ( )} - {selectedPlace?.geometry?.location && marker && ( + {selectedPlace?.location && marker && (
- {selectedPlace.name ?? 'No name'} + {selectedPlace.displayName ?? 'No name'}
- {selectedPlace.formatted_address ?? 'No address'} + {selectedPlace.formattedAddress ?? 'No address'}
From 13509167e0c95d013c02affca44e12035a42cdcb Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 10:59:51 -0700 Subject: [PATCH 04/16] chore: remove experimental react samples from migration branch Change-Id: Ifff9d52a47f7b405a4c2b645c2eb600a47b2354c --- samples/react-circle-simple/README.md | 41 ---- samples/react-circle-simple/index.html | 53 ------ samples/react-circle-simple/package.json | 13 -- samples/react-circle-simple/src/app.tsx | 179 ------------------ samples/react-circle-simple/tsconfig.json | 8 - .../react-place-autocomplete-map/index.html | 35 ---- .../react-place-autocomplete-map/package.json | 13 -- .../react-place-autocomplete-map/src/app.tsx | 152 --------------- .../test-script.tsx | 1 - .../tsconfig.json | 8 - 10 files changed, 503 deletions(-) delete mode 100644 samples/react-circle-simple/README.md delete mode 100644 samples/react-circle-simple/index.html delete mode 100644 samples/react-circle-simple/package.json delete mode 100644 samples/react-circle-simple/src/app.tsx delete mode 100644 samples/react-circle-simple/tsconfig.json delete mode 100644 samples/react-place-autocomplete-map/index.html delete mode 100644 samples/react-place-autocomplete-map/package.json delete mode 100644 samples/react-place-autocomplete-map/src/app.tsx delete mode 100644 samples/react-place-autocomplete-map/test-script.tsx delete mode 100644 samples/react-place-autocomplete-map/tsconfig.json diff --git a/samples/react-circle-simple/README.md b/samples/react-circle-simple/README.md deleted file mode 100644 index e68d1b300..000000000 --- a/samples/react-circle-simple/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Google Maps JavaScript Sample - -## react-circle-simple - -Add a meaningful description for react-circle-simple here... - -## Setup - -### Before starting run: - -`npm i` - -### Run an example on a local web server - -`cd samples/react-circle-simple` -`npm start` - -### Build an individual example - -`cd samples/react-circle-simple` -`npm run build` - -From 'samples': - -`npm run build --workspace=react-circle-simple/` - -### Build all of the examples. - -From 'samples': - -`npm run build-all` - -### Run lint to check for problems - -`cd samples/react-circle-simple` -`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). diff --git a/samples/react-circle-simple/index.html b/samples/react-circle-simple/index.html deleted file mode 100644 index f84b7c32d..000000000 --- a/samples/react-circle-simple/index.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - React - Simple Circle - - - - - -
- - - diff --git a/samples/react-circle-simple/package.json b/samples/react-circle-simple/package.json deleted file mode 100644 index 571c2c4c1..000000000 --- a/samples/react-circle-simple/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@js-api-samples/react-circle-simple", - "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" -} diff --git a/samples/react-circle-simple/src/app.tsx b/samples/react-circle-simple/src/app.tsx deleted file mode 100644 index 10a5d76ba..000000000 --- a/samples/react-circle-simple/src/app.tsx +++ /dev/null @@ -1,179 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// [START maps_react_circle_simple_app] -import React, { useEffect, useState, useRef } from 'react'; -import { createRoot } from 'react-dom/client'; -import { APIProvider, Map, AdvancedMarker, useMap, MapMouseEvent } from '@vis.gl/react-google-maps'; - -const API_KEY = 'GOOGLE_MAPS_API_KEY'; - -// ----------------------------------------------------------------------------- -// The @vis.gl/react-google-maps library does not provide a native component. -// Users are expected to build their own wrapper component using the useMap() hook -// and the imperative google.maps.Circle class. -// ----------------------------------------------------------------------------- -type CircleProps = google.maps.CircleOptions & { - onCenterChanged?: (center: google.maps.LatLngLiteral) => void; -}; - -const Circle = ({ onCenterChanged, ...options }: CircleProps) => { - const map = useMap(); - const circleRef = useRef(null); - - useEffect(() => { - if (!map) return; - - // Instantiate the circle only once when the map is ready - circleRef.current = new google.maps.Circle({ - map, - ...options - }); - - return () => { - if (circleRef.current) { - circleRef.current.setMap(null); - } - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [map]); - - // Update the circle imperatively when React props (options) change. - // We explicitly extract radius and center to avoid passing a new options object - // reference on every render, which causes infinite loops! - const { radius, center } = options; - useEffect(() => { - if (circleRef.current) { - circleRef.current.setOptions({ radius, center }); - } - }, [radius, center]); - - // Handle events - useEffect(() => { - if (!circleRef.current || !onCenterChanged) return; - - const listener = circleRef.current.addListener('center_changed', () => { - const newCenter = circleRef.current?.getCenter(); - if (newCenter) { - const lat = newCenter.lat(); - const lng = newCenter.lng(); - - // CRITICAL FIX: The Maps API has floating point precision loss when it - // projects coordinates. Calling setOptions({center}) slightly changes the float, - // which fires center_changed again, resulting in an infinite loop! - // We only update React state if the drag distance was meaningful. - if ( - !center || - Math.abs(lat - (center.lat as number)) > 0.000001 || - Math.abs(lng - (center.lng as number)) > 0.000001 - ) { - onCenterChanged({ lat, lng }); - } - } - }); - - return () => { - google.maps.event.removeListener(listener); - }; - }, [onCenterChanged, center]); - - return null; // A circle has no DOM representation, so we return null -}; - -// ----------------------------------------------------------------------------- -// Component to handle map clicks and imperatively pan the map -// ----------------------------------------------------------------------------- -const MapClickHandler = ({ onMapClick }: { onMapClick: (latLng: google.maps.LatLngLiteral) => void }) => { - const map = useMap(); - - useEffect(() => { - if (!map) return; - const listener = map.addListener('click', (e: google.maps.MapMouseEvent) => { - if (e.latLng) { - const center = { lat: e.latLng.lat(), lng: e.latLng.lng() }; - onMapClick(center); - map.panTo(center); // Imperatively pan the map - } - }); - - return () => google.maps.event.removeListener(listener); - }, [map, onMapClick]); - - return null; -}; - -// ----------------------------------------------------------------------------- -// Main Application -// ----------------------------------------------------------------------------- -export default function App() { - const initialCenter = { lat: 34.98956821576194, lng: 135.74239981260283 }; - const [circleCenter, setCircleCenter] = useState(initialCenter); - const [radius, setRadius] = useState(400); - - return ( - -
- Radius - - - -
- - - {/* Handles clicks and pans the map imperatively */} - - - - - -
- - - -
-
-
-
- ); -} - -export function renderToDom(container: HTMLElement) { - const root = createRoot(container); - root.render( - - - - ); -} -// [END maps_react_circle_simple_app] diff --git a/samples/react-circle-simple/tsconfig.json b/samples/react-circle-simple/tsconfig.json deleted file mode 100644 index b7054a4ed..000000000 --- a/samples/react-circle-simple/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.react-base.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["./src/**/*.ts*"] -} diff --git a/samples/react-place-autocomplete-map/index.html b/samples/react-place-autocomplete-map/index.html deleted file mode 100644 index 577474987..000000000 --- a/samples/react-place-autocomplete-map/index.html +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - React - react place autocomplete map - - - - -
- - - diff --git a/samples/react-place-autocomplete-map/package.json b/samples/react-place-autocomplete-map/package.json deleted file mode 100644 index ebaee0be1..000000000 --- a/samples/react-place-autocomplete-map/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@js-api-samples/react-place-autocomplete-map", - "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" -} diff --git a/samples/react-place-autocomplete-map/src/app.tsx b/samples/react-place-autocomplete-map/src/app.tsx deleted file mode 100644 index 424a37dc5..000000000 --- a/samples/react-place-autocomplete-map/src/app.tsx +++ /dev/null @@ -1,152 +0,0 @@ -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'; - -declare global { - namespace JSX { - interface IntrinsicElements { - 'gmp-place-autocomplete': React.DetailedHTMLProps< - React.HTMLAttributes, - HTMLElement - >; - } - } -} - -const PlaceAutocomplete = ({ - onPlaceSelect, -}: { - onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; -}) => { - const map = useMap(); - const placesLibrary = useMapsLibrary('places'); - const inputRef = useRef(null); - - useEffect(() => { - if (!map || !placesLibrary || !inputRef.current) return; - - const options = { - fields: ['geometry', 'name', 'formatted_address'], - strictBounds: true, // This enforces the same strict bounds constraint as locationRestriction - }; - - const autocomplete = new placesLibrary.Autocomplete(inputRef.current, options); - - // The traditional API has a built-in bindTo method that flawlessly syncs to the map's internal bounds state - autocomplete.bindTo('bounds', map); - - const listener = autocomplete.addListener('place_changed', () => { - const place = autocomplete.getPlace(); - - if (!place.geometry || !place.geometry.location) { - onPlaceSelect(null); - return; - } - - if (place.geometry.viewport) { - map.fitBounds(place.geometry.viewport); - } else { - map.setCenter(place.geometry.location); - map.setZoom(17); - } - - onPlaceSelect(place); - }); - - return () => { - google.maps.event.removeListener(listener); - }; - }, [map, placesLibrary, onPlaceSelect]); - - return ( -
- -
- ); -}; - -export default function App() { - const [selectedPlace, setSelectedPlace] = - useState(null); - const [markerRef, marker] = useAdvancedMarkerRef(); - - return ( - - - - - - - {selectedPlace?.geometry?.location && ( - - )} - - {selectedPlace?.geometry?.location && marker && ( - -
- - {selectedPlace.name ?? 'No name'} - -
- - {selectedPlace.formatted_address ?? 'No address'} - -
-
- )} -
-
- ); -} - -export function renderToDom(container: HTMLElement) { - const root = createRoot(container); - root.render( - - - - ); -} diff --git a/samples/react-place-autocomplete-map/test-script.tsx b/samples/react-place-autocomplete-map/test-script.tsx deleted file mode 100644 index f0fb474fa..000000000 --- a/samples/react-place-autocomplete-map/test-script.tsx +++ /dev/null @@ -1 +0,0 @@ -// I will not replace the app.tsx code, I will just write a small test component to print to console diff --git a/samples/react-place-autocomplete-map/tsconfig.json b/samples/react-place-autocomplete-map/tsconfig.json deleted file mode 100644 index b7054a4ed..000000000 --- a/samples/react-place-autocomplete-map/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.react-base.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["./src/**/*.ts*"] -} From 0ff24e44a321e3d504a85d90146672f7f42358cf Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 11:04:14 -0700 Subject: [PATCH 05/16] docs: add best practices writeup to README Change-Id: Ifb8641ba611c95d5d3232e123b3431e56ff54cdd --- samples/rgm-autocomplete/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/samples/rgm-autocomplete/README.md b/samples/rgm-autocomplete/README.md index 0fd5209af..fb662c504 100644 --- a/samples/rgm-autocomplete/README.md +++ b/samples/rgm-autocomplete/README.md @@ -39,3 +39,18 @@ From 'samples': For feedback related to this sample, please open a new issue on [GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). + +## Best Practices for Place Autocomplete in React + +When integrating Google Maps Place Autocomplete within a React application, this sample implements two key best practices: + +### 1. Flawless Bounds Synchronization (Location Restriction) +When placing an autocomplete widget inside a floating React Portal or outside the main DOM tree of the map, newer Web Components (like ``) may lose context of the map's viewport. +To guarantee that search predictions are strictly biased or restricted to the map's current bounds, this sample uses the rock-solid traditional imperative class: `new placesLibrary.Autocomplete()`. +By calling `autocomplete.bindTo('bounds', map);`, the autocomplete widget perfectly syncs with the map's internal bounds state, ensuring users always see search predictions relevant to their current map view rather than their IP address. + +### 2. Hydrating to the Modern `Place` Class +While the traditional Autocomplete class returns a legacy `PlaceResult`, this sample demonstrates how to seamlessly upgrade it to the modern `google.maps.places.Place` class without paying for duplicate API requests: +* First, configure the Autocomplete widget to *only* request the `place_id` (`fields: ['place_id']`). This is included in the autocomplete session and is extremely fast. +* Second, intercept the `place_changed` event, grab the `place_id`, and use it to instantiate a modern `Place` class (`new placesLibrary.Place({ id })`). +* Finally, call `await place.fetchFields(...)` to hydrate the object with its geometry and details before passing it up to your React state. From c8ce26f5205932baddfa46b0bce60a97e2831297 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 11:17:59 -0700 Subject: [PATCH 06/16] refactor: use modern PlaceAutocompleteElement and update best practices docs Change-Id: Id19072e7159ffef4df6cd9a5a8026a3ed1af8ae0 --- samples/rgm-autocomplete/README.md | 18 ++---- samples/rgm-autocomplete/src/app.tsx | 88 ++++++++++++++-------------- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/samples/rgm-autocomplete/README.md b/samples/rgm-autocomplete/README.md index fb662c504..5e7755889 100644 --- a/samples/rgm-autocomplete/README.md +++ b/samples/rgm-autocomplete/README.md @@ -42,15 +42,9 @@ For feedback related to this sample, please open a new issue on ## Best Practices for Place Autocomplete in React -When integrating Google Maps Place Autocomplete within a React application, this sample implements two key best practices: - -### 1. Flawless Bounds Synchronization (Location Restriction) -When placing an autocomplete widget inside a floating React Portal or outside the main DOM tree of the map, newer Web Components (like ``) may lose context of the map's viewport. -To guarantee that search predictions are strictly biased or restricted to the map's current bounds, this sample uses the rock-solid traditional imperative class: `new placesLibrary.Autocomplete()`. -By calling `autocomplete.bindTo('bounds', map);`, the autocomplete widget perfectly syncs with the map's internal bounds state, ensuring users always see search predictions relevant to their current map view rather than their IP address. - -### 2. Hydrating to the Modern `Place` Class -While the traditional Autocomplete class returns a legacy `PlaceResult`, this sample demonstrates how to seamlessly upgrade it to the modern `google.maps.places.Place` class without paying for duplicate API requests: -* First, configure the Autocomplete widget to *only* request the `place_id` (`fields: ['place_id']`). This is included in the autocomplete session and is extremely fast. -* Second, intercept the `place_changed` event, grab the `place_id`, and use it to instantiate a modern `Place` class (`new placesLibrary.Place({ id })`). -* Finally, call `await place.fetchFields(...)` to hydrate the object with its geometry and details before passing it up to your React state. +When integrating Google Maps Place Autocomplete within a React application, this sample implements a key best practice for maintaining strict location bias: + +### Flawless Bounds Synchronization (Location Restriction) +When placing the modern `` Web Component inside a floating React Portal or 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 programmatically mounts the `PlaceAutocompleteElement` and manually syncs the map's bounds. +By listening to the map's `bounds_changed` event and assigning `map.getBounds().toJSON()` to the element's `locationRestriction` property, we safely bypass any cross-context issues across the React boundary. This ensures users always see search predictions strictly relevant to their current map view rather than their IP address. diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index 96ec1bf8b..7297e5e17 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -38,52 +38,63 @@ const PlaceAutocomplete = ({ }) => { const map = useMap(); const placesLibrary = useMapsLibrary('places'); - const inputRef = useRef(null); + const containerRef = useRef(null); useEffect(() => { - if (!map || !placesLibrary || !inputRef.current) return; - - const options = { - // Only request the ID, as we will use it to construct the modern Place object - fields: ['place_id'], - strictBounds: true, + 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(); + } }; - const autocomplete = new placesLibrary.Autocomplete(inputRef.current, options); - - // The traditional API has a built-in bindTo method that flawlessly syncs to the map's internal bounds state - autocomplete.bindTo('bounds', map); + // Sync initially and whenever the map moves + syncBounds(); + const boundsListener = map.addListener('bounds_changed', syncBounds); - const listener = autocomplete.addListener('place_changed', async () => { - const placeResult = autocomplete.getPlace(); + // 3. Listen for the gmp-placeselect event (the modern equivalent of place_changed) + const placeSelectListener = () => { + const place = autocomplete.place; - if (!placeResult.place_id) { + if (!place) { onPlaceSelect(null); return; } - // Construct the modern Place object using the ID - const place = new placesLibrary.Place({ - id: placeResult.place_id, - }); - - // Fetch the modern fields - await place.fetchFields({ + // The modern Web Component automatically fetches fields if requested, but we can also + // manually ensure we have the geometry before moving the map + 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); }); + }; - if (place.viewport) { - map.fitBounds(place.viewport); - } else if (place.location) { - map.setCenter(place.location); - map.setZoom(13); - } - - onPlaceSelect(place); - }); + autocomplete.addEventListener('gmp-placeselect', placeSelectListener); return () => { - google.maps.event.removeListener(listener); + google.maps.event.removeListener(boundsListener); + autocomplete.removeEventListener('gmp-placeselect', placeSelectListener); + + // Clean up the DOM element when unmounting + if (containerRef.current) { + containerRef.current.innerHTML = ''; + } }; }, [map, placesLibrary, onPlaceSelect]); @@ -100,20 +111,7 @@ const PlaceAutocomplete = ({ fontSize: 'small', width: '300px', }}> - +
); }; From cc1c8ba80eb59ca714f9528d71b07d2754917457 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 11:22:25 -0700 Subject: [PATCH 07/16] fix: Fixes TS error Change-Id: Ie115a8493247cde3fc3231221876f3b176e8c859 --- samples/rgm-autocomplete/src/app.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index 7297e5e17..c9e4314d9 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -61,17 +61,16 @@ const PlaceAutocomplete = ({ syncBounds(); const boundsListener = map.addListener('bounds_changed', syncBounds); - // 3. Listen for the gmp-placeselect event (the modern equivalent of place_changed) - const placeSelectListener = () => { - const place = autocomplete.place; - - if (!place) { + // 3. Listen for the gmp-select event + const placeSelectListener = (event: any) => { + const placePrediction = event.placePrediction; + if (!placePrediction) { onPlaceSelect(null); return; } - // The modern Web Component automatically fetches fields if requested, but we can also - // manually ensure we have the geometry before moving the map + const place = placePrediction.toPlace(); + place.fetchFields({ fields: ['location', 'viewport', 'displayName', 'formattedAddress'], }).then(() => { @@ -85,11 +84,11 @@ const PlaceAutocomplete = ({ }); }; - autocomplete.addEventListener('gmp-placeselect', placeSelectListener); + autocomplete.addEventListener('gmp-select', placeSelectListener); return () => { google.maps.event.removeListener(boundsListener); - autocomplete.removeEventListener('gmp-placeselect', placeSelectListener); + autocomplete.removeEventListener('gmp-select', placeSelectListener); // Clean up the DOM element when unmounting if (containerRef.current) { From e9a211f609bc43aaaa0168af49d77a7a9ba53792 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 11:32:08 -0700 Subject: [PATCH 08/16] docs: Updates readme and code comments. Change-Id: Ic29906ff21ab6322d4577c87c66e7ff18e1fddcf --- samples/rgm-autocomplete/README.md | 23 +++++++++++++++++------ samples/rgm-autocomplete/src/app.tsx | 6 +++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/samples/rgm-autocomplete/README.md b/samples/rgm-autocomplete/README.md index 5e7755889..8f165bde9 100644 --- a/samples/rgm-autocomplete/README.md +++ b/samples/rgm-autocomplete/README.md @@ -40,11 +40,22 @@ From 'samples': For feedback related to this sample, please open a new issue on [GitHub](https://github.com/googlemaps-samples/js-api-samples/issues). -## Best Practices for Place Autocomplete in React +## Integrating Place Autocomplete & The New Place Class -When integrating Google Maps Place Autocomplete within a React application, this sample implements a key best practice for maintaining strict location bias: +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. -### Flawless Bounds Synchronization (Location Restriction) -When placing the modern `` Web Component inside a floating React Portal or 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 programmatically mounts the `PlaceAutocompleteElement` and manually syncs the map's bounds. -By listening to the map's `bounds_changed` event and assigning `map.getBounds().toJSON()` to the element's `locationRestriction` property, we safely bypass any cross-context issues across the React boundary. This ensures users always see search predictions strictly relevant to their current map view rather than their IP address. +### 1. Programmatic Instantiation +Instead of rendering the `` 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. \ No newline at end of file diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index c9e4314d9..ae469a1a7 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -57,11 +57,11 @@ const PlaceAutocomplete = ({ } }; - // Sync initially and whenever the map moves + // Sync initially and whenever the map moves. syncBounds(); const boundsListener = map.addListener('bounds_changed', syncBounds); - // 3. Listen for the gmp-select event + // 3. Listen for the gmp-select event. const placeSelectListener = (event: any) => { const placePrediction = event.placePrediction; if (!placePrediction) { @@ -90,7 +90,7 @@ const PlaceAutocomplete = ({ google.maps.event.removeListener(boundsListener); autocomplete.removeEventListener('gmp-select', placeSelectListener); - // Clean up the DOM element when unmounting + // Clean up the DOM element when unmounting. if (containerRef.current) { containerRef.current.innerHTML = ''; } From 9ae491ea0b2f290940b6023068de72795fd33430 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 11:36:09 -0700 Subject: [PATCH 09/16] Formats files using Prettier. Change-Id: I081ccefcc4ba6e960216ebf4f70734052f92aea6 --- samples/rgm-autocomplete/README.md | 23 +++++++++++----- samples/rgm-autocomplete/src/app.tsx | 31 ++++++++++++--------- samples/rgm-autocomplete/style.css | 40 ++++++++++++++-------------- 3 files changed, 55 insertions(+), 39 deletions(-) diff --git a/samples/rgm-autocomplete/README.md b/samples/rgm-autocomplete/README.md index 8f165bde9..14648200c 100644 --- a/samples/rgm-autocomplete/README.md +++ b/samples/rgm-autocomplete/README.md @@ -4,6 +4,8 @@ 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: @@ -45,17 +47,24 @@ For feedback related to this sample, please open a new issue on 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 `` 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. + +Instead of rendering the `` 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. + +- **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. + +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. \ No newline at end of file + +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. diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index ae469a1a7..fdb5ddedf 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -71,17 +71,24 @@ const PlaceAutocomplete = ({ const place = placePrediction.toPlace(); - 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); - }); + 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); + }); }; autocomplete.addEventListener('gmp-select', placeSelectListener); @@ -89,7 +96,7 @@ const PlaceAutocomplete = ({ return () => { google.maps.event.removeListener(boundsListener); autocomplete.removeEventListener('gmp-select', placeSelectListener); - + // Clean up the DOM element when unmounting. if (containerRef.current) { containerRef.current.innerHTML = ''; diff --git a/samples/rgm-autocomplete/style.css b/samples/rgm-autocomplete/style.css index 62c35098f..cbf89fd6d 100644 --- a/samples/rgm-autocomplete/style.css +++ b/samples/rgm-autocomplete/style.css @@ -5,50 +5,50 @@ */ /* [START maps_rgm_autocomplete] */ body { - margin: 0; - font-family: sans-serif; + margin: 0; + font-family: sans-serif; } #app { - width: 100vw; - height: 100vh; + width: 100vw; + height: 100vh; } .autocomplete-container input, .autocomplete-control { - box-sizing: border-box; + box-sizing: border-box; } .autocomplete-control { - margin: 24px; - background: #fff; + margin: 24px; + background: #fff; } .autocomplete-container { - width: 300px; + width: 300px; } .autocomplete-container input { - width: 100%; - height: 40px; - padding: 0 12px; - font-size: 18px; + width: 100%; + height: 40px; + padding: 0 12px; + font-size: 18px; } .autocomplete-container .custom-list { - width: 100%; - list-style: none; - padding: 0; - margin: 0; + width: 100%; + list-style: none; + padding: 0; + margin: 0; } .autocomplete-container .custom-list-item { - padding: 8px; + padding: 8px; } .autocomplete-container .custom-list-item:hover { - background: lightgrey; - cursor: pointer; + background: lightgrey; + cursor: pointer; } -/* [END maps_rgm_autocomplete] */ \ No newline at end of file +/* [END maps_rgm_autocomplete] */ From 4af2fa2e28f85722e1e60da9f0603a77063fb1dd Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 11:59:47 -0700 Subject: [PATCH 10/16] fix: Resolve TypeScript any types for event listener Change-Id: I76e65cbd7110c633d1e623ab54b4f28e95cf2f7a --- samples/rgm-autocomplete/src/app.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index fdb5ddedf..57a89fc61 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -62,7 +62,7 @@ const PlaceAutocomplete = ({ const boundsListener = map.addListener('bounds_changed', syncBounds); // 3. Listen for the gmp-select event. - const placeSelectListener = (event: any) => { + const placeSelectListener = (event: google.maps.places.PlacePredictionSelectEvent) => { const placePrediction = event.placePrediction; if (!placePrediction) { onPlaceSelect(null); From e811e5861a128384605a7a67d5e2ee65e7730f3b Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 12:05:15 -0700 Subject: [PATCH 11/16] fix: Resolve TypeScript error on removeEventListener Change-Id: Icf6ae6e266fd7d5939ef7975e6f4a148ef0159b1 --- samples/rgm-autocomplete/src/app.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index 57a89fc61..36ac16629 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -62,7 +62,8 @@ const PlaceAutocomplete = ({ const boundsListener = map.addListener('bounds_changed', syncBounds); // 3. Listen for the gmp-select event. - const placeSelectListener = (event: google.maps.places.PlacePredictionSelectEvent) => { + const placeSelectListener = (e: Event) => { + const event = e as google.maps.places.PlacePredictionSelectEvent; const placePrediction = event.placePrediction; if (!placePrediction) { onPlaceSelect(null); From 4287b6ba0b2420744541135da9c4cc85836a6a68 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 12:10:13 -0700 Subject: [PATCH 12/16] fix: Resolve ESLint warning and floating promise Change-Id: I8a86b395397d95b6b75e82ae48779eb978454213 --- samples/rgm-autocomplete/src/app.tsx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index 36ac16629..95ae8e6c9 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -64,15 +64,9 @@ const PlaceAutocomplete = ({ // 3. Listen for the gmp-select event. const placeSelectListener = (e: Event) => { const event = e as google.maps.places.PlacePredictionSelectEvent; - const placePrediction = event.placePrediction; - if (!placePrediction) { - onPlaceSelect(null); - return; - } - - const place = placePrediction.toPlace(); + const place = event.placePrediction.toPlace(); - place + void place .fetchFields({ fields: [ 'location', @@ -89,7 +83,8 @@ const PlaceAutocomplete = ({ map.setZoom(13); } onPlaceSelect(place); - }); + }) + .catch((e) => console.error(e)); }; autocomplete.addEventListener('gmp-select', placeSelectListener); From 9d46d13f7682974c9bf8921414d049a4d972e54a Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 12:13:33 -0700 Subject: [PATCH 13/16] fix: Resolve strict ESLint rules for catch blocks Change-Id: Ib0e14557304be28023be5d45284b8c7d3dbabe6a --- samples/rgm-autocomplete/src/app.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/rgm-autocomplete/src/app.tsx b/samples/rgm-autocomplete/src/app.tsx index 95ae8e6c9..a681fd00b 100644 --- a/samples/rgm-autocomplete/src/app.tsx +++ b/samples/rgm-autocomplete/src/app.tsx @@ -84,7 +84,9 @@ const PlaceAutocomplete = ({ } onPlaceSelect(place); }) - .catch((e) => console.error(e)); + .catch((err: unknown) => { + console.error(err); + }); }; autocomplete.addEventListener('gmp-select', placeSelectListener); From 464d80eff26745c89ab79ac5af190605e2521160 Mon Sep 17 00:00:00 2001 From: William French Date: Thu, 6 Aug 2026 12:22:29 -0700 Subject: [PATCH 14/16] Removes stray folder. Change-Id: I93bf9f4c2b98d2ea6669b16e550f4e49427cf0c1 --- .../react-web-components-markers/README.md | 41 ------------------- .../react-web-components-markers/index.html | 38 ----------------- .../react-web-components-markers/package.json | 13 ------ .../react-web-components-markers/src/app.tsx | 39 ------------------ .../tsconfig.json | 8 ---- 5 files changed, 139 deletions(-) delete mode 100644 samples/react-web-components-markers/README.md delete mode 100644 samples/react-web-components-markers/index.html delete mode 100644 samples/react-web-components-markers/package.json delete mode 100644 samples/react-web-components-markers/src/app.tsx delete mode 100644 samples/react-web-components-markers/tsconfig.json diff --git a/samples/react-web-components-markers/README.md b/samples/react-web-components-markers/README.md deleted file mode 100644 index 73f6380c5..000000000 --- a/samples/react-web-components-markers/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# Google Maps JavaScript Sample - -## react-web-components-markers - -Add a meaningful description for react-web-components-markers here... - -## Setup - -### Before starting run: - -`npm i` - -### Run an example on a local web server - -`cd samples/react-web-components-markers` -`npm start` - -### Build an individual example - -`cd samples/react-web-components-markers` -`npm run build` - -From 'samples': - -`npm run build --workspace=react-web-components-markers/` - -### Build all of the examples. - -From 'samples': - -`npm run build-all` - -### Run lint to check for problems - -`cd samples/react-web-components-markers` -`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). diff --git a/samples/react-web-components-markers/index.html b/samples/react-web-components-markers/index.html deleted file mode 100644 index 4143a21bc..000000000 --- a/samples/react-web-components-markers/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - React - Web Components Markers - - - - - -
- - - diff --git a/samples/react-web-components-markers/package.json b/samples/react-web-components-markers/package.json deleted file mode 100644 index 0dcaf438d..000000000 --- a/samples/react-web-components-markers/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@js-api-samples/react-web-components-markers", - "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" -} diff --git a/samples/react-web-components-markers/src/app.tsx b/samples/react-web-components-markers/src/app.tsx deleted file mode 100644 index a302ed210..000000000 --- a/samples/react-web-components-markers/src/app.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// [START maps_react_web_components_markers_app] -import React from 'react'; -import { createRoot } from 'react-dom/client'; -import { APIProvider, Map, AdvancedMarker } from '@vis.gl/react-google-maps'; - -const API_KEY = 'GOOGLE_MAPS_API_KEY'; - -export default function App() { - return ( - - - - - - ); -} - -export function renderToDom(container: HTMLElement) { - const root = createRoot(container); - root.render( - - - - ); -} -// [END maps_react_web_components_markers_app] diff --git a/samples/react-web-components-markers/tsconfig.json b/samples/react-web-components-markers/tsconfig.json deleted file mode 100644 index b7054a4ed..000000000 --- a/samples/react-web-components-markers/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.react-base.json", - "compilerOptions": { - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["./src/**/*.ts*"] -} From 3a09b2f6fff91a3e6d76617b38e80c935077ebd3 Mon Sep 17 00:00:00 2001 From: William French Date: Fri, 7 Aug 2026 10:12:53 -0700 Subject: [PATCH 15/16] fix: Align react and react-dom versions to 19.2.8 to fix Error 527 Change-Id: Ic4d6705f0b11ce28b60ee4cd589bd56a1da6e9c3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a6af3ad9b..e50e50d4f 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@vis.gl/react-google-maps": "1.8.3", "dotenv": "^17.4.2", "react": "^19.2.8", - "react-dom": "^19.2.7" + "react-dom": "^19.2.8" }, "overrides": { "fast-xml-parser": "5.7.1", From 0675837c4f98c18d1f8adfe2af52023c8b2f7848 Mon Sep 17 00:00:00 2001 From: William French Date: Fri, 7 Aug 2026 10:23:11 -0700 Subject: [PATCH 16/16] chore: update package-lock.json Change-Id: I4acaa1da9fc41b16dd990e8cf77fd78cf0e8f080 --- package-lock.json | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index ab6fb7f87..5c2bba890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@vis.gl/react-google-maps": "1.8.3", "dotenv": "^17.4.2", "react": "^19.2.8", - "react-dom": "^19.2.7" + "react-dom": "^19.2.8" }, "devDependencies": { "@eslint/css": "^1.4.0", @@ -1359,10 +1359,6 @@ "resolved": "samples/polyline-utility", "link": true }, - "node_modules/@js-api-samples/react-circle-simple": { - "resolved": "samples/react-circle-simple", - "link": true - }, "node_modules/@js-api-samples/react-places-ui-kit-search-nearby": { "resolved": "samples/react-ui-kit-search-nearby", "link": true @@ -1387,14 +1383,14 @@ "resolved": "samples/react-ui-kit-place-details-latlng-compact", "link": true }, - "node_modules/@js-api-samples/react-web-components-markers": { - "resolved": "samples/react-web-components-markers", - "link": true - }, "node_modules/@js-api-samples/rectangle-event": { "resolved": "samples/rectangle-event", "link": true }, + "node_modules/@js-api-samples/rgm-autocomplete": { + "resolved": "samples/rgm-autocomplete", + "link": true + }, "node_modules/@js-api-samples/routes-compute-routes": { "resolved": "samples/routes-compute-routes", "link": true @@ -5655,15 +5651,15 @@ } }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" + "react": "^19.2.8" } }, "node_modules/readable-stream": { @@ -6941,7 +6937,8 @@ }, "samples/react-circle-simple": { "name": "@js-api-samples/react-circle-simple", - "version": "1.0.0" + "version": "1.0.0", + "extraneous": true }, "samples/react-ui-kit-place-details": { "name": "@js-api-samples/react-ui-kit-place-details", @@ -6978,12 +6975,17 @@ }, "samples/react-web-components-markers": { "name": "@js-api-samples/react-web-components-markers", - "version": "1.0.0" + "version": "1.0.0", + "extraneous": true }, "samples/rectangle-event": { "name": "@js-api-samples/rectangle-event", "version": "1.0.0" }, + "samples/rgm-autocomplete": { + "name": "@js-api-samples/rgm-autocomplete", + "version": "1.0.0" + }, "samples/routes-compute-routes": { "name": "@js-api-samples/routes-compute-routes", "version": "1.0.0"