From dc9821f558032253c28fc52ed6b7585079ad8438 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Thu, 12 Jul 2018 22:00:09 +0300 Subject: [PATCH 1/4] Add sessionToken for Autocomplete and place details calls --- .../LocationAutocompleteInput.js | 19 +++++++++++++++++-- src/util/googleMaps.js | 14 ++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) 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 { From d32561d09ed0a728ec8aac5b0cbd8ebe3f7be146 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Thu, 12 Jul 2018 22:00:47 +0300 Subject: [PATCH 2/4] Use sparse fields for place details call --- src/util/googleMaps.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/util/googleMaps.js b/src/util/googleMaps.js index 3c3a25a6..e64daf75 100644 --- a/src/util/googleMaps.js +++ b/src/util/googleMaps.js @@ -47,9 +47,10 @@ export const getPlaceDetails = (placeId, sessionToken) => const serviceStatus = window.google.maps.places.PlacesServiceStatus; const el = document.createElement('div'); const service = new window.google.maps.places.PlacesService(el); + const fields = ['address_component', 'formatted_address', 'geometry', 'place_id']; const sessionTokenMaybe = sessionToken ? { sessionToken } : {}; - service.getDetails({ placeId, ...sessionTokenMaybe }, (place, status) => { + service.getDetails({ placeId, fields, ...sessionTokenMaybe }, (place, status) => { if (status !== serviceStatus.OK) { reject( new Error(`Could not get details for place id "${placeId}", error status was "${status}"`) From f4fbb22b3c00255e56153169d8c622713877c2f6 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Thu, 12 Jul 2018 22:10:06 +0300 Subject: [PATCH 3/4] Update Changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e214ea4..3d5b6d65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,9 @@ way to update this template, but currently, we follow a pattern: --- ## Upcoming version - +* [change] Use sessionTokens and fields for Autocomplete calls to Google Maps. + This is a reaction to pricing change of Google Maps APIs. + [#867](https://github.com/sharetribe/flex-template-web/pull/867) * [change] Change TransactionPage state management in loadData. [#863](https://github.com/sharetribe/flex-template-web/pull/863), [#865](https://github.com/sharetribe/flex-template-web/pull/865) & [#866](https://github.com/sharetribe/flex-template-web/pull/866) * [fix] Fix submit button state on contact details page. From aeac2cd960f81cf14b4cabd7eb3e0f139d1ef168 Mon Sep 17 00:00:00 2001 From: Vesa Luusua Date: Thu, 19 Jul 2018 14:08:52 +0300 Subject: [PATCH 4/4] Remove clearing of sessionToken when the input is empty Note: this might be reversed, if there's some time limit for tokens) --- .../LocationAutocompleteInput/LocationAutocompleteInput.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js index 79e2292a..ba198de7 100644 --- a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js +++ b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js @@ -217,7 +217,6 @@ class LocationAutocompleteInput extends Component { this.setState({ highlightedIndex: -1 }); if (!newValue) { - this.autocompleteSessionToken = null; // No need to fetch predictions on empty input return; }