diff --git a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js index a501ed13..79e2292a 100644 --- a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js +++ b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js @@ -152,6 +152,10 @@ class LocationAutocompleteInput extends Component { // Ref to the input element. this.input = null; + // Current sessionToken used to combine autocomplete calls with place details call + // This reduces Google Maps pricing. + this.autocompleteSessionToken = null; + this.changeHighlight = this.changeHighlight.bind(this); this.selectItem = this.selectItem.bind(this); this.selectItemIfNoneSelected = this.selectItemIfNoneSelected.bind(this); @@ -213,6 +217,7 @@ class LocationAutocompleteInput extends Component { this.setState({ highlightedIndex: -1 }); if (!newValue) { + this.autocompleteSessionToken = null; // No need to fetch predictions on empty input return; } @@ -266,7 +271,11 @@ class LocationAutocompleteInput extends Component { selectedPlace: null, }); - getPlaceDetails(placeId) + this.autocompleteSessionToken = + this.autocompleteSessionToken || new window.google.maps.places.AutocompleteSessionToken(); + const sessionToken = this.autocompleteSessionToken; + + getPlaceDetails(placeId, sessionToken) .then(place => { this.props.input.onChange({ search: prediction.description, @@ -274,6 +283,7 @@ class LocationAutocompleteInput extends Component { selectedPlaceId: placeId, selectedPlace: place, }); + this.autocompleteSessionToken = null; }) .catch(e => { // eslint-disable-next-line no-console @@ -298,7 +308,12 @@ class LocationAutocompleteInput extends Component { throw new Error('Google Maps API must be loaded for LocationAutocompleteInput'); } const onChange = this.props.input.onChange; - getPlacePredictions(search) + + this.autocompleteSessionToken = + this.autocompleteSessionToken || new window.google.maps.places.AutocompleteSessionToken(); + const sessionToken = this.autocompleteSessionToken; + + getPlacePredictions(search, sessionToken) .then(results => { const { search: currentSearch } = currentValue(this.props); diff --git a/src/util/googleMaps.js b/src/util/googleMaps.js index 4611eecc..3c3a25a6 100644 --- a/src/util/googleMaps.js +++ b/src/util/googleMaps.js @@ -36,17 +36,20 @@ const placeCountry = place => { * * @param {String} placeId - ID for a place received from the * autocomplete service + * @param {String} sessionToken - token to tie different autocomplete character searches together + * with getPlaceDetails call * * @return {Promise} Promise that * resolves to the detailed place, rejects if the request failed */ -export const getPlaceDetails = placeId => +export const getPlaceDetails = (placeId, sessionToken) => new Promise((resolve, reject) => { const serviceStatus = window.google.maps.places.PlacesServiceStatus; const el = document.createElement('div'); const service = new window.google.maps.places.PlacesService(el); + const sessionTokenMaybe = sessionToken ? { sessionToken } : {}; - service.getDetails({ placeId }, (place, status) => { + service.getDetails({ placeId, ...sessionTokenMaybe }, (place, status) => { if (status !== serviceStatus.OK) { reject( new Error(`Could not get details for place id "${placeId}", error status was "${status}"`) @@ -71,16 +74,19 @@ const predictionSuccessful = status => { * Get place predictions for the given search * * @param {String} search - place name or address to search + * @param {String} sessionToken - token to tie different autocomplete character searches together + * with getPlaceDetails call * * @return {Promise<{ search, predictions[] }>} - Promise of an object * with the original search query and an array of * `google.maps.places.AutocompletePrediction` objects */ -export const getPlacePredictions = search => +export const getPlacePredictions = (search, sessionToken) => new Promise((resolve, reject) => { const service = new window.google.maps.places.AutocompleteService(); + const sessionTokenMaybe = sessionToken ? { sessionToken } : {}; - service.getPlacePredictions({ input: search }, (predictions, status) => { + service.getPlacePredictions({ input: search, ...sessionTokenMaybe }, (predictions, status) => { if (!predictionSuccessful(status)) { reject(new Error(`Prediction service status not OK: ${status}`)); } else {