Skip to content

Commit

Permalink
docs: add new autocomplete example (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrMetalWood committed Feb 7, 2024
1 parent 11fdc13 commit 5176df8
Show file tree
Hide file tree
Showing 23 changed files with 693 additions and 207 deletions.
61 changes: 0 additions & 61 deletions docs/api-reference/hooks/use-autocomplete.md

This file was deleted.

1 change: 0 additions & 1 deletion docs/guides/interacting-with-google-maps-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ Other hooks provide access to different Google Maps API services:
- [useDirectionsService](../api-reference/hooks/use-directions-service.md)
for the [Directions Service](https://developers.google.com/maps/documentation/javascript/directions)
- [useStreetViewPanorama](../api-reference/hooks/use-streetview-panorama.md) for the [Streetview Service](https://developers.google.com/maps/documentation/javascript/streetview)
- [useAutocomplete](../api-reference/hooks/use-autocomplete.md) for the [Places Widget](https://developers.google.com/maps/documentation/javascript/reference/places-widget)

The [useMapsLibrary](../api-reference/hooks/use-maps-library.md) hook can be
utilized to load other parts of the Google Maps API that are not loaded by default.
Expand Down
3 changes: 1 addition & 2 deletions docs/table-of-contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@
"api-reference/hooks/use-api-is-loaded",
"api-reference/hooks/use-maps-library",
"api-reference/hooks/use-directions-service",
"api-reference/hooks/use-streetview-panorama",
"api-reference/hooks/use-autocomplete"
"api-reference/hooks/use-streetview-panorama"
]
}
]
Expand Down
58 changes: 58 additions & 0 deletions examples/autocomplete/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Autocomplete Examples

Here you can find a few example implementations of the autocomplete functionality utilizing the Google Places API.

## Examples

We have three different implementations to demonstrate how to add autocomplete functionality to your application

### 1) Google Maps Autocomplete Widget

When using the [Google Maps Autocomplete widget][autocomplete-widget] you provide an HTML input element of your choice and Google handles all the rest. It will fetch predictions when the user types and it will get the details for a place when the user selects a prediction from the list.

### 2) Custom Build

When you need complete control over every aspect of your autocomplete you can choose to build your own by utilizing the [Autocomplete Service][autocomplete-service] for fetching query predictions and the [Places Service][place-details] for fetching the place details.

When building your own you are completely free but also responsible for the user experience of the autocomplete. You are also responsible for handling the autocomplete session with a [Session token][session-token]. This can easily be overlooked and may lead to unexpected surprises when it comes to billing.

### 3) Third Party Select Widget

This is basically the same as the custom build, except for not having to implement the list/dropdown/DOM handling yourself. A lot of third party text box widgets provide functionionality for handling keyboard navigation and focus handling. For the demo we used the [Combobox][combobox] from `react-widgets`.

## Google Maps API key

This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform.
See [the official documentation][get-api-key] on how to create and configure your own key. For this example to work you also need to enable the `Places API` in your Google Cloud Console.

The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a
file named `.env` in the example directory with the following content:

```shell title=".env"
GOOGLE_MAPS_API_KEY="<YOUR API KEY HERE>"
```

If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets)

## Development

Go into the example-directory and run

```shell
npm install
```

To start the example with the local library run

```shell
npm run start-local
```

The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example)

[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key
[autocomplete-widget]: https://developers.google.com/maps/documentation/javascript/place-autocomplete#add-autocomplete
[autocomplete-service]: https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompleteService.getPlacePredictions
[place-details]: https://developers.google.com/maps/documentation/javascript/reference/places-service#PlacesService.getDetails
[session-token]: https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompleteSessionToken
[combobox]: https://jquense.github.io/react-widgets/docs/Combobox
31 changes: 31 additions & 0 deletions examples/autocomplete/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no" />
<title>Autocomplete Examples</title>
<meta name="description" content="Autocomplete Examples" />
<style>
body {
margin: 0;
font-family: sans-serif;
}
#app {
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module">
import '@vis.gl/react-google-maps/examples.css';
import '@vis.gl/react-google-maps/examples.js';
import {renderToDom} from './src/app';

renderToDom(document.querySelector('#app'));
</script>
</body>
</html>
15 changes: 15 additions & 0 deletions examples/autocomplete/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"type": "module",
"dependencies": {
"@vis.gl/react-google-maps": "*",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-widgets": "^5.8.4",
"vite": "^5.0.4"
},
"scripts": {
"start": "vite",
"start-local": "vite --config ../vite.config.local.js",
"build": "vite build"
}
}
59 changes: 59 additions & 0 deletions examples/autocomplete/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, {useState} from 'react';
import {createRoot} from 'react-dom/client';
import {APIProvider, ControlPosition, Map} from '@vis.gl/react-google-maps';

import ControlPanel from './control-panel';
import {CustomMapControl} from './map-control';
import MapHandler from './map-handler';

const API_KEY =
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string);

export type AutocompleteMode = {id: string; label: string};

const autocompleteModes: Array<AutocompleteMode> = [
{id: 'classic', label: 'Google Autocomplete Widget'},
{id: 'custom', label: 'Custom Build'},
{id: 'custom-hybrid', label: 'Custom w/ Select Widget'}
];

const App = () => {
const [selectedAutocompleteMode, setSelectedAutocompleteMode] =
useState<AutocompleteMode>(autocompleteModes[0]);

const [selectedPlace, setSelectedPlace] =
useState<google.maps.places.PlaceResult | null>(null);

return (
<APIProvider apiKey={API_KEY}>
<Map
zoom={3}
center={{lat: 22.54992, lng: 0}}
gestureHandling={'greedy'}
disableDefaultUI={true}
/>

<CustomMapControl
controlPosition={ControlPosition.TOP}
selectedAutocompleteMode={selectedAutocompleteMode}
onPlaceSelect={setSelectedPlace}
/>

<ControlPanel
autocompleteModes={autocompleteModes}
selectedAutocompleteMode={selectedAutocompleteMode}
onAutocompleteModeChange={setSelectedAutocompleteMode}
/>

<MapHandler place={selectedPlace} />
</APIProvider>
);
};

export default App;

export function renderToDom(container: HTMLElement) {
const root = createRoot(container);

root.render(<App />);
}
44 changes: 44 additions & 0 deletions examples/autocomplete/src/autocomplete-alpha.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// NOTE: This requires the alpha version of the Google Maps API and is not yet
// recommended to be used in production applications. We will add this to the example map
// when it reaches GA (General Availability). Treat this as a preview of what's to come.

import React, {useRef, useEffect, useState} from 'react';
import {useMapsLibrary} from '@vis.gl/react-google-maps';

interface Props {
onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void;
}

// This is an example of the new "PlaceAutocomplete" widget.
// https://developers.google.com/maps/documentation/javascript/place-autocomplete-new
export const PlaceAutocompleteNew = ({onPlaceSelect}: Props) => {
const [placeAutocomplete, setPlaceAutocomplete] = useState<Node | null>(null);
const containerRef = useRef<HTMLDivElement>(null);
const places = useMapsLibrary('places');

useEffect(() => {
if (!places) return;
// @ts-expect-error Using an alpha feature here. The types are not up to date yet
setPlaceAutocomplete(new places.PlaceAutocompleteElement());
}, [places]);

useEffect(() => {
if (!placeAutocomplete) return;

placeAutocomplete.addEventListener(
'gmp-placeselect',
// @ts-expect-error This new event has no types yet
async ({place}: {place: google.maps.places.Place}) => {
await place.fetchFields({
fields: ['displayName', 'formattedAddress', 'location', 'viewport']
});

onPlaceSelect(place.toJSON());
}
);

containerRef.current?.appendChild(placeAutocomplete);
}, [onPlaceSelect, placeAutocomplete]);

return <div className="autocomplete-container" ref={containerRef} />;
};
39 changes: 39 additions & 0 deletions examples/autocomplete/src/autocomplete-classic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, {useRef, useEffect, useState} from 'react';
import {useMapsLibrary} from '@vis.gl/react-google-maps';

interface Props {
onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void;
}

// This is an example of the classic "Place Autocomplete" widget.
// https://developers.google.com/maps/documentation/javascript/place-autocomplete
export const PlaceAutocompleteClassic = ({onPlaceSelect}: Props) => {
const [placeAutocomplete, setPlaceAutocomplete] =
useState<google.maps.places.Autocomplete | null>(null);
const inputRef = useRef<HTMLInputElement>(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 (
<div className="autocomplete-container">
<input ref={inputRef} />
</div>
);
};
Loading

0 comments on commit 5176df8

Please sign in to comment.