Three ways to add Google Autocomplete to your React app
Google Autocomplete in React: Should you use react-google-places-autocomplete, prebuilt components, or a custom build?
Google Autocomplete is a Google Maps API that returns location suggestions as a user types into a search field. Suggestions appear in a dropdown in real time, narrowing down as the user types. It's commonly used in e-commerce checkouts, delivery and logistics apps, ride sharing platforms, and travel websites - anywhere accurate address input matters. In this blog post, I cover three ways to add Google Autocomplete to a React app and explain the benefits and tradeoffs of using each. As with my other tutorials in this series, you can find working code samples at our GitHub repo react_google_maps_demo.

Part 1: React Google Maps: Build with Google Maps using React
Part 2: Google Maps with React: Add a Google Map and style it
Part 3: Work with Google Maps React map markers and info windows
Part 4: Draw a Google Maps polyline in React
Part 5: Three ways to add Google Autocomplete to your React app (this article)
Adding Google Autocomplete with the react-google-places-autocomplete library

The fastest way to add Google Autocomplete to your website or app is by using the react-google-places-autocomplete library. It's built on top of react-select , so you get a polished dropdown out of the box along with a flexible styling API to customize every part of the UI. To start using it, follow these steps:
- Install the library
npm install react-google-places-autocomplete- Enable the Places API in your Google Cloud Console and grab your API key. You'll want to use the legacy Google Places API, not Places API (New) because it uses the legacy
google.maps.places.AutocompleteServiceunder the hood. The library was written before Places API (New) product was launched, and hasn't been updated to use it. - Import the
GooglePlacesAutocompletecomponent together with thegeocodeByPlaceIdandgetLatLngmethods. The legacy Place Autocomplete API does not return a geocoded latitude and longitude, which is why we need these two methods to make a follow on API call to the Google Geocoding API.
import GooglePlacesAutocomplete from 'react-google-places-autocomplete';
import { geocodeByPlaceId, getLatLng } from 'react-google-places-autocomplete';- Add a
<GooglePlacesAutocomplete/>component to your return statement. This gives you a basic text input field that updates with predicted place options as the user types. You can style the dropdown using basic CSS or a custom styles prop.
return (
<div>
<div
style={{
minWidth: "300px",
position: "absolute",
top: "25px",
left: "25px",
}}
>
<GooglePlacesAutocomplete
apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
selectProps={{
value: autocompletePlace,
onChange: handleSelect,
}}
/>
</div>
</div>
);- Write an event handler to handle the
onChangeevent from the autocomplete component. The code below takes theplaceobject returned by the Place Autocomplete API and geocodes it to get its GPS coordinates.
const [autocompletePlace, setAutocompletePlace] = useState(null);
const [coords, setCoords] = useState(null);
const handleSelect = async (place) => {
setAutocompletePlace(place);
const results = await geocodeByPlaceId(place.value.place_id);
const { lat, lng } = await getLatLng(results[0]);
setCoords({ lat, lng });
console.log(lat, lng);
};As you can see, react-google-places-autocomplete is easy to use and comes with tons of features and styling options thanks to react-select.

For instance, to add a custom icon to the left of each dropdown option (see above), all you need to do is modify the formatOptionLabel prop (which lets you control exactly what renders inside each option) and add an inline SVG to it.
const MarkerIcon = () => (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
{/* Circle background */}
<circle cx="16" cy="16" r="15" fill="#94B4F7" />
{/* Map marker */}
<path
d="M16 8C13.24 8 11 10.24 11 13C11 17 16 22 16 22C16 22 21 17 21 13C21 10.24 18.76 8 16 8ZM16 14.5C15.17 14.5 14.5 13.83 14.5 13C14.5 12.17 15.17 11.5 16 11.5C16.83 11.5 17.5 12.17 17.5 13C17.5 13.83 16.83 14.5 16 14.5Z"
fill="#ffffff"
/>
</svg>
);
const formatOptionLabel = ({ label }) => (
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<MarkerIcon />
<span>{label}</span>
</div>
);
return (
<div>
<GooglePlacesAutocomplete
apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
selectProps={{
value: autocompletePlace,
onChange: handleSelect,
formatOptionLabel: formatOptionLabel,
}}
/>
</div>
);So what's the downside to using it?
The main problem with this library is that it does not play well with @vis.gl/react-google-maps, which is a non-starter if your app is built around Google Maps. If you use them together, both libraries try to inject <script src="maps.googleapis.com/...?key=YOUR_KEY&libraries=places"> at the same time. Google detects two Maps scripts on the page and throws a warning:
This causes all kinds of render and data consistency issues on the frontend. So should you use react-google-places-autocomplete? My answer is yes, particularly if you are not using a Google Map in your app e.g. if you are using autocomplete strictly for address validation on a checkout page. If you are using autocomplete with a Google Map, read on!
Adding Google Autocomplete using prebuilt components

The easiest way to add Google Autocomplete to a React app is with the <gmp-place-autocomplete/> web component. It's part of Google's Maps Web Components collection - a set of HTML elements that replace verbose Javascript setup with simple, declarative markup.
First, add the <APIProvider/> component from @vis.gl/react-google-maps to the return statement of App.jsx.
<APIProvider
apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
libraries={["places"]}
version="beta"
></APIProvider>;Make sure to add the following props:
libraries={["places"]}: loads the Places library, which is required for<gmp-place-autocomplete/>to work.version="beta": loads the beta version of the API, which is required because<gmp-place-autocomplete/>isn't in the stable release yet.
Nested inside <APIProvider/>, add the <gmp-map/> component. Its the web component alternative to <Map/> in the @vis.gl/react-google-maps library.
<gmp-map
ref={mapRef}
center="49.2827,-123.1207"
zoom="13"
map-id={import.meta.env.VITE_GOOGLE_MAPS_MAP_ID}
style={{ display: "block", width: "100vw", height: "100vh" }}
></gmp-map>;Add <gmp-place-autocomplete/> as a child of <gmp-map/>. Wrap it in a div and set slot="control-inline-start-block-start" on the div to position it in the top left corner of the map.
<div slot="control-inline-start-block-start" style={{ margin: "10px" }}>
<gmp-place-autocomplete placeholder="Search for a place..."></gmp-place-autocomplete>
</div>Here's what the full App.jsx looks like:
import { useRef, useEffect } from "react";
import { APIProvider } from "@vis.gl/react-google-maps";
function App() {
const mapRef = useRef(null);
useEffect(() => {
if (!mapRef.current) return;
customElements.whenDefined("gmp-map").then(() => {
mapRef.current.innerMap.setOptions({
mapTypeControl: false,
});
});
}, []);
return (
<APIProvider
apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
libraries={["places"]}
version="beta"
>
<gmp-map
ref={mapRef}
center="49.2827,-123.1207"
zoom="13"
map-id={import.meta.env.VITE_GOOGLE_MAPS_MAP_ID}
style={{ display: "block", width: "100vw", height: "100vh" }}
>
<div slot="control-inline-start-block-start" style={{ margin: "10px" }}>
<gmp-place-autocomplete placeholder="Search for a place..."></gmp-place-autocomplete>
</div>
</gmp-map>
</APIProvider>
);
}
export default App;The widget works, but it doesn't do anything with your selection yet. To make it useful e.g. dropping a marker at the selected location, you'll need to capture the place data when a user picks a suggestion, store it in state, and use that state to update the marker on the map. Here's what we need to change in our code:

- Add an
autocompleteRefto track the autocomplete element.
<gmp-place-autocomplete
ref={autocompleteRef}
placeholder="Search for a place..."
></gmp-place-autocomplete>- Include a second
useEffectthat attaches ahandlePlaceSelectevent handler to listen for thegmp-selectevent, which fires when the user selects a place from the autocomplete dropdown. The handler then updates the marker position.
useEffect(() => {
if (!autocompleteRef.current) return;
const handlePlaceSelect = async ({ placePrediction }) => {
const place = placePrediction.toPlace();
await place.fetchFields({ fields: ["location", "displayName"] });
const location = place.location;
setMarkerPosition(`${location.lat()},${location.lng()}`);
// Pan the map to the selected location
mapRef.current.innerMap.panTo(location);
mapRef.current.innerMap.setZoom(15);
};
autocompleteRef.current.addEventListener("gmp-select", handlePlaceSelect);
return () => {
autocompleteRef.current?.removeEventListener(
"gmp-select",
handlePlaceSelect,
);
};
}, []);- Include the
markerlibrary so that<gmp-advanced-marker>gets registered.
<APIProvider
apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY}
libraries={["places", "marker"]}
version="beta"
></APIProvider>- Introduce a
markerPositionstate to store the coordinates of the user's selected place.
const [markerPosition, setMarkerPosition] = useState(null)- Insert a
<gmp-advanced-marker>as a child component of<gmp-map/>that renders conditionally whenmarkerPositionis set.
{
markerPosition && (
<>
{console.log("Marker position:", markerPosition)}
<gmp-advanced-marker position={markerPosition}></gmp-advanced-marker>
</>
);
}Here's what the revised App.jsx looks like:
import { useRef, useEffect, useState } from 'react'
import { APIProvider } from '@vis.gl/react-google-maps'
function App() {
const mapRef = useRef(null)
const autocompleteRef = useRef(null)
const [markerPosition, setMarkerPosition] = useState(null)
useEffect(() => {
if (!mapRef.current) return
customElements.whenDefined('gmp-map').then(() => {
mapRef.current.innerMap.setOptions({
mapTypeControl: false,
})
})
}, [])
useEffect(() => {
if (!autocompleteRef.current) return
const handlePlaceSelect = async ({ placePrediction }) => {
const place = placePrediction.toPlace()
await place.fetchFields({ fields: ['location', 'displayName'] })
const location = place.location
setMarkerPosition(`${location.lat()},${location.lng()}`)
// Pan the map to the selected location
mapRef.current.innerMap.panTo(location)
mapRef.current.innerMap.setZoom(15)
}
autocompleteRef.current.addEventListener('gmp-select', handlePlaceSelect)
return () => {
autocompleteRef.current?.removeEventListener('gmp-select', handlePlaceSelect)
}
}, [])
return (
<APIProvider apiKey={import.meta.env.VITE_GOOGLE_MAPS_API_KEY} libraries={['places', 'marker']} version="beta">
<gmp-map
ref={mapRef}
center="49.2827,-123.1207"
zoom="13"
map-id={import.meta.env.VITE_GOOGLE_MAPS_MAP_ID}
style={{ display: 'block', width: '100vw', height: '100vh' }}
>
<div slot="control-inline-start-block-start" style={{ margin: '10px' }}>
<gmp-place-autocomplete
ref={autocompleteRef}
placeholder="Search for a place..."
></gmp-place-autocomplete>
</div>
{markerPosition && (
<>
{console.log('Marker position:', markerPosition)}
<gmp-advanced-marker position={markerPosition}></gmp-advanced-marker>
</>
)}
</gmp-map>
</APIProvider>
)
}
export default AppDon't worry if the details feel like a lot. The full source code is available in our GitHub repo, react_google_maps_demo.
But manually attaching event listeners and reaching into the DOM with refs isn't idiomatic React. A cleaner approach is to use the @vis.gl/react-google-maps built in components, which wrap Google's Maps API in React-friendly components and hooks. That's the approach I recommend, and the one we'll use in the next section.
Google Autocomplete custom build in react-google-maps

First, a heads-up. @vis.gl/react-google-maps doesn't provide a wrapper component for google.maps.places.PlaceAutocompleteElement. Instead, it gives you a useMapsLibrary('places') hook (docs) that loads the Places Library and returns it once ready, letting you instantiate PlaceAutocompleteElement however you like.
This is by design. The maintainers have actually explicitly declined to add an autocomplete hook, stating that "providing a good autocomplete solution is a lot more complicated than can properly be implemented in a simple hook" (source).
The good news is that adding your own version of Google Autocomplete to a @vis.gl/react-google-maps app is largely a matter of including boilerplate JSX code to your project and adding a <PlaceAutocompleteInput/> component to your render output.
- In your
/srcfolder, create a new/componentsfolder and copy the following files into it (download link):AutocompleteStyles.css: Basic styling for the Google Autocomplete widget.PlaceAutocompleteInput.jsx: The Google Autocomplete component.SearchBox.css: Styling for the parent<SearchBox/>component.SearchBox.jsx: The component that wraps<PlaceAutocompleteInput/>. - In
App.jsx, add<SearchBox/>as a sibling of<Map/>. If you run the app now you'll see a working Google Autocomplete text field that returns location suggestions as you type. Selecting a location does nothing though because we haven't hooked it up. - Add an
autocompletePlacestate variable to store the most recently selected place.
const [autocompletePlace, setAutocompletePlace] = useState(null);- Pass the state setter
setAutocompletePlacedown to the<SearchBox/>component as a prop calledonSelectAutocompletePlace, so the child can update the parent's state when a place is selected.
<SearchBox onSelectAutocompletePlace={setAutocompletePlace} />- Conditionally render a
<Marker/>component when theautocompletePlaceis not null i.e. the user has selected a place from the Google Autocomplete widget (also don't forget to importMarkerat the top i.e.import { Map, Marker } from '@vis.gl/react-google-maps';.
{
autocompletePlace && (
<Marker
position={{
lat: autocompletePlace.geometry.location.lat(),
lng: autocompletePlace.geometry.location.lng(),
}}
/>
);
}The magic behind the custom Google Autocomplete implementation takes place in PlaceAutocompleteInput.jsx. Its return statement contains a text input field with a ref="inputRef".
return (
<div className="PlaceAutocompleteInput">
<input ref={inputRef} />
</div>
);In the useEffect hook of PlaceAutocompleteInput.jsx, we bind this text input field (using the ref mentioned above) to a new Google Places Autocomplete instance:
useEffect(() => {
if (!places || !inputRef.current) return;
const options = {
fields: ["place_id", "geometry", "name", "formatted_address"],
};
setPlaceAutocomplete(new places.Autocomplete(inputRef.current, options));
}, [places]);From there, Google's Autocomplete class handles the rest. It attaches event listeners, creates and positions the dropdown, and wires up calls to the Google Places API - all automagically.
The last step is to add a listener on the place_changed event and pass the selected place data to the onPlaceSelect prop, which eventually bubbles up to App.jsx.
useEffect(() => {
if (!placeAutocomplete) return;
placeAutocomplete.addListener("place_changed", () => {
onPlaceSelect(placeAutocomplete.getPlace());
});
}, [onPlaceSelect, placeAutocomplete]);That's it! You've now got a clean, React idiomatic Google Autocomplete widget wired up to a map with marker placement. From here, you can extend it however you like: enable smooth pan-and-zoom, add custom marker icons, display place details in a sidebar, or restrict results to specific regions or place types.
Where to go from here
This series has focused on building Google Maps apps in React through small, practical examples. Our tool of choice has been @vis.gl/react-google-maps, though as you've seen, other approaches work just as well. Building a production app is a bigger undertaking, but we've got you covered there too. This blog has plenty of real world examples using various Google Maps APIs, complete with open source code. Here are a few of my favorites:



Things move fast in both the React and Google Maps worlds. The easiest way to keep up is to follow the Google Maps Platform blog and check in on vis.gl now and then for new examples and release notes.
👋 As always, if you have any questions or suggestions for me, please reach out or say hello on LinkedIn.