diff --git a/GooglePlacesAutocomplete.d.ts b/GooglePlacesAutocomplete.d.ts index 6f5a0bcc..15541b1c 100644 --- a/GooglePlacesAutocomplete.d.ts +++ b/GooglePlacesAutocomplete.d.ts @@ -286,11 +286,15 @@ interface GooglePlaceData { interface Point { lat: number; lng: number; + latitude: number; + longitude: number; } interface AddressComponent { long_name: string; short_name: string; + longText: string; + shortText: string; types: PlaceType[]; } @@ -323,6 +327,11 @@ interface GooglePlaceDetail { url: string; utc_offset: number; vicinity: string; + // New Places API parameters + addressComponents: AddressComponent[]; + adrFormatAddress: string; + formattedAddress: string; + location: Point; } /** @see https://developers.google.com/places/web-service/autocomplete */ @@ -431,6 +440,8 @@ interface GooglePlacesAutocompleteProps { /** text input props */ textInputProps?: TextInputProps | Object; timeout?: number; + isNewPlacesAPI?: boolean; + fields?: string; } export type GooglePlacesAutocompleteRef = { diff --git a/GooglePlacesAutocomplete.js b/GooglePlacesAutocomplete.js index 99a2a132..60836de9 100644 --- a/GooglePlacesAutocomplete.js +++ b/GooglePlacesAutocomplete.js @@ -2,6 +2,7 @@ import debounce from 'lodash.debounce'; import PropTypes from 'prop-types'; import Qs from 'qs'; +import { v4 as uuidv4 } from 'uuid'; import React, { forwardRef, useMemo, @@ -159,7 +160,7 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => { const [listLoaderDisplayed, setListLoaderDisplayed] = useState(false); const inputRef = useRef(); - + const [sessionToken, setSessionToken] = useState(uuidv4()); useEffect(() => { setUrl(getRequestUrl(props.requestUrl)); }, [getRequestUrl, props.requestUrl]); @@ -281,10 +282,14 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => { if (request.status === 200) { const responseJSON = JSON.parse(request.responseText); - - if (responseJSON.status === 'OK') { + if ( + responseJSON.status === 'OK' || + (props.isNewPlacesAPI && responseJSON.id) + ) { // if (_isMounted === true) { - const details = responseJSON.result; + const details = props.isNewPlacesAPI + ? responseJSON + : responseJSON.result; _disableRowLoaders(); _onBlur(); @@ -322,16 +327,29 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => { } }; - request.open( - 'GET', - `${url}/place/details/json?` + - Qs.stringify({ - key: props.query.key, - placeid: rowData.place_id, - language: props.query.language, - ...props.GooglePlacesDetailsQuery, - }), - ); + if (props.isNewPlacesAPI) { + request.open( + 'GET', + `${url}/v1/places/${rowData.place_id}?` + + Qs.stringify({ + key: props.query.key, + sessionToken, + fields: props.fields, + }), + ); + setSessionToken(uuidv4()); + } else { + request.open( + 'GET', + `${url}/place/details/json?` + + Qs.stringify({ + key: props.query.key, + placeid: rowData.place_id, + language: props.query.language, + ...props.GooglePlacesDetailsQuery, + }), + ); + } request.withCredentials = requestShouldUseWithCredentials(); setRequestHeaders(request, getRequestHeaders(props.requestUrl)); @@ -419,6 +437,29 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => { return results; }; + const _filterResultsByPlacePredictions = (unfilteredResults) => { + const results = []; + for (let i = 0; i < unfilteredResults.length; i++) { + if (unfilteredResults[i].placePrediction) { + results.push({ + description: unfilteredResults[i].placePrediction.text?.text, + place_id: unfilteredResults[i].placePrediction.placeId, + reference: unfilteredResults[i].placePrediction.placeId, + structured_formatting: { + main_text: + unfilteredResults[i].placePrediction.structuredFormat?.mainText + ?.text, + secondary_text: + unfilteredResults[i].placePrediction.structuredFormat + ?.secondaryText?.text, + }, + types: unfilteredResults[i].placePrediction.types ?? [], + }); + } + } + return results; + }; + const _requestNearby = (latitude, longitude) => { _abortRequests(); @@ -524,6 +565,7 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => { setListLoaderDisplayed(false); if (request.status === 200) { const responseJSON = JSON.parse(request.responseText); + if (typeof responseJSON.predictions !== 'undefined') { // if (_isMounted === true) { const results = @@ -538,6 +580,14 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => { setDataSource(buildRowsFromResults(results, text)); // } } + if (typeof responseJSON.suggestions !== 'undefined') { + const results = _filterResultsByPlacePredictions( + responseJSON.suggestions, + ); + + _results = results; + setDataSource(buildRowsFromResults(results, text)); + } if (typeof responseJSON.error_message !== 'undefined') { if (!props.onFail) console.warn( @@ -556,18 +606,39 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => { setStateText(props.preProcess(text)); } - request.open( - 'GET', - `${url}/place/autocomplete/json?input=` + - encodeURIComponent(text) + - '&' + - Qs.stringify(props.query), - ); + if (props.isNewPlacesAPI) { + const keyQueryParam = props.query.key + ? '?' + + Qs.stringify({ + key: props.query.key, + }) + : ''; + request.open('POST', `${url}/v1/places:autocomplete${keyQueryParam}`); + } else { + request.open( + 'GET', + `${url}/place/autocomplete/json?input=` + + encodeURIComponent(text) + + '&' + + Qs.stringify(props.query), + ); + } request.withCredentials = requestShouldUseWithCredentials(); setRequestHeaders(request, getRequestHeaders(props.requestUrl)); - request.send(); + if (props.isNewPlacesAPI) { + const { key, locationbias, types, ...rest } = props.query; + request.send( + JSON.stringify({ + input: text, + sessionToken, + ...rest, + }), + ); + } else { + request.send(); + } } else { _results = []; setDataSource(buildRowsFromResults([])); @@ -942,6 +1013,8 @@ GooglePlacesAutocomplete.propTypes = { textInputHide: PropTypes.bool, textInputProps: PropTypes.object, timeout: PropTypes.number, + isNewPlacesAPI: PropTypes.bool, + fields: PropTypes.string, }; GooglePlacesAutocomplete.defaultProps = { @@ -986,6 +1059,8 @@ GooglePlacesAutocomplete.defaultProps = { textInputHide: false, textInputProps: {}, timeout: 20000, + isNewPlacesAPI: false, + fields: '*', }; GooglePlacesAutocomplete.displayName = 'GooglePlacesAutocomplete'; diff --git a/package.json b/package.json index a299ca91..ba5265ff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-google-places-autocomplete", - "version": "2.5.6", + "version": "2.5.7", "description": "Customizable Google Places autocomplete component for iOS and Android React-Native apps", "main": "GooglePlacesAutocomplete.js", "types": "GooglePlacesAutocomplete.d.ts", @@ -31,7 +31,8 @@ "dependencies": { "lodash.debounce": "^4.0.8", "prop-types": "^15.7.2", - "qs": "~6.9.1" + "qs": "~6.9.1", + "uuid": "^10.0.0" }, "devDependencies": { "@react-native-community/eslint-config": "2.0.0",