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

> [!IMPORTANT]
> **Important:** The [vis.gl/react-google-maps](https://visgl.github.io/react-google-maps/) library is offered using an open source license. It is not governed by the Google Maps Platform Support Technical Support Services Guidelines, the SLA, or the Deprecation Policy (however, any Google Maps Platform services used by the library remain subject to the Google Maps Platform Terms of Service). If you find a bug, or have a feature request, file an issue on [GitHub](https://github.com/visgl/react-google-maps/issues) .

<iframe src="https://maps-docs-team.web.app/samples/rgm-autocomplete/dist/" allow="fullscreen; "></iframe>

### TypeScript

```jsx
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>,
                HTMLElement
            >;
        }
    }
}

const PlaceAutocomplete = ({
    onPlaceSelect,
}: {
    onPlaceSelect: (place: google.maps.places.Place | null) => void;
}) => {
    const map = useMap();
    const placesLibrary = useMapsLibrary('places');
    const containerRef = useRef<HTMLDivElement>(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: Event) => {
            const event = e as google.maps.places.PlacePredictionSelectEvent;
            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: unknown) => {
                    console.error(err);
                });
        };

        autocomplete.addEventListener('gmp-select', placeSelectListener);

        return () => {
            google.maps.event.removeListener(boundsListener);
            autocomplete.removeEventListener('gmp-select', placeSelectListener);

            // Clean up the DOM element when unmounting.
            if (containerRef.current) {
                containerRef.current.innerHTML = '';
            }
        };
    }, [map, placesLibrary, onPlaceSelect]);

    return (
        <div
            className="place-autocomplete-card"
            style={{
                backgroundColor: '#fff',
                borderRadius: '5px',
                boxShadow: 'rgba(0, 0, 0, 0.35) 0px 5px 15px',
                margin: '10px',
                padding: '5px',
                fontFamily: 'Roboto, sans-serif',
                fontSize: 'small',
                width: '300px',
            }}>
            <div ref={containerRef} style={{ width: '100%' }} />
        </div>
    );
};

export default function App() {
    const [selectedPlace, setSelectedPlace] =
        useState<google.maps.places.Place | null>(null);
    const [markerRef, marker] = useAdvancedMarkerRef();

    return (
        <APIProvider apiKey={API_KEY}>
            <Map
                defaultCenter={{ lat: 40.749933, lng: -73.98633 }}
                defaultZoom={13}
                gestureHandling={'greedy'}
                mapId="DEMO_MAP_ID"
                disableDefaultUI={true}>
                <MapControl position={ControlPosition.BLOCK_START_INLINE_START}>
                    <PlaceAutocomplete onPlaceSelect={setSelectedPlace} />
                </MapControl>

                {selectedPlace?.location && (
                    <AdvancedMarker
                        ref={markerRef}
                        position={selectedPlace.location}
                    />
                )}

                {selectedPlace?.location && marker && (
                    <InfoWindow anchor={marker}>
                        <div>
                            <span style={{ fontWeight: 'bold' }}>
                                {selectedPlace.displayName ?? 'No name'}
                            </span>
                            <br />
                            <span>
                                {selectedPlace.formattedAddress ?? 'No address'}
                            </span>
                        </div>
                    </InfoWindow>
                )}
            </Map>
        </APIProvider>
    );
}

export function renderToDom(container: HTMLElement) {
    const root = createRoot(container);
    root.render(
        <React.StrictMode>
            <App />
        </React.StrictMode>
    );
}
```

### JavaScript

```javascript
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)));
}
```

### CSS

```css
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;
}
```

### HTML

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

            renderToDom(document.querySelector('#app'));
        </script>
    </head>
    <body>
        <div id="app"></div>
    </body>
</html>
```

### Clone Sample


Git and Node.js are required to run this sample locally. Follow these [instructions](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
to install Node.js and NPM. You will also need a [Google Maps API key](https://developers.google.com/maps/documentation/javascript/get-api-key.md.txt).


The following commands clone the repository, install dependencies, and copy the environment file.

```javascript
  git clone https://github.com/googlemaps-samples/js-api-samples.git
  cd js-api-samples
  npm i
  cp .env.template .env
```


Once you copy the environment file, make sure to open the `.env` file and replace `<Insert your own development API key here>` with your actual API key before starting the sample.

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

## Integration notes

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

### 1. Programmatic Instantiation

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

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

### 2. Location Restriction \& Cross-Context Objects

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

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

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

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

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

### 4. Event Cleanup

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