├── .babelrc ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── SECURITY.md ├── docs ├── debounce.js ├── example.gif ├── examples.js └── formik.js ├── index.d.ts ├── index.js ├── lib ├── ReactGoogleAutocomplete.js ├── constants.js ├── index.js ├── usePlacesAutocompleteService.d.ts ├── usePlacesAutocompleteService.js ├── usePlacesService.js ├── usePlacesWidget.js └── utils.js ├── package-lock.json ├── package.json └── src ├── ReactGoogleAutocomplete.js ├── constants.js ├── index.js ├── usePlacesAutocompleteService.d.ts ├── usePlacesAutocompleteService.js ├── usePlacesWidget.js └── utils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "ie": "11" 9 | } 10 | } 11 | ] 12 | // TODO: Minify breaks IE 11 13 | // ["minify"] 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '34 16 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | /node_modules/ 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ven Korolev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](/docs/example.gif) 2 | 3 | ![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/ReactGoogleAutocomplete.js?compression=gzip&label=gzip) 4 | ![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/ReactGoogleAutocomplete.js?compression=brotli&label=brotli) 5 | ![](https://badgen.net/npm/dm/react-google-autocomplete) 6 | [![GitHub license](https://img.shields.io/github/license/Naereen/StrapDown.js.svg)](https://GitHub.com/ErrorPro/react-google-autocomplete/master/LICENSE) 7 | 8 | ## The package provides 3 tools for working with google places services: 9 | 10 | 1. [ReactGoogleAutocomplete](#reactgoogleautocomplete) is a simple html input component that provides functionality of the [google places widgets](https://developers.google.com/maps/documentation/javascript/reference/places-widget#AutocompleteOptions). 11 | 2. [usePlacesWidget](#useplaceswidget) is a react hook that provides the same functionality as `ReactGoogleAutocomplete` does but it does not create any dom elements. Instead, it gives you back a react ref which you can set to any input you want. 12 | 3. [usePlacesAutocompleteService](#useplacesautocompleteservice) is a more complex react hook. It uses [google places autocomplete service](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service) and it provides all the functionality to you as the returned value. In addition to that, you can set a `debounce` prop which will reduce the amount of requests users send to Google. 13 | 14 | If you find this package helpful please give it a star because it hepls it grow and motivates us to build new features and support the old ones. 15 | 16 | ## Install 17 | 18 | `npm i react-google-autocomplete --save` 19 | 20 | or 21 | 22 | `yarn add react-google-autocomplete` 23 | 24 |
25 | 26 | As of version 1.2.4, you can now pass an `apiKey` prop to automatically load the Google maps scripts. The api key can be found in your [google cloud console.](https://developers.google.com/maps/documentation/javascript/get-api-key). The places service hook requires both the Places API and Maps Javascript API to be enabled. 27 | 28 | ```js 29 | console.log(place)} 32 | /> 33 | or 34 | const { ref } = usePlacesWidget({ 35 | apiKey: YOUR_GOOGLE_MAPS_API_KEY, 36 | onPlaceSelected: (place) => console.log(place) 37 | }) 38 | 39 | 40 | ``` 41 | 42 | Alternatively if not passing the `apiKey` prop, you can include google autocomplete link api in your app. Somewhere in index.html or somewhere else. More info [here](https://developers.google.com/maps/documentation/places/web-service/autocomplete) 43 | 44 | ```html 45 | 49 | ``` 50 | 51 | ## ReactGoogleAutocomplete 52 | 53 | This is a simple react component for working with google [autocomplete](https://developers.google.com/maps/documentation/javascript/examples/places-autocomplete) 54 | 55 | ```js 56 | import Autocomplete from "react-google-autocomplete"; 57 | 58 | { 61 | console.log(place); 62 | }} 63 | />; 64 | ``` 65 | 66 | ### Props 67 | 68 | - `apiKey`: pass to automatically load the Google maps scripts. The api key can be found in your [google cloud console.](https://developers.google.com/maps/documentation/javascript/get-api-key) 69 | 70 | - `ref`: [React ref](https://reactjs.org/docs/hooks-reference.html#useref) to be assigned the underlying text input ref. 71 | 72 | - `onPlaceSelected: (place: `[PlaceResult](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult)`, inputRef, `[autocompleteRef](https://developers.google.com/maps/documentation/javascript/reference/places-widget#Autocomplete)`) => void`: The function gets invoked every time a user chooses location. 73 | 74 | - `options`: [Google autocomplete options.](https://developers.google.com/maps/documentation/javascript/reference/places-widget#AutocompleteOptions) 75 | 76 | - `options.types`: By default it uses (cities). 77 | - `options.fields`: By default it uses `address_components`, `geometry.location`, `place_id`, `formatted_address`. 78 | 79 | - `inputAutocompleteValue`: Autocomplete value to be set to the underlying input. 80 | 81 | - `googleMapsScriptBaseUrl`: Provide custom google maps url. By default `https://maps.googleapis.com/maps/api/js` 82 | 83 | - `defaultValue` prop is used for setting up the default value e.g `defaultValue={'Amsterdam'}`. 84 | 85 | - `language`: Set [language](https://developers.google.com/maps/documentation/places/web-service/details#PlaceDetailsRequests) to be used for the results. If not specified, Google defaults to load the most appropriate language based on the users location or browser setting. 86 | 87 | - `libraries`: prop is used for loading additional google libraries alongside the places api, `defaultValue={["places"]}`. 88 | 89 | You can pass any prop specified for the hmtl [input tag](https://www.w3schools.com/tags/tag_input.asp). You can also set [options.fields](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlaceResult) prop if you need extra information, now it defaults to basic data in order to control expenses. 90 | 91 | ## usePlacesWidget 92 | 93 | ![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/usePlacesWidget.js?compression=brotli&label=brotli) 94 | ![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/usePlacesWidget.js?compression=gzip&label=gzip) 95 | 96 | Is a hook that has a single config argument. It has exactly the same interface as ReactGoogleAutocomplete props. This hook is actually used in the ReactGoogleAutocomplete component. 97 | 98 | ```js 99 | import { usePlacesWidget } from "react-google-autocomplete"; 100 | 101 | export default () => { 102 | const { ref, autocompleteRef } = usePlacesWidget({ 103 | apiKey:YOUR_GOOGLE_MAPS_API_KEY, 104 | onPlaceSelected: (place) => { 105 | console.log(place); 106 | } 107 | }); 108 | 109 | return 110 | } 111 | ``` 112 | 113 | ### Arguments 114 | 115 | It has only one config argument which has propperties: `apiKey`, `ref`, `onPlaceSelected`, `options`, `inputAutocompleteValue`, `googleMapsScriptBaseUrl`. The same props described [here](#reactgoogleautocomplete) 116 | 117 | ### Returned value 118 | 119 | This hook returns object with only two properties: 120 | 121 | - `ref` - is a react ref which you can assign to any input you would like. 122 | - `autocompleteRef` - is the [autocomplete](https://developers.google.com/maps/documentation/javascript/reference/places-widget#Autocomplete) instance 123 | 124 | ## usePlacesAutocompleteService 125 | 126 | ![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/usePlacesAutocompleteService.js?compression=brotli&label=brotli) 127 | ![](https://img.badgesize.io/ErrorPro/react-google-autocomplete/master/lib/usePlacesAutocompleteService.js?compression=gzip&label=gzip) 128 | 129 | This is an initial implementation of debounced google places autocomplete service. It gives you an option to reduce the amount of requests sent to google which reduce your costs. For the time being we decided to use `lodash.debounce` to save time and in the later versions we might write our own implementation of debounce with hooks. Because it uses lodash we also decided to not put it into the index library file so it lives in its own file and could be only imported by it. 130 | 131 | ```js 132 | import usePlacesService from "react-google-autocomplete/lib/usePlacesAutocompleteService"; 133 | 134 | export default () => { 135 | const { 136 | placesService, 137 | placePredictions, 138 | getPlacePredictions, 139 | isPlacePredictionsLoading, 140 | } = usePlacesService({ 141 | apiKey: process.env.REACT_APP_GOOGLE, 142 | }); 143 | 144 | useEffect(() => { 145 | // fetch place details for the first element in placePredictions array 146 | if (placePredictions.length) 147 | placesService?.getDetails( 148 | { 149 | placeId: placePredictions[0].place_id, 150 | }, 151 | (placeDetails) => savePlaceDetailsToState(placeDetails) 152 | ); 153 | }, [placePredictions]); 154 | 155 | return ( 156 | <> 157 | { 160 | getPlacePredictions({ input: evt.target.value }); 161 | }} 162 | loading={isPlacePredictionsLoading} 163 | /> 164 | {placePredictions.map((item) => renderItem(item))} 165 | 166 | ); 167 | }; 168 | ``` 169 | 170 | [example](/docs/debounce.js) 171 | 172 | ### Arguments 173 | 174 | The hook has only one config argument. 175 | 176 | - `config`: 177 | - `apiKey`: Google api key, otherwise google api has to be loaded manually. 178 | - `googleMapsScriptBaseUrl`: Provide custom google maps url. By default `https://maps.googleapis.com/maps/api/js`. 179 | - `debounce`: Number of milliseconds to accumulate responses for. 180 | - `options`: Default [options](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#QueryAutocompletionRequest) which will be passed to every request. 181 | - `sessionToken`: If true then a [session token](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompleteSessionToken) will be attached to every request. 182 | - `language`: If the language code is set, the results will be returned in the specificed [language](https://developers.google.com/maps/documentation/places/web-service/details#PlaceDetailsRequests) 183 | - `libraries`: prop is used for loading additional google libraries alongside the places api, `defaultValue={["places"]}`. 184 | 185 | ### Returned value 186 | 187 | The hook returns an object with properties: 188 | 189 | - `placesAutocompleteService`: Instance of [AutocompleteService](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompleteService) 190 | - `placesService`: Instance of [PlacesService](https://developers.google.com/maps/documentation/javascript/reference/places-service#PlacesService) 191 | - `autocompleteSessionToken`: Instance of [AutocompleteSessionToken](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompleteSessionToken). You can use this to [group several requests into a single session](https://developers.google.com/maps/documentation/places/web-service/session-tokens) 192 | - `refreshSessionToken`: call this function if you need [to refresh the session token](https://developers.google.com/maps/documentation/places/web-service/session-tokens) 193 | - `placePredictions`: an array of [AutocompletePrediction](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompleteResponse) 194 | - `isPlacePredictionsLoading`: sets to true when a `getPlacePredictions` request is being sent and not yet resolved. 195 | - `getPlacePredictions: (opt: `[Options](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompletionRequest)`): void`: a function which you call whenever you want to request places predictions. Takes one [argument](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#AutocompleteResponse). 196 | - `queryPredictions`: an array of [QueryAutocompletePrediction](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#QueryAutocompletePrediction) 197 | - `isQueryPredictionsLoading`: sets to true when `getQueryPredictions` request is being sent and not yet resolved. 198 | - `getQueryPredictions: (opt: `[Options](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#QueryAutocompletionRequest)`): void`: a function which you call whenever you want to request query predictions. Takes one [argument](https://developers.google.com/maps/documentation/javascript/reference/places-autocomplete-service#QueryAutocompletionRequest). 199 | 200 | ## Examples 201 | 202 | ### Simple autocomplete with options 203 | 204 | ```js 205 | import Autocomplete from "react-google-autocomplete"; 206 | 207 | { 211 | console.log(place); 212 | }} 213 | options={{ 214 | types: ["(regions)"], 215 | componentRestrictions: { country: "ru" }, 216 | }} 217 | defaultValue="Amsterdam" 218 | />; 219 | ``` 220 | 221 | or 222 | 223 | ```js 224 | import { usePlacesWidget } from "react-google-autocomplete"; 225 | 226 | export default () => { 227 | const { ref } = usePlacesWidget({ 228 | apiKey: YOUR_GOOGLE_MAPS_API_KEY, 229 | onPlaceSelected: (place) => { 230 | console.log(place); 231 | }, 232 | options: { 233 | types: ["(regions)"], 234 | componentRestrictions: { country: "ru" }, 235 | }, 236 | }); 237 | 238 | return ; 239 | }; 240 | ``` 241 | 242 | ### Getting access to the google autocomplete instance 243 | 244 | ```js 245 | { 247 | console.log(autocomplete); 248 | }} 249 | /> 250 | ``` 251 | 252 | or 253 | 254 | ```js 255 | const { ref, autocompleteRef } = usePlacesWidget({ 256 | apiKey: YOUR_GOOGLE_MAPS_API_KEY, 257 | onPlaceSelected: (place) => { 258 | console.log(place); 259 | }, 260 | }); 261 | ``` 262 | 263 | ### More examples(dynamic props, MaterialUI, Ant, Bootstrap) could be found in [docs/examples.js](/docs/examples.js) 264 | 265 | Formik example lives [here](/docs/formik.js) 266 | 267 | Debounce example lives [here](/docs/debounce.js) 268 | 269 | ### Typescript 270 | 271 | We are planning on rewriting the library with TS/Flow in the later releases but you can already use it with TypeScript because we use [declaration files](https://www.typescriptlang.org/docs/handbook/declaration-files/dts-from-js.html). 272 | 273 | ### TODO 274 | 275 | - ~~Check that it fully works with SSR~~ Fully works with SSR: tested with: Next.js, Gatsby.js and custom SSR based on Express.js. 276 | - Add more UI libraries examples/supports 277 | - Add eslint config(base-airbnb) 278 | - Rewrite the lib to TS and add flow support 279 | - Remove lodash and use own built-in solution for debouncing 280 | 281 | ### Troubleshooting 282 | 283 | - You have included the Google Maps JavaScript API multiple times on this page. [Solution](https://github.com/ErrorPro/react-google-autocomplete/issues/89#issuecomment-846583668) 284 | 285 | ## Contribution 286 | 287 | If you would like to see something in this library please create an issue and I will implement it as soon as possible. 288 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | These versions are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | >=2.1.x | :white_check_mark: | 11 | 12 | ## Reporting a Vulnerability 13 | 14 | Open an issue or a PR with your questions/suggestion. 15 | -------------------------------------------------------------------------------- /docs/debounce.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Input, List } from "antd"; 3 | 4 | import useGoogle from "react-google-autocomplete/lib/usePlacesAutocompleteService"; 5 | 6 | export const Debounce = ({ a }) => { 7 | const { 8 | placePredictions, 9 | getPlacePredictions, 10 | isPlacePredictionsLoading, 11 | } = useGoogle({ 12 | apiKey: process.env.REACT_APP_GOOGLE, 13 | }); 14 | const [value, setValue] = useState(""); 15 | 16 | return ( 17 |
18 | Debounced 19 | { 24 | getPlacePredictions({ input: evt.target.value }); 25 | setValue(evt.target.value); 26 | }} 27 | loading={isPlacePredictionsLoading} 28 | /> 29 |
40 | {!isPlacePredictionsLoading && ( 41 | ( 44 | setValue(item.description)}> 45 | 46 | 47 | )} 48 | /> 49 | )} 50 |
51 |
52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /docs/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErrorPro/react-google-autocomplete/f87aec99cbdce5eee1de7826c9e7e6a7a0d293cc/docs/example.gif -------------------------------------------------------------------------------- /docs/examples.js: -------------------------------------------------------------------------------- 1 | import React, { FC, RefObject, useRef, useState } from "react"; 2 | import "bootstrap/dist/css/bootstrap.min.css"; 3 | import "./App.css"; 4 | import { Input as AntInput } from "antd"; 5 | import "antd/dist/antd.css"; 6 | import { Input, TextField } from "@material-ui/core"; 7 | import Form from "react-bootstrap/Form"; 8 | import Autocomplete, { usePlacesWidget } from "react-google-autocomplete"; 9 | 10 | function App() { 11 | const inputRef = useRef(null); 12 | const antInputRef = useRef(null); 13 | const [country, setCountry] = useState("us"); 14 | const { ref: materialRef } = usePlacesWidget({ 15 | apiKey: process.env.REACT_APP_GOOGLE, 16 | onPlaceSelected: (place) => console.log(place), 17 | inputAutocompleteValue: "country", 18 | options: { 19 | componentRestrictions: { country }, 20 | }, 21 | }); 22 | const { ref: bootstrapRef } = usePlacesWidget({ 23 | apiKey: process.env.REACT_APP_GOOGLE, 24 | onPlaceSelected: (place) => console.log(place), 25 | }); 26 | const { ref: antRef } = usePlacesWidget({ 27 | apiKey: process.env.REACT_APP_GOOGLE, 28 | onPlaceSelected: (place) => { 29 | //@ts-ignore 30 | antInputRef.current.setValue(place?.formatted_address); 31 | }, 32 | }); 33 | 34 | return ( 35 |
36 |
37 | Standard HTML 38 | { 43 | console.log(selected); 44 | }} 45 | options={{ 46 | types: ["geocode", "establishment"], 47 | componentRestrictions: { country }, 48 | }} 49 | defaultValue="Moscow" 50 | /> 51 | 64 |
65 | Material UI 66 | ( 70 | console.log(selected)} 74 | /> 75 | )} 76 | /> 77 |
78 |
79 | Material UI 80 | 86 |
87 |
88 |
89 | 90 | Bootstrap 91 | 92 | 93 |
94 |
95 |
96 | Ant Design 97 | { 99 | antInputRef.current = c; 100 | if (c) antRef.current = c.input; 101 | }} 102 | size="large" 103 | /> 104 |
105 |
106 |
107 | ); 108 | } 109 | 110 | export default App; 111 | -------------------------------------------------------------------------------- /docs/formik.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useFormik } from "formik"; 3 | import { TextField } from "@material-ui/core"; 4 | 5 | import Autocomplete, { usePlacesWidget } from "react-google-autocomplete"; 6 | 7 | export default function Formik() { 8 | const formik = useFormik({ 9 | initialValues: { 10 | country: "", 11 | countryAnother: "", 12 | }, 13 | onSubmit: (values) => { 14 | alert(JSON.stringify(values, null, 2)); 15 | }, 16 | }); 17 | 18 | const { ref } = usePlacesWidget({ 19 | apiKey: process.env.REACT_APP_GOOGLE, 20 | onPlaceSelected: (place) => { 21 | formik.setFieldValue("country", place.formatted_address); 22 | }, 23 | }); 24 | 25 | return ( 26 |
27 | Formik state: {JSON.stringify(formik.values)} 28 |
32 | 44 | 51 | formik.setFieldValue("countryAnother", place.formatted_address) 52 | } 53 | onChange={formik.handleChange} 54 | /> 55 | 56 | 57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { RefObject } from "react"; 2 | 3 | export interface ReactGoogleAutocompleteProps { 4 | onPlaceSelected?: ( 5 | places: google.maps.places.PlaceResult, 6 | ref: RefObject, 7 | autocompleteRef: RefObject 8 | ) => void; 9 | inputAutocompleteValue?: string; 10 | options?: google.maps.places.AutocompleteOptions; 11 | libraries?: string[]; 12 | apiKey?: string; 13 | language?: string; 14 | googleMapsScriptBaseUrl?: string; 15 | } 16 | 17 | export interface ReactGoogleAutocompleteInputProps 18 | extends ReactGoogleAutocompleteProps, 19 | React.DetailedHTMLProps< 20 | React.HTMLAttributes, 21 | HTMLInputElement 22 | > {} 23 | 24 | export default function ReactGoogleAutocomplete( 25 | props: ReactGoogleAutocompleteInputProps & T 26 | ): JSX.Element; 27 | 28 | export function usePlacesWidget( 29 | props: ReactGoogleAutocompleteProps 30 | ): { 31 | ref: RefObject; 32 | autocompleteRef: RefObject; 33 | }; 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib'); 4 | -------------------------------------------------------------------------------- /lib/ReactGoogleAutocomplete.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports.default = void 0; 9 | 10 | var _react = _interopRequireWildcard(require("react")); 11 | 12 | var _propTypes = _interopRequireDefault(require("prop-types")); 13 | 14 | var _usePlacesWidget2 = _interopRequireDefault(require("./usePlacesWidget")); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } 19 | 20 | function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 21 | 22 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } 23 | 24 | function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 25 | 26 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 27 | 28 | function ReactGoogleAutocomplete(props) { 29 | var onPlaceSelected = props.onPlaceSelected, 30 | apiKey = props.apiKey, 31 | libraries = props.libraries, 32 | inputAutocompleteValue = props.inputAutocompleteValue, 33 | options = props.options, 34 | googleMapsScriptBaseUrl = props.googleMapsScriptBaseUrl, 35 | refProp = props.refProp, 36 | language = props.language, 37 | rest = _objectWithoutProperties(props, ["onPlaceSelected", "apiKey", "libraries", "inputAutocompleteValue", "options", "googleMapsScriptBaseUrl", "refProp", "language"]); 38 | 39 | var _usePlacesWidget = (0, _usePlacesWidget2.default)({ 40 | ref: refProp, 41 | googleMapsScriptBaseUrl: googleMapsScriptBaseUrl, 42 | onPlaceSelected: onPlaceSelected, 43 | apiKey: apiKey, 44 | libraries: libraries, 45 | inputAutocompleteValue: inputAutocompleteValue, 46 | options: options, 47 | language: language 48 | }), 49 | ref = _usePlacesWidget.ref; 50 | 51 | return /*#__PURE__*/_react.default.createElement("input", _extends({ 52 | ref: ref 53 | }, rest)); 54 | } 55 | 56 | ReactGoogleAutocomplete.propTypes = { 57 | apiKey: _propTypes.default.string, 58 | libraries: _propTypes.default.arrayOf(_propTypes.default.string), 59 | ref: _propTypes.default.oneOfType([// Either a function 60 | _propTypes.default.func, // Or anything shaped { current: any } 61 | _propTypes.default.shape({ 62 | current: _propTypes.default.any 63 | })]), 64 | googleMapsScriptBaseUrl: _propTypes.default.string, 65 | onPlaceSelected: _propTypes.default.func, 66 | inputAutocompleteValue: _propTypes.default.string, 67 | options: _propTypes.default.shape({ 68 | componentRestrictions: _propTypes.default.object, 69 | bounds: _propTypes.default.object, 70 | location: _propTypes.default.object, 71 | offset: _propTypes.default.number, 72 | origin: _propTypes.default.object, 73 | radius: _propTypes.default.number, 74 | sessionToken: _propTypes.default.object, 75 | types: _propTypes.default.arrayOf(_propTypes.default.string) 76 | }), 77 | language: _propTypes.default.string 78 | }; 79 | 80 | var _default = /*#__PURE__*/(0, _react.forwardRef)(function (props, ref) { 81 | return /*#__PURE__*/_react.default.createElement(ReactGoogleAutocomplete, _extends({}, props, { 82 | refProp: ref 83 | })); 84 | }); 85 | 86 | exports.default = _default; -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.GOOGLE_MAP_SCRIPT_BASE_URL = void 0; 7 | var GOOGLE_MAP_SCRIPT_BASE_URL = "https://maps.googleapis.com/maps/api/js"; 8 | exports.GOOGLE_MAP_SCRIPT_BASE_URL = GOOGLE_MAP_SCRIPT_BASE_URL; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "default", { 7 | enumerable: true, 8 | get: function get() { 9 | return _ReactGoogleAutocomplete.default; 10 | } 11 | }); 12 | Object.defineProperty(exports, "usePlacesWidget", { 13 | enumerable: true, 14 | get: function get() { 15 | return _usePlacesWidget.default; 16 | } 17 | }); 18 | 19 | var _ReactGoogleAutocomplete = _interopRequireDefault(require("./ReactGoogleAutocomplete")); 20 | 21 | var _usePlacesWidget = _interopRequireDefault(require("./usePlacesWidget")); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -------------------------------------------------------------------------------- /lib/usePlacesAutocompleteService.d.ts: -------------------------------------------------------------------------------- 1 | interface usePlacesAutocompleteServiceConfig { 2 | apiKey?: string; 3 | libraries?: string[]; 4 | googleMapsScriptBaseUrl?: string; 5 | debounce?: number; 6 | options?: google.maps.places.AutocompletionRequest; 7 | sessionToken?: boolean; 8 | language?: string; 9 | } 10 | 11 | interface usePlacesAutocompleteServiceResponse { 12 | placesService: google.maps.places.PlacesService | null; 13 | autocompleteSessionToken: 14 | | google.maps.places.AutocompleteSessionToken 15 | | undefined; 16 | placesAutocompleteService: google.maps.places.AutocompleteService | null; 17 | placePredictions: google.maps.places.AutocompletePrediction[]; 18 | isPlacePredictionsLoading: boolean; 19 | getPlacePredictions: (opt: google.maps.places.AutocompletionRequest) => void; 20 | queryPredictions: google.maps.places.QueryAutocompletePrediction[]; 21 | isQueryPredictionsLoading: boolean; 22 | getQueryPredictions: ( 23 | opt: google.maps.places.QueryAutocompletionRequest 24 | ) => void; 25 | refreshSessionToken: () => void; 26 | } 27 | 28 | export default function usePlacesAutocompleteService( 29 | options: usePlacesAutocompleteServiceConfig 30 | ): usePlacesAutocompleteServiceResponse; 31 | -------------------------------------------------------------------------------- /lib/usePlacesAutocompleteService.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = usePlacesAutocompleteService; 7 | 8 | var _react = require("react"); 9 | 10 | var _lodash = _interopRequireDefault(require("lodash.debounce")); 11 | 12 | var _utils = require("./utils"); 13 | 14 | var _constants = require("./constants"); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } 19 | 20 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 21 | 22 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 23 | 24 | function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } 25 | 26 | function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } 27 | 28 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 29 | 30 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 31 | 32 | function _iterableToArrayLimit(arr, i) { var _i = arr && (typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]); if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } 33 | 34 | function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } 35 | 36 | function usePlacesAutocompleteService(_ref) { 37 | var apiKey = _ref.apiKey, 38 | _ref$libraries = _ref.libraries, 39 | libraries = _ref$libraries === void 0 ? "places" : _ref$libraries, 40 | _ref$googleMapsScript = _ref.googleMapsScriptBaseUrl, 41 | googleMapsScriptBaseUrl = _ref$googleMapsScript === void 0 ? _constants.GOOGLE_MAP_SCRIPT_BASE_URL : _ref$googleMapsScript, 42 | _ref$debounce = _ref.debounce, 43 | debounce = _ref$debounce === void 0 ? 300 : _ref$debounce, 44 | _ref$options = _ref.options, 45 | options = _ref$options === void 0 ? {} : _ref$options, 46 | sessionToken = _ref.sessionToken, 47 | language = _ref.language; 48 | var languageQueryParam = language ? "&language=".concat(language) : ""; 49 | var googleMapsScriptUrl = "".concat(googleMapsScriptBaseUrl, "?key=").concat(apiKey, "&libraries=").concat(libraries).concat(languageQueryParam); 50 | 51 | var _useState = (0, _react.useState)([]), 52 | _useState2 = _slicedToArray(_useState, 2), 53 | placePredictions = _useState2[0], 54 | setPlacePredictions = _useState2[1]; 55 | 56 | var _useState3 = (0, _react.useState)(false), 57 | _useState4 = _slicedToArray(_useState3, 2), 58 | isPlacePredsLoading = _useState4[0], 59 | setIsPlacePredsLoading = _useState4[1]; 60 | 61 | var _useState5 = (0, _react.useState)(null), 62 | _useState6 = _slicedToArray(_useState5, 2), 63 | placeInputValue = _useState6[0], 64 | setPlaceInputValue = _useState6[1]; 65 | 66 | var _useState7 = (0, _react.useState)(false), 67 | _useState8 = _slicedToArray(_useState7, 2), 68 | isQueryPredsLoading = _useState8[0], 69 | setIsQueryPredsLoading = _useState8[1]; 70 | 71 | var _useState9 = (0, _react.useState)(false), 72 | _useState10 = _slicedToArray(_useState9, 2), 73 | queryInputValue = _useState10[0], 74 | setQueryInputValue = _useState10[1]; 75 | 76 | var _useState11 = (0, _react.useState)([]), 77 | _useState12 = _slicedToArray(_useState11, 2), 78 | queryPredictions = _useState12[0], 79 | setQueryPredictions = _useState12[1]; 80 | 81 | var placesAutocompleteService = (0, _react.useRef)(null); 82 | var placesService = (0, _react.useRef)(null); 83 | var autocompleteSession = (0, _react.useRef)(null); 84 | var handleLoadScript = (0, _react.useCallback)(function () { 85 | return (0, _utils.loadGoogleMapScript)(googleMapsScriptBaseUrl, googleMapsScriptUrl); 86 | }, [googleMapsScriptBaseUrl, googleMapsScriptUrl]); 87 | var debouncedPlacePredictions = (0, _react.useCallback)((0, _lodash.default)(function (optionsArg) { 88 | if (placesAutocompleteService.current && optionsArg.input) placesAutocompleteService.current.getPlacePredictions(_objectSpread(_objectSpread(_objectSpread({}, sessionToken && autocompleteSession.current ? { 89 | sessionToken: autocompleteSession.current 90 | } : {}), options), optionsArg), function (r) { 91 | setIsPlacePredsLoading(false); 92 | setPlacePredictions(r || []); 93 | }); 94 | }, debounce), [debounce]); 95 | var debouncedQueryPredictions = (0, _react.useCallback)((0, _lodash.default)(function (optionsArg) { 96 | if (placesAutocompleteService.current && optionsArg.input) placesAutocompleteService.current.getQueryPredictions(_objectSpread(_objectSpread(_objectSpread({}, sessionToken && autocompleteSession.current ? { 97 | sessionToken: autocompleteSession.current 98 | } : {}), options), optionsArg), function (r) { 99 | setIsQueryPredsLoading(false); 100 | setQueryPredictions(r || []); 101 | }); 102 | }, debounce), [debounce]); 103 | (0, _react.useEffect)(function () { 104 | if (!_utils.isBrowser) return; 105 | 106 | var buildService = function buildService() { 107 | // eslint-disable-next-line no-undef 108 | if (!google) return console.error("Google has not been found. Make sure your provide apiKey prop."); // eslint-disable-next-line no-undef 109 | 110 | placesAutocompleteService.current = new google.maps.places.AutocompleteService(); // eslint-disable-next-line no-undef 111 | 112 | placesService.current = new google.maps.places.PlacesService(document.createElement("div")); 113 | if (sessionToken) autocompleteSession.current = new google.maps.places.AutocompleteSessionToken(); 114 | }; 115 | 116 | if (apiKey) { 117 | handleLoadScript().then(function () { 118 | return buildService(); 119 | }); 120 | } else { 121 | buildService(); 122 | } 123 | }, []); 124 | return { 125 | placesService: placesService.current, 126 | autocompleteSessionToken: autocompleteSession.current, 127 | placesAutocompleteService: placesAutocompleteService.current, 128 | placePredictions: placeInputValue ? placePredictions : [], 129 | isPlacePredictionsLoading: isPlacePredsLoading, 130 | getPlacePredictions: function getPlacePredictions(opt) { 131 | if (opt.input) { 132 | setPlaceInputValue(opt.input); 133 | setIsPlacePredsLoading(true); 134 | debouncedPlacePredictions(opt); 135 | return; 136 | } 137 | 138 | setPlacePredictions([]); 139 | setPlaceInputValue(null); 140 | debouncedPlacePredictions(opt); 141 | setIsPlacePredsLoading(false); 142 | }, 143 | queryPredictions: queryInputValue ? queryPredictions : [], 144 | isQueryPredictionsLoading: isQueryPredsLoading, 145 | getQueryPredictions: function getQueryPredictions(opt) { 146 | if (opt.input) { 147 | setQueryInputValue(opt.input); 148 | setIsQueryPredsLoading(true); 149 | debouncedQueryPredictions(opt); 150 | return; 151 | } 152 | 153 | setQueryPredictions([]); 154 | setQueryInputValue(null); 155 | debouncedQueryPredictions(opt); 156 | setIsQueryPredsLoading(false); 157 | }, 158 | refreshSessionToken: function refreshSessionToken() { 159 | autocompleteSession.current = new google.maps.places.AutocompleteSessionToken(); 160 | } 161 | }; 162 | } -------------------------------------------------------------------------------- /lib/usePlacesService.js: -------------------------------------------------------------------------------- 1 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var _extends=Object.assign||function(a){for(var b,c=1;c= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } 27 | 28 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 29 | 30 | function usePlacesWidget(props) { 31 | var ref = props.ref, 32 | onPlaceSelected = props.onPlaceSelected, 33 | apiKey = props.apiKey, 34 | _props$libraries = props.libraries, 35 | libraries = _props$libraries === void 0 ? "places" : _props$libraries, 36 | _props$inputAutocompl = props.inputAutocompleteValue, 37 | inputAutocompleteValue = _props$inputAutocompl === void 0 ? "new-password" : _props$inputAutocompl, 38 | _props$options = props.options; 39 | _props$options = _props$options === void 0 ? {} : _props$options; 40 | 41 | var _props$options$types = _props$options.types, 42 | types = _props$options$types === void 0 ? ["(cities)"] : _props$options$types, 43 | componentRestrictions = _props$options.componentRestrictions, 44 | _props$options$fields = _props$options.fields, 45 | fields = _props$options$fields === void 0 ? ["address_components", "geometry.location", "place_id", "formatted_address"] : _props$options$fields, 46 | bounds = _props$options.bounds, 47 | options = _objectWithoutProperties(_props$options, ["types", "componentRestrictions", "fields", "bounds"]), 48 | _props$googleMapsScri = props.googleMapsScriptBaseUrl, 49 | googleMapsScriptBaseUrl = _props$googleMapsScri === void 0 ? _constants.GOOGLE_MAP_SCRIPT_BASE_URL : _props$googleMapsScri, 50 | language = props.language; 51 | 52 | var inputRef = (0, _react.useRef)(null); 53 | var event = (0, _react.useRef)(null); 54 | var autocompleteRef = (0, _react.useRef)(null); 55 | var languageQueryParam = language ? "&language=".concat(language) : ""; 56 | var googleMapsScriptUrl = "".concat(googleMapsScriptBaseUrl, "?libraries=").concat(libraries, "&key=").concat(apiKey).concat(languageQueryParam); 57 | var handleLoadScript = (0, _react.useCallback)(function () { 58 | return (0, _utils.loadGoogleMapScript)(googleMapsScriptBaseUrl, googleMapsScriptUrl); 59 | }, [googleMapsScriptBaseUrl, googleMapsScriptUrl]); 60 | (0, _react.useEffect)(function () { 61 | var config = _objectSpread(_objectSpread({}, options), {}, { 62 | fields: fields, 63 | types: types, 64 | bounds: bounds 65 | }); 66 | 67 | if (componentRestrictions) { 68 | config.componentRestrictions = componentRestrictions; 69 | } 70 | 71 | if (autocompleteRef.current || !inputRef.current || !_utils.isBrowser) return; 72 | if (ref && !ref.current) ref.current = inputRef.current; 73 | 74 | var handleAutoComplete = function handleAutoComplete() { 75 | var _google$maps; 76 | 77 | if (typeof google === "undefined") return console.error("Google has not been found. Make sure your provide apiKey prop."); 78 | if (!((_google$maps = google.maps) !== null && _google$maps !== void 0 && _google$maps.places)) return console.error("Google maps places API must be loaded."); 79 | if (!(inputRef.current instanceof HTMLInputElement)) return console.error("Input ref must be HTMLInputElement."); 80 | autocompleteRef.current = new google.maps.places.Autocomplete(inputRef.current, config); 81 | 82 | if (autocompleteRef.current) { 83 | event.current = autocompleteRef.current.addListener("place_changed", function () { 84 | if (onPlaceSelected && autocompleteRef && autocompleteRef.current) { 85 | onPlaceSelected(autocompleteRef.current.getPlace(), inputRef.current, autocompleteRef.current); 86 | } 87 | }); 88 | } 89 | }; 90 | 91 | if (apiKey) { 92 | handleLoadScript().then(function () { 93 | return handleAutoComplete(); 94 | }); 95 | } else { 96 | handleAutoComplete(); 97 | } 98 | 99 | return function () { 100 | return event.current ? event.current.remove() : undefined; 101 | }; 102 | }, []); 103 | (0, _react.useEffect)(function () { 104 | if (autocompleteRef.current && onPlaceSelected) { 105 | event.current = autocompleteRef.current.addListener("place_changed", function () { 106 | if (onPlaceSelected && autocompleteRef && autocompleteRef.current) { 107 | onPlaceSelected(autocompleteRef.current.getPlace(), inputRef.current, autocompleteRef.current); 108 | } 109 | }); 110 | } 111 | 112 | return function () { 113 | return event.current ? event.current.remove() : undefined; 114 | }; 115 | }, [onPlaceSelected]); // Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445 116 | 117 | (0, _react.useEffect)(function () { 118 | // TODO find out why react 18(strict mode) hangs the page loading 119 | if (_utils.isBrowser && window.MutationObserver && inputRef.current && inputRef.current instanceof HTMLInputElement) { 120 | var observerHack = new MutationObserver(function () { 121 | observerHack.disconnect(); 122 | 123 | if (inputRef.current) { 124 | inputRef.current.autocomplete = inputAutocompleteValue; 125 | } 126 | }); 127 | observerHack.observe(inputRef.current, { 128 | attributes: true, 129 | attributeFilter: ["autocomplete"] 130 | }); 131 | return function () { 132 | observerHack.disconnect(); // Cleanup 133 | }; 134 | } 135 | }, [inputAutocompleteValue]); 136 | (0, _react.useEffect)(function () { 137 | if (autocompleteRef.current) { 138 | autocompleteRef.current.setFields(fields); 139 | } 140 | }, [fields]); 141 | (0, _react.useEffect)(function () { 142 | if (autocompleteRef.current) { 143 | autocompleteRef.current.setBounds(bounds); 144 | } 145 | }, [bounds]); 146 | (0, _react.useEffect)(function () { 147 | if (autocompleteRef.current) { 148 | autocompleteRef.current.setComponentRestrictions(componentRestrictions); 149 | } 150 | }, [componentRestrictions]); 151 | (0, _react.useEffect)(function () { 152 | if (autocompleteRef.current) { 153 | autocompleteRef.current.setOptions(options); 154 | } 155 | }, [options]); 156 | return { 157 | ref: inputRef, 158 | autocompleteRef: autocompleteRef 159 | }; 160 | } -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.loadGoogleMapScript = exports.isBrowser = void 0; 7 | var isBrowser = typeof window !== "undefined" && window.document; 8 | exports.isBrowser = isBrowser; 9 | 10 | var loadGoogleMapScript = function loadGoogleMapScript(googleMapsScriptBaseUrl, googleMapsScriptUrl) { 11 | if (!isBrowser) return Promise.resolve(); 12 | 13 | if (typeof google !== "undefined") { 14 | if (google.maps && google.maps.api) return Promise.resolve(); 15 | } 16 | 17 | var scriptElements = document.querySelectorAll("script[src*=\"".concat(googleMapsScriptBaseUrl, "\"]")); 18 | 19 | if (scriptElements && scriptElements.length) { 20 | return new Promise(function (resolve) { 21 | // in case we already have a script on the page and it's loaded we resolve 22 | if (typeof google !== "undefined") return resolve(); // otherwise we wait until it's loaded and resolve 23 | 24 | scriptElements[0].addEventListener("load", function () { 25 | return resolve(); 26 | }); 27 | }); 28 | } 29 | 30 | var scriptUrl = new URL(googleMapsScriptUrl); 31 | scriptUrl.searchParams.set("callback", "__REACT_GOOGLE_AUTOCOMPLETE_CALLBACK__"); 32 | var el = document.createElement("script"); 33 | el.src = scriptUrl.toString(); 34 | return new Promise(function (resolve) { 35 | window.__REACT_GOOGLE_AUTOCOMPLETE_CALLBACK__ = resolve; 36 | document.body.appendChild(el); 37 | }); 38 | }; 39 | 40 | exports.loadGoogleMapScript = loadGoogleMapScript; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-google-autocomplete", 3 | "version": "2.7.5", 4 | "description": "React component for google autocomplete.", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "build": "babel src/ --out-dir lib" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/ErrorPro/react-google-autocomplete.git" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "google", 18 | "autocomplete", 19 | "react google autocomplete", 20 | "react google", 21 | "react autocomplete", 22 | "google autocomplete", 23 | "react google places autocomplete", 24 | "google places autocomplete", 25 | "react google places autocomplete service", 26 | "google places autocomplete service", 27 | "google places service", 28 | "react google places service", 29 | "google places widget", 30 | "react google places widget", 31 | "google places api", 32 | "react google", 33 | "react places api", 34 | "google api" 35 | ], 36 | "author": "Ven Korolev", 37 | "license": "ISC", 38 | "bugs": { 39 | "url": "https://github.com/ErrorPro/react-google-autocomplete/issues" 40 | }, 41 | "homepage": "https://github.com/ErrorPro/react-google-autocomplete#readme", 42 | "dependencies": { 43 | "lodash.debounce": "^4.0.8", 44 | "prop-types": "^15.5.0" 45 | }, 46 | "peerDependencies": { 47 | "react": ">=16.8.0" 48 | }, 49 | "devDependencies": { 50 | "@babel/cli": "^7.13.16", 51 | "@babel/core": "^7.14.0", 52 | "@babel/preset-env": "^7.14.0", 53 | "@babel/preset-react": "^7.13.13", 54 | "@types/google.maps": "^3.44.3", 55 | "babel-preset-minify": "^0.5.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ReactGoogleAutocomplete.js: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import usePlacesWidget from "./usePlacesWidget"; 5 | 6 | function ReactGoogleAutocomplete(props) { 7 | const { 8 | onPlaceSelected, 9 | apiKey, 10 | libraries, 11 | inputAutocompleteValue, 12 | options, 13 | googleMapsScriptBaseUrl, 14 | refProp, 15 | language, 16 | ...rest 17 | } = props; 18 | 19 | const { ref } = usePlacesWidget({ 20 | ref: refProp, 21 | googleMapsScriptBaseUrl, 22 | onPlaceSelected, 23 | apiKey, 24 | libraries, 25 | inputAutocompleteValue, 26 | options, 27 | language 28 | }); 29 | 30 | return ; 31 | } 32 | 33 | ReactGoogleAutocomplete.propTypes = { 34 | apiKey: PropTypes.string, 35 | libraries: PropTypes.arrayOf(PropTypes.string), 36 | ref: PropTypes.oneOfType([ 37 | // Either a function 38 | PropTypes.func, 39 | // Or anything shaped { current: any } 40 | PropTypes.shape({ current: PropTypes.any }), 41 | ]), 42 | googleMapsScriptBaseUrl: PropTypes.string, 43 | onPlaceSelected: PropTypes.func, 44 | inputAutocompleteValue: PropTypes.string, 45 | options: PropTypes.shape({ 46 | componentRestrictions: PropTypes.object, 47 | bounds: PropTypes.object, 48 | location: PropTypes.object, 49 | offset: PropTypes.number, 50 | origin: PropTypes.object, 51 | radius: PropTypes.number, 52 | sessionToken: PropTypes.object, 53 | types: PropTypes.arrayOf(PropTypes.string), 54 | }), 55 | language: PropTypes.string, 56 | }; 57 | 58 | export default forwardRef((props, ref) => ( 59 | 60 | )); 61 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const GOOGLE_MAP_SCRIPT_BASE_URL = 2 | "https://maps.googleapis.com/maps/api/js"; 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default } from "./ReactGoogleAutocomplete"; 2 | export { default as usePlacesWidget } from "./usePlacesWidget"; 3 | -------------------------------------------------------------------------------- /src/usePlacesAutocompleteService.d.ts: -------------------------------------------------------------------------------- 1 | interface usePlacesAutocompleteServiceConfig { 2 | apiKey?: string; 3 | libraries?: string[]; 4 | googleMapsScriptBaseUrl?: string; 5 | debounce?: number; 6 | options?: google.maps.places.AutocompletionRequest; 7 | sessionToken?: boolean; 8 | language?: string; 9 | } 10 | 11 | interface usePlacesAutocompleteServiceResponse { 12 | placesService: google.maps.places.PlacesService | null; 13 | autocompleteSessionToken: 14 | | google.maps.places.AutocompleteSessionToken 15 | | undefined; 16 | placesAutocompleteService: google.maps.places.AutocompleteService | null; 17 | placePredictions: google.maps.places.AutocompletePrediction[]; 18 | isPlacePredictionsLoading: boolean; 19 | getPlacePredictions: (opt: google.maps.places.AutocompletionRequest) => void; 20 | queryPredictions: google.maps.places.QueryAutocompletePrediction[]; 21 | isQueryPredictionsLoading: boolean; 22 | getQueryPredictions: ( 23 | opt: google.maps.places.QueryAutocompletionRequest 24 | ) => void; 25 | refreshSessionToken: () => void; 26 | } 27 | 28 | export default function usePlacesAutocompleteService( 29 | options: usePlacesAutocompleteServiceConfig 30 | ): usePlacesAutocompleteServiceResponse; 31 | -------------------------------------------------------------------------------- /src/usePlacesAutocompleteService.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useCallback, useRef, useState } from "react"; 2 | import debounceFn from "lodash.debounce"; 3 | 4 | import { isBrowser, loadGoogleMapScript } from "./utils"; 5 | import { GOOGLE_MAP_SCRIPT_BASE_URL } from "./constants"; 6 | 7 | export default function usePlacesAutocompleteService({ 8 | apiKey, 9 | libraries = "places", 10 | googleMapsScriptBaseUrl = GOOGLE_MAP_SCRIPT_BASE_URL, 11 | debounce = 300, 12 | options = {}, 13 | sessionToken, 14 | language, 15 | }) { 16 | const languageQueryParam = language ? `&language=${language}` : ""; 17 | const googleMapsScriptUrl = `${googleMapsScriptBaseUrl}?key=${apiKey}&libraries=${libraries}${languageQueryParam}`; 18 | const [placePredictions, setPlacePredictions] = useState([]); 19 | const [isPlacePredsLoading, setIsPlacePredsLoading] = useState(false); 20 | const [placeInputValue, setPlaceInputValue] = useState(null); 21 | const [isQueryPredsLoading, setIsQueryPredsLoading] = useState(false); 22 | const [queryInputValue, setQueryInputValue] = useState(false); 23 | const [queryPredictions, setQueryPredictions] = useState([]); 24 | const placesAutocompleteService = useRef(null); 25 | const placesService = useRef(null); 26 | const autocompleteSession = useRef(null); 27 | const handleLoadScript = useCallback( 28 | () => loadGoogleMapScript(googleMapsScriptBaseUrl, googleMapsScriptUrl), 29 | [googleMapsScriptBaseUrl, googleMapsScriptUrl] 30 | ); 31 | 32 | const debouncedPlacePredictions = useCallback( 33 | debounceFn((optionsArg) => { 34 | if (placesAutocompleteService.current && optionsArg.input) 35 | placesAutocompleteService.current.getPlacePredictions( 36 | { 37 | ...(sessionToken && autocompleteSession.current 38 | ? { sessionToken: autocompleteSession.current } 39 | : {}), 40 | ...options, 41 | ...optionsArg, 42 | }, 43 | (r) => { 44 | setIsPlacePredsLoading(false); 45 | setPlacePredictions(r || []); 46 | } 47 | ); 48 | }, debounce), 49 | [debounce] 50 | ); 51 | 52 | const debouncedQueryPredictions = useCallback( 53 | debounceFn((optionsArg) => { 54 | if (placesAutocompleteService.current && optionsArg.input) 55 | placesAutocompleteService.current.getQueryPredictions( 56 | { 57 | ...(sessionToken && autocompleteSession.current 58 | ? { sessionToken: autocompleteSession.current } 59 | : {}), 60 | ...options, 61 | ...optionsArg, 62 | }, 63 | (r) => { 64 | setIsQueryPredsLoading(false); 65 | setQueryPredictions(r || []); 66 | } 67 | ); 68 | }, debounce), 69 | [debounce] 70 | ); 71 | 72 | useEffect(() => { 73 | if (!isBrowser) return; 74 | 75 | const buildService = () => { 76 | // eslint-disable-next-line no-undef 77 | if (!google) 78 | return console.error( 79 | "Google has not been found. Make sure your provide apiKey prop." 80 | ); 81 | 82 | // eslint-disable-next-line no-undef 83 | placesAutocompleteService.current = 84 | new google.maps.places.AutocompleteService(); 85 | 86 | // eslint-disable-next-line no-undef 87 | placesService.current = new google.maps.places.PlacesService( 88 | document.createElement("div") 89 | ); 90 | 91 | if (sessionToken) 92 | autocompleteSession.current = 93 | new google.maps.places.AutocompleteSessionToken(); 94 | }; 95 | 96 | if (apiKey) { 97 | handleLoadScript().then(() => buildService()); 98 | } else { 99 | buildService(); 100 | } 101 | }, []); 102 | 103 | return { 104 | placesService: placesService.current, 105 | autocompleteSessionToken: autocompleteSession.current, 106 | placesAutocompleteService: placesAutocompleteService.current, 107 | placePredictions: placeInputValue ? placePredictions : [], 108 | isPlacePredictionsLoading: isPlacePredsLoading, 109 | getPlacePredictions: (opt) => { 110 | if (opt.input) { 111 | setPlaceInputValue(opt.input); 112 | setIsPlacePredsLoading(true); 113 | debouncedPlacePredictions(opt); 114 | return; 115 | } 116 | setPlacePredictions([]); 117 | setPlaceInputValue(null); 118 | debouncedPlacePredictions(opt); 119 | setIsPlacePredsLoading(false); 120 | }, 121 | queryPredictions: queryInputValue ? queryPredictions : [], 122 | isQueryPredictionsLoading: isQueryPredsLoading, 123 | getQueryPredictions: (opt) => { 124 | if (opt.input) { 125 | setQueryInputValue(opt.input); 126 | setIsQueryPredsLoading(true); 127 | debouncedQueryPredictions(opt); 128 | return; 129 | } 130 | setQueryPredictions([]); 131 | setQueryInputValue(null); 132 | debouncedQueryPredictions(opt); 133 | setIsQueryPredsLoading(false); 134 | }, 135 | refreshSessionToken: () => { 136 | autocompleteSession.current = 137 | new google.maps.places.AutocompleteSessionToken(); 138 | }, 139 | }; 140 | } 141 | -------------------------------------------------------------------------------- /src/usePlacesWidget.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useCallback } from "react"; 2 | 3 | import { loadGoogleMapScript, isBrowser } from "./utils"; 4 | import { GOOGLE_MAP_SCRIPT_BASE_URL } from "./constants"; 5 | 6 | export default function usePlacesWidget(props) { 7 | const { 8 | ref, 9 | onPlaceSelected, 10 | apiKey, 11 | libraries = "places", 12 | inputAutocompleteValue = "new-password", 13 | options: { 14 | types = ["(cities)"], 15 | componentRestrictions, 16 | fields = [ 17 | "address_components", 18 | "geometry.location", 19 | "place_id", 20 | "formatted_address", 21 | ], 22 | bounds, 23 | ...options 24 | } = {}, 25 | googleMapsScriptBaseUrl = GOOGLE_MAP_SCRIPT_BASE_URL, 26 | language, 27 | } = props; 28 | const inputRef = useRef(null); 29 | const event = useRef(null); 30 | const autocompleteRef = useRef(null); 31 | const languageQueryParam = language ? `&language=${language}` : ""; 32 | const googleMapsScriptUrl = `${googleMapsScriptBaseUrl}?libraries=${libraries}&key=${apiKey}${languageQueryParam}`; 33 | 34 | const handleLoadScript = useCallback( 35 | () => loadGoogleMapScript(googleMapsScriptBaseUrl, googleMapsScriptUrl), 36 | [googleMapsScriptBaseUrl, googleMapsScriptUrl] 37 | ); 38 | 39 | useEffect(() => { 40 | const config = { 41 | ...options, 42 | fields, 43 | types, 44 | bounds, 45 | }; 46 | 47 | if (componentRestrictions) { 48 | config.componentRestrictions = componentRestrictions; 49 | } 50 | 51 | if (autocompleteRef.current || !inputRef.current || !isBrowser) return; 52 | 53 | if (ref && !ref.current) ref.current = inputRef.current; 54 | 55 | const handleAutoComplete = () => { 56 | if (typeof google === "undefined") 57 | return console.error( 58 | "Google has not been found. Make sure your provide apiKey prop." 59 | ); 60 | 61 | if (!google.maps?.places) 62 | return console.error("Google maps places API must be loaded."); 63 | 64 | if (!(inputRef.current instanceof HTMLInputElement)) 65 | return console.error("Input ref must be HTMLInputElement."); 66 | 67 | autocompleteRef.current = new google.maps.places.Autocomplete( 68 | inputRef.current, 69 | config 70 | ); 71 | 72 | if (autocompleteRef.current) { 73 | event.current = autocompleteRef.current.addListener( 74 | "place_changed", 75 | () => { 76 | if (onPlaceSelected && autocompleteRef && autocompleteRef.current) { 77 | onPlaceSelected( 78 | autocompleteRef.current.getPlace(), 79 | inputRef.current, 80 | autocompleteRef.current 81 | ); 82 | } 83 | } 84 | ); 85 | } 86 | }; 87 | 88 | if (apiKey) { 89 | handleLoadScript().then(() => handleAutoComplete()); 90 | } else { 91 | handleAutoComplete(); 92 | } 93 | 94 | return () => (event.current ? event.current.remove() : undefined); 95 | }, []); 96 | 97 | useEffect(() => { 98 | if (autocompleteRef.current && onPlaceSelected) { 99 | event.current = autocompleteRef.current.addListener("place_changed", function () { 100 | if (onPlaceSelected && autocompleteRef && autocompleteRef.current) { 101 | onPlaceSelected(autocompleteRef.current.getPlace(), inputRef.current, autocompleteRef.current); 102 | } 103 | }); 104 | } 105 | return () => (event.current ? event.current.remove() : undefined); 106 | }, [onPlaceSelected]); 107 | 108 | // Autofill workaround adapted from https://stackoverflow.com/questions/29931712/chrome-autofill-covers-autocomplete-for-google-maps-api-v3/49161445#49161445 109 | useEffect(() => { 110 | // TODO find out why react 18(strict mode) hangs the page loading 111 | if ( 112 | isBrowser && 113 | window.MutationObserver && 114 | inputRef.current && 115 | inputRef.current instanceof HTMLInputElement 116 | ) { 117 | const observerHack = new MutationObserver(() => { 118 | observerHack.disconnect(); 119 | 120 | if (inputRef.current) { 121 | inputRef.current.autocomplete = inputAutocompleteValue; 122 | } 123 | }); 124 | observerHack.observe(inputRef.current, { 125 | attributes: true, 126 | attributeFilter: ["autocomplete"], 127 | }); 128 | 129 | return () => { 130 | observerHack.disconnect(); // Cleanup 131 | }; 132 | } 133 | }, [inputAutocompleteValue]); 134 | 135 | useEffect(() => { 136 | if (autocompleteRef.current) { 137 | autocompleteRef.current.setFields(fields); 138 | } 139 | }, [fields]); 140 | 141 | useEffect(() => { 142 | if (autocompleteRef.current) { 143 | autocompleteRef.current.setBounds(bounds); 144 | } 145 | }, [bounds]); 146 | 147 | useEffect(() => { 148 | if (autocompleteRef.current) { 149 | autocompleteRef.current.setComponentRestrictions(componentRestrictions); 150 | } 151 | }, [componentRestrictions]); 152 | 153 | useEffect(() => { 154 | if (autocompleteRef.current) { 155 | autocompleteRef.current.setOptions(options); 156 | } 157 | }, [options]); 158 | 159 | return { 160 | ref: inputRef, 161 | autocompleteRef, 162 | }; 163 | } 164 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | export const isBrowser = typeof window !== "undefined" && window.document; 2 | 3 | export const loadGoogleMapScript = ( 4 | googleMapsScriptBaseUrl, 5 | googleMapsScriptUrl 6 | ) => { 7 | if (!isBrowser) return Promise.resolve(); 8 | 9 | if (typeof google !== "undefined") { 10 | if (google.maps && google.maps.api) return Promise.resolve(); 11 | } 12 | 13 | const scriptElements = document.querySelectorAll( 14 | `script[src*="${googleMapsScriptBaseUrl}"]` 15 | ); 16 | 17 | if (scriptElements && scriptElements.length) { 18 | return new Promise((resolve) => { 19 | // in case we already have a script on the page and it's loaded we resolve 20 | if (typeof google !== "undefined") return resolve(); 21 | 22 | // otherwise we wait until it's loaded and resolve 23 | scriptElements[0].addEventListener("load", () => resolve()); 24 | }); 25 | } 26 | 27 | let scriptUrl = new URL(googleMapsScriptUrl); 28 | scriptUrl.searchParams.set( 29 | "callback", 30 | "__REACT_GOOGLE_AUTOCOMPLETE_CALLBACK__" 31 | ); 32 | const el = document.createElement("script"); 33 | el.src = scriptUrl.toString(); 34 | 35 | return new Promise((resolve) => { 36 | window.__REACT_GOOGLE_AUTOCOMPLETE_CALLBACK__ = resolve; 37 | document.body.appendChild(el); 38 | }); 39 | }; 40 | --------------------------------------------------------------------------------