Home

Awesome

<em><b>USE-PLACES-AUTOCOMPLETE</b></em>

This is a React hook for Google Maps Places Autocomplete, which helps you build a UI component with the feature of place autocomplete easily! By leveraging the power of Google Maps Places API, you can provide a great UX (user experience) for user interacts with your search bar or form, etc. Hope you guys πŸ‘πŸ» it.

❀️ it? ⭐️ it on GitHub or Tweet about it.

build status coverage status npm version npm downloads npm downloads gzip size All Contributors PRs welcome Twitter URL

Live Demo

demo

⚑️ Try yourself: https://use-places-autocomplete.netlify.app

Features

Requirement

To use use-places-autocomplete, you must use react@16.8.0 or greater which includes hooks.

Installation

This package is distributed via npm.

$ yarn add use-places-autocomplete
# or
$ npm install --save use-places-autocomplete

When working with TypeScript you need to install the @types/google.maps as a devDependencies.

$ yarn add --dev @types/google.maps
# or
$ npm install --save-dev @types/google.maps

Getting Started

usePlacesAutocomplete is based on the Places Autocomplete (or more specific docs) of Google Maps Place API. If you are unfamiliar with these APIs, we recommend you review them before we start.

Setup APIs

To use this hook, there're two things we need to do:

  1. Enable Google Maps Places API.
  2. Get an API key.

Load the library

Use the script tag to load the library in your project and pass the value of the callback parameter to the callbackName option.

<script
  defer
  src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=YOUR_CALLBACK_NAME"
></script>

⚠️ If you got a global function not found error. Make sure usePlaceAutocomplete is declared before the script was loaded. You can use the async or defer attribute of the <script> element to achieve that.

Create the component

Now we can start to build our component. Check the API out to learn more.

import usePlacesAutocomplete, {
  getGeocode,
  getLatLng,
} from "use-places-autocomplete";
import useOnclickOutside from "react-cool-onclickoutside";

const PlacesAutocomplete = () => {
  const {
    ready,
    value,
    suggestions: { status, data },
    setValue,
    clearSuggestions,
  } = usePlacesAutocomplete({
    callbackName: "YOUR_CALLBACK_NAME",
    requestOptions: {
      /* Define search scope here */
    },
    debounce: 300,
  });
  const ref = useOnclickOutside(() => {
    // When the user clicks outside of the component, we can dismiss
    // the searched suggestions by calling this method
    clearSuggestions();
  });

  const handleInput = (e) => {
    // Update the keyword of the input element
    setValue(e.target.value);
  };

  const handleSelect =
    ({ description }) =>
    () => {
      // When the user selects a place, we can replace the keyword without request data from API
      // by setting the second parameter to "false"
      setValue(description, false);
      clearSuggestions();

      // Get latitude and longitude via utility functions
      getGeocode({ address: description }).then((results) => {
        const { lat, lng } = getLatLng(results[0]);
        console.log("πŸ“ Coordinates: ", { lat, lng });
      });
    };

  const renderSuggestions = () =>
    data.map((suggestion) => {
      const {
        place_id,
        structured_formatting: { main_text, secondary_text },
      } = suggestion;

      return (
        <li key={place_id} onClick={handleSelect(suggestion)}>
          <strong>{main_text}</strong> <small>{secondary_text}</small>
        </li>
      );
    });

  return (
    <div ref={ref}>
      <input
        value={value}
        onChange={handleInput}
        disabled={!ready}
        placeholder="Where are you going?"
      />
      {/* We can use the "status" to decide whether we should display the dropdown or not */}
      {status === "OK" && <ul>{renderSuggestions()}</ul>}
    </div>
  );
};

πŸ’‘ react-cool-onclickoutside is my other hook library, which can help you handle the interaction of user clicks outside of the component(s).

Easy right? This is the magic of usePlacesAutocomplete ✨. I just showed you how it works via a minimal example. However, you can build a UX rich autocomplete component, like WAI-ARIA compliant and keyword interaction like my demo, by checking the code or integrating this hook with the combobox of Reach UI to achieve that.

Edit usePlacesAutocomplete x Reach UI

import usePlacesAutocomplete from "use-places-autocomplete";
import {
  Combobox,
  ComboboxInput,
  ComboboxPopover,
  ComboboxList,
  ComboboxOption,
} from "@reach/combobox";

import "@reach/combobox/styles.css";

const PlacesAutocomplete = () => {
  const {
    ready,
    value,
    suggestions: { status, data },
    setValue,
  } = usePlacesAutocomplete({ callbackName: "YOUR_CALLBACK_NAME" });

  const handleInput = (e) => {
    setValue(e.target.value);
  };

  const handleSelect = (val) => {
    setValue(val, false);
  };

  return (
    <Combobox onSelect={handleSelect} aria-labelledby="demo">
      <ComboboxInput value={value} onChange={handleInput} disabled={!ready} />
      <ComboboxPopover>
        <ComboboxList>
          {status === "OK" &&
            data.map(({ place_id, description }) => (
              <ComboboxOption key={place_id} value={description} />
            ))}
        </ComboboxList>
      </ComboboxPopover>
    </Combobox>
  );
};

Lazily Initializing The Hook

When loading the Google Maps Places API via a 3rd-party library, you may need to wait for the script to be ready before using this hook. However, you can lazily initialize the hook in the following ways, depending on your use case.

Option 1, manually initialize the hook:

import usePlacesAutocomplete from "use-places-autocomplete";

const App = () => {
  const { init } = usePlacesAutocomplete({
    initOnMount: false, // Disable initializing when the component mounts, default is true
  });

  const [loading] = useGoogleMapsApi({
    library: "places",
    onLoad: () => init(), // Lazily initializing the hook when the script is ready
  });

  return <div>{/* Some components... */}</div>;
};

Option 2, wrap the hook into a conditional component:

import usePlacesAutocomplete from "use-places-autocomplete";

const PlacesAutocomplete = () => {
  const { ready, value, suggestions, setValue } = usePlacesAutocomplete();

  return <div>{/* Some components... */}</div>;
};

const App = () => {
  const [loading] = useGoogleMapsApi({ library: "places" });

  return (
    <div>
      {!loading ? <PlacesAutocomplete /> : null}
      {/* Other components... */}
    </div>
  );
};

Cache Data For You

By default, this library caches the response data to help you save the cost of Google Maps Places API and optimize search performance.

const methods = usePlacesAutocomplete({
  // Provide the cache time in seconds, the default is 24 hours
  cache: 24 * 60 * 60,
});

By the way, the cached data is stored via the Window.sessionStorage API.

Custom cache key

You may need to have multiple caches. For example, if you use different place type restrictions for different pickers in your app.

const methods = usePlacesAutocomplete({
  // Provide a custom cache key
  cacheKey: "region-restricted",
});

Note that usePlacesAutocomplete will prefix this with upa-, so the above would become upa-region-restricted in sessionStorage.

API

const returnObj = usePlacesAutocomplete(parameterObj);

Parameter object (optional)

When using usePlacesAutocomplete, you can configure the following options via the parameter.

KeyTypeDefaultDescription
requestOptionsobjectThe request options of Google Maps Places API except for input (e.g. bounds, radius, etc.).
googleMapsobjectwindow.google.mapsIn case you want to provide your own Google Maps object, pass the google.maps to it.
callbackNamestringThe value of the callback parameter when loading the Google Maps JavaScript library.
debouncenumber200Number of milliseconds to delay before making a request to Google Maps Places API.
cachenumber | false86400 (24 hours)Number of seconds to cache the response data of Google Maps Places API.
cacheKeystring"upa"Optional cache key so one can use multiple caches if needed.
defaultValuestring""Default value for the input element.
initOnMountbooleantrueInitialize the hook with Google Maps Places API when the component mounts.

Return object

It's returned with the following properties.

KeyTypeDefaultDescription
readybooleanfalseThe ready status of usePlacesAutocomplete.
valuestring""value for the input element.
suggestionsobject{ loading: false, status: "", data: [] }See suggestions.
setValuefunction(value, shouldFetchData = true) => {}See setValue.
clearSuggestionsfunctionSee clearSuggestions.
clearCachefunction(key = cacheKey) => {}Clears the cached data.
initfunctionUseful when lazily initializing the hook.

suggestions

The search result of Google Maps Places API, which contains the following properties:

setValue

Set the value of the input element. Use the case below.

import usePlacesAutocomplete from "use-places-autocomplete";

const PlacesAutocomplete = () => {
  const { value, setValue } = usePlacesAutocomplete();

  const handleInput = (e) => {
    // Place a "string" to update the value of the input element
    setValue(e.target.value);
  };

  return (
    <div>
      <input value={value} onChange={handleInput} />
      {/* Render dropdown */}
    </div>
  );
};

In addition, the setValue method has an extra parameter, which can be used to disable hitting Google Maps Places API.

import usePlacesAutocomplete from "use-places-autocomplete";

const PlacesAutocomplete = () => {
  const {
    value,
    suggestions: { status, data },
    setValue,
  } = usePlacesAutocomplete();

  const handleSelect =
    ({ description }) =>
    () => {
      // When the user selects a place, we can replace the keyword without requesting data from the API
      // by setting the second parameter to "false"
      setValue(description, false);
    };

  const renderSuggestions = () =>
    data.map((suggestion) => (
      <li key={suggestion.place_id} onClick={handleSelect(suggestion)}>
        {/* Render suggestion text */}
      </li>
    ));

  return (
    <div>
      <input value={value} onChange={handleInput} />
      {status === "OK" && <ul>{renderSuggestions()}</ul>}
    </div>
  );
};

clearSuggestions

Calling the method will clear and reset all the properties of the suggestions object to default. It's useful for dismissing the dropdown.

import usePlacesAutocomplete from "use-places-autocomplete";
import useOnclickOutside from "react-cool-onclickoutside";

const PlacesAutocomplete = () => {
  const {
    value,
    suggestions: { status, data },
    setValue,
    clearSuggestions,
  } = usePlacesAutocomplete();
  const ref = useOnclickOutside(() => {
    // When the user clicks outside of the component, call it to clear and reset the suggestions data
    clearSuggestions();
  });

  const renderSuggestions = () =>
    data.map((suggestion) => (
      <li key={suggestion.place_id} onClick={handleSelect(suggestion)}>
        {/* Render suggestion text */}
      </li>
    ));

  return (
    <div ref={ref}>
      <input value={value} onChange={handleInput} />
      {/* After calling the clearSuggestions(), the "status" is reset so the dropdown is hidden */}
      {status === "OK" && <ul>{renderSuggestions()}</ul>}
    </div>
  );
};

Utility Functions

We provide getGeocode, getLatLng, getZipCode, and getDetails utils for you to do geocoding and get geographic coordinates when needed.

getGeocode

It helps you convert address (e.g. "Section 5, Xinyi Road, Xinyi District, Taipei City, Taiwan") into geographic coordinates (e.g. latitude 25.033976 and longitude 121.5645389), or restrict the results to a specific area by Google Maps Geocoding API.

In case you want to restrict the results to a specific area, you will have to pass the address and the componentRestrictions matching the GeocoderComponentRestrictions interface.

import { getGeocode } from "use-places-autocomplete";

const parameter = {
  address: "Section 5, Xinyi Road, Xinyi District, Taipei City, Taiwan",
  // or
  placeId: "ChIJraeA2rarQjQRPBBjyR3RxKw",
};

getGeocode(parameter)
  .then((results) => {
    console.log("Geocoding results: ", results);
  })
  .catch((error) => {
    console.log("Error: ", error);
  });

getGeocode is an asynchronous function with the following API:

getLatLng

It helps you get the lat and lng from the result object of getGeocode.

import { getGeocode, getLatLng } from "use-places-autocomplete";

const parameter = {
  address: "Section 5, Xinyi Road, Xinyi District, Taipei City, Taiwan",
};

getGeocode(parameter).then((results) => {
  const { lat, lng } = getLatLng(results[0]);
  console.log("Coordinates: ", { lat, lng });
});

getLatLng is a function with the following API:

getZipCode

It helps you get the postal_code from the result object of getGeocode.

import { getGeocode, getZipCode } from "use-places-autocomplete";

const parameter = {
  address: "Section 5, Xinyi Road, Xinyi District, Taipei City, Taiwan",
};

getGeocode(parameter)
  // By default we use the "long_name" value from API response, you can tell the utility to use "short_name"
  // by setting the second parameter to "true"
  .then((results) => {
    const zipCode = getZipCode(results[0], false);
    console.log("ZIP Code: ", zipCode);
  });

getZipCode is a function with the following API:

getDetails

Retrieves a great deal of information about a particular place ID (suggestion).

import usePlacesAutocomplete, { getDetails } from "use-places-autocomplete";

const PlacesAutocomplete = () => {
  const { suggestions, value, setValue } = usePlacesAutocomplete();

  const handleInput = (e) => {
    // Place a "string" to update the value of the input element
    setValue(e.target.value);
  };

  const submit = () => {
    const parameter = {
      // Use the "place_id" of suggestion from the dropdown (object), here just taking the first suggestion for brevity
      placeId: suggestions[0].place_id,
      // Specify the return data that you want (optional)
      fields: ["name", "rating"],
    };

    getDetails(parameter)
      .then((details) => {
        console.log("Details: ", details);
      })
      .catch((error) => {
        console.log("Error: ", error);
      });
  };

  return (
    <div>
      <input value={value} onChange={handleInput} />
      {/* Render dropdown */}
      <button onClick={submit}>Submit Suggestion</button>
    </div>
  );
};

getDetails is an asynchronous function with the following API:

⚠️ warning, you are billed based on how much information you retrieve, So it is advised that you retrieve just what you need.

Articles / Blog Posts

πŸ’‘ If you have written any blog post or article about use-places-autocomplete, please open a PR to add it here.

Contributors ✨

Thanks goes to these wonderful people (emoji key):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- prettier-ignore-start --> <!-- markdownlint-disable --> <table> <tbody> <tr> <td align="center" valign="top" width="14.28%"><a href="https://wellyshen.com"><img src="https://avatars1.githubusercontent.com/u/21308003?v=4?s=100" width="100px;" alt="Welly"/><br /><sub><b>Welly</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=wellyshen" title="Code">πŸ’»</a> <a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=wellyshen" title="Documentation">πŸ“–</a> <a href="#maintenance-wellyshen" title="Maintenance">🚧</a></td> <td align="center" valign="top" width="14.28%"><a href="https://kylekirkby.github.io"><img src="https://avatars0.githubusercontent.com/u/4564433?v=4?s=100" width="100px;" alt="Kyle"/><br /><sub><b>Kyle</b></sub></a><br /><a href="#translation-kylekirkby" title="Translation">🌍</a></td> <td align="center" valign="top" width="14.28%"><a href="https://www.lkaric.tech"><img src="https://avatars0.githubusercontent.com/u/16634314?v=4?s=100" width="100px;" alt="Lazar KariΔ‡"/><br /><sub><b>Lazar KariΔ‡</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=rejvban" title="Code">πŸ’»</a> <a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=rejvban" title="Documentation">πŸ“–</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/reharik"><img src="https://avatars2.githubusercontent.com/u/882382?v=4?s=100" width="100px;" alt="Raif Harik"/><br /><sub><b>Raif Harik</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=reharik" title="Code">πŸ’»</a> <a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=reharik" title="Documentation">πŸ“–</a> <a href="#ideas-reharik" title="Ideas, Planning, & Feedback">πŸ€”</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/Xerxes-J"><img src="https://avatars0.githubusercontent.com/u/18053412?v=4?s=100" width="100px;" alt="Xerxes Jarquin"/><br /><sub><b>Xerxes Jarquin</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/issues?q=author%3AXerxes-J" title="Bug reports">πŸ›</a></td> <td align="center" valign="top" width="14.28%"><a href="https://www.lucasoconnell.net/"><img src="https://avatars1.githubusercontent.com/u/63400356?v=4?s=100" width="100px;" alt="Lucas O'Connell"/><br /><sub><b>Lucas O'Connell</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=Isoaxe" title="Documentation">πŸ“–</a></td> <td align="center" valign="top" width="14.28%"><a href="http://www.keven.com.br"><img src="https://avatars.githubusercontent.com/u/5994795?v=4?s=100" width="100px;" alt="Keven Jesus"/><br /><sub><b>Keven Jesus</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/issues?q=author%3Akevenjesus" title="Bug reports">πŸ›</a></td> </tr> <tr> <td align="center" valign="top" width="14.28%"><a href="https://github.com/viniciusueharaweb"><img src="https://avatars.githubusercontent.com/u/77734864?v=4?s=100" width="100px;" alt="Vinicius Uehara"/><br /><sub><b>Vinicius Uehara</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=viniciusueharaweb" title="Documentation">πŸ“–</a></td> <td align="center" valign="top" width="14.28%"><a href="http://orbiteleven.net"><img src="https://avatars.githubusercontent.com/u/331393?v=4?s=100" width="100px;" alt="Damon"/><br /><sub><b>Damon</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=orbiteleven" title="Code">πŸ’»</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/RavenHursT"><img src="https://avatars.githubusercontent.com/u/496983?v=4?s=100" width="100px;" alt="Matthew Marcus"/><br /><sub><b>Matthew Marcus</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=RavenHursT" title="Code">πŸ’»</a></td> <td align="center" valign="top" width="14.28%"><a href="https://github.com/csandman"><img src="https://avatars.githubusercontent.com/u/9214195?v=4?s=100" width="100px;" alt="Chris Sandvik"/><br /><sub><b>Chris Sandvik</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=csandman" title="Code">πŸ’»</a></td> <td align="center" valign="top" width="14.28%"><a href="https://linktr.ee/tyeetale"><img src="https://avatars.githubusercontent.com/u/17817587?v=4?s=100" width="100px;" alt="Thomas Yee"/><br /><sub><b>Thomas Yee</b></sub></a><br /><a href="https://github.com/wellyshen/use-places-autocomplete/commits?author=tyeetale" title="Documentation">πŸ“–</a></td> </tr> </tbody> </table> <!-- markdownlint-restore --> <!-- prettier-ignore-end --> <!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the all-contributors specification. Contributions of any kind are welcome!