npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@gralweb/use-google-places-autocomplete

v1.1.3

Published

Headless React hook for Google Places Autocomplete with full TypeScript support. Bring your own UI.

Readme

use-google-places-autocomplete

Headless React hook for Google Places Autocomplete. Bring your own UI.

npm version License: MIT

Why Headless?

This package provides only the logic for Google Places Autocomplete, giving you complete freedom to build your own UI:

  • No CSS to override - Use your own styles
  • Works with any UI library - shadcn, MUI, Chakra, Tailwind, etc.
  • Smaller bundle size - Only the hook logic
  • Full TypeScript support - Complete type safety
  • Modern Google Places API - Uses latest API features

Installation

npm install @gralweb/use-google-places-autocomplete
# or
yarn add @gralweb/use-google-places-autocomplete
# or
pnpm add @gralweb/use-google-places-autocomplete

Quick Start

import { usePlacesAutocomplete } from '@gralweb/use-google-places-autocomplete'

function MyAutocomplete() {
  const { 
    getInputProps,
    containerRef, 
    predictions, 
    isOpen, 
    handleSelectPlace,
    isLoaded 
  } = usePlacesAutocomplete({
    apiKey: 'YOUR_GOOGLE_MAPS_API_KEY',
    onPlaceSelect: (place) => {
      console.log('Selected place:', place)
    },
  })

  if (!isLoaded) return <div>Loading...</div>

  return (
    <div ref={containerRef}>
      <input {...getInputProps({ placeholder: "Search places..." })} />
      {isOpen && predictions.length > 0 && (
        <ul>
          {predictions.map((prediction) => (
            <li 
              key={prediction.placeId}
              onClick={() => handleSelectPlace(prediction)}
            >
              {prediction.description}
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

API Reference

usePlacesAutocomplete(options)

Options

| Option | Type | Required | Default | Description | | ------ | ---- | -------- | ------- | ----------- | | apiKey | string | ✅ | - | Google Maps API Key | | onPlaceSelect | (place: PlaceDetails) => void | ❌ | - | Callback when a place is selected | | onError | (error: Error) => void | ❌ | - | Error handler callback | | options | AutocompleteOptions | ❌ | See below | Google Places API options | | debounceMs | number | ❌ | 300 | Debounce delay in milliseconds | | minChars | number | ❌ | 3 | Minimum characters to trigger search |

Default options:

{
  types: ['geocode'],
  fields: [
    'id',
    'displayName',
    'formattedAddress',
    'location',
    'addressComponents',
  ],
}

Returns

{
  // Function to get input props with automatic merging (recommended)
  getInputProps: (userProps?: React.InputHTMLAttributes<HTMLInputElement>) => 
    React.InputHTMLAttributes<HTMLInputElement> & { ref: React.RefObject<HTMLInputElement> }
  
  // Legacy: Props to spread on your input element (deprecated, use getInputProps instead)
  inputProps: {
    ref: React.RefObject<HTMLInputElement>
    value: string
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
    onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void
    autoComplete: string
  }
  
  // Ref for the container (handles click-outside)
  containerRef: React.RefObject<HTMLDivElement | null>
  
  // Predictions array
  predictions: PlaceResult[]
  
  // UI state
  isOpen: boolean
  selectedIndex: number
  
  // Selection handler
  handleSelectPlace: (prediction: PlaceResult) => void
  
  // Loading state
  isLoaded: boolean
  loadError: Error | null
  
  // Manual control methods
  clearSuggestions: () => void
  setValue: (value: string) => void
}

Examples

Controlled Input (Forms)

New in v1.1.0: Use getInputProps() for seamless integration with controlled inputs and form libraries.

import { useState } from 'react'
import { usePlacesAutocomplete } from '@gralweb/use-google-places-autocomplete'

function ControlledExample() {
  const [address, setAddress] = useState('')
  
  const { 
    getInputProps,
    containerRef, 
    predictions, 
    isOpen,
    handleSelectPlace 
  } = usePlacesAutocomplete({
    apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
    onPlaceSelect: (place) => {
      setAddress(place.formattedAddress)
    },
  })

  return (
    <div ref={containerRef}>
      <input 
        {...getInputProps({
          value: address,
          onChange: (e) => setAddress(e.target.value),
          placeholder: "Enter your address...",
          className: "form-input"
        })}
      />
      {isOpen && predictions.length > 0 && (
        <ul className="suggestions">
          {predictions.map((prediction) => (
            <li 
              key={prediction.placeId}
              onClick={() => handleSelectPlace(prediction)}
            >
              <div>{prediction.mainText}</div>
              <div className="text-sm text-gray-500">{prediction.secondaryText}</div>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

With React Hook Form

import { useForm } from 'react-hook-form'
import { usePlacesAutocomplete } from '@gralweb/use-google-places-autocomplete'

function FormExample() {
  const { register, handleSubmit, setValue, watch } = useForm()
  const address = watch('address')
  
  const { getInputProps, containerRef, predictions, isOpen, handleSelectPlace } = 
    usePlacesAutocomplete({
      apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
      onPlaceSelect: (place) => {
        setValue('address', place.formattedAddress)
        setValue('lat', place.geometry?.location.lat)
        setValue('lng', place.geometry?.location.lng)
      },
    })

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div ref={containerRef}>
        <input
          {...getInputProps({
            ...register('address'),
            value: address,
            placeholder: "Address"
          })}
        />
        {isOpen && predictions.length > 0 && (
          <ul>
            {predictions.map((prediction) => (
              <li key={prediction.placeId} onClick={() => handleSelectPlace(prediction)}>
                {prediction.description}
              </li>
            ))}
          </ul>
        )}
      </div>
      <button type="submit">Submit</button>
    </form>
  )
}

With Custom Styling

import { usePlacesAutocomplete } from '@gralweb/use-google-places-autocomplete'

function StyledAutocomplete() {
  const { 
    getInputProps,
    containerRef, 
    predictions, 
    isOpen,
    selectedIndex,
    handleSelectPlace 
  } = usePlacesAutocomplete({
    apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
    onPlaceSelect: (place) => {
      console.log('Selected:', place.formattedAddress)
    },
  })

  return (
    <div ref={containerRef} className="relative w-full max-w-md">
      <input 
        {...getInputProps({
          className: "w-full px-4 py-2 border rounded-lg",
          placeholder: "Search for a place..."
        })}
      />
      {isOpen && predictions.length > 0 && (
        <ul className="absolute w-full mt-1 bg-white border rounded-lg shadow-lg">
          {predictions.map((prediction, index) => (
            <li
              key={prediction.placeId}
              onClick={() => handleSelectPlace(prediction)}
              className={`px-4 py-2 cursor-pointer hover:bg-gray-100 ${
                index === selectedIndex ? 'bg-gray-100' : ''
              }`}
            >
              <div className="font-medium">{prediction.mainText}</div>
              <div className="text-sm text-gray-500">{prediction.secondaryText}</div>
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

With shadcn/ui

import { usePlacesAutocomplete } from '@gralweb/use-google-places-autocomplete'
import { Input } from '@/components/ui/input'
import { Card } from '@/components/ui/card'

function ShadcnExample() {
  const { 
    getInputProps,
    containerRef, 
    predictions, 
    isOpen,
    handleSelectPlace 
  } = usePlacesAutocomplete({
    apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
    onPlaceSelect: (place) => console.log(place),
  })

  return (
    <div ref={containerRef} className="relative w-full max-w-md">
      <Input {...getInputProps({ placeholder: "Search places..." })} />
      {isOpen && predictions.length > 0 && (
        <Card className="absolute w-full mt-1 p-0">
          {predictions.map((prediction) => (
            <div
              key={prediction.placeId}
              onClick={() => handleSelectPlace(prediction)}
              className="px-4 py-2 cursor-pointer hover:bg-accent"
            >
              {prediction.description}
            </div>
          ))}
        </Card>
      )}
    </div>
  )
}

With Material-UI

import { usePlacesAutocomplete } from '@gralweb/use-google-places-autocomplete'
import TextField from '@mui/material/TextField'
import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem'
import Paper from '@mui/material/Paper'

function MUIExample() {
  const { 
    getInputProps,
    containerRef, 
    predictions, 
    isOpen,
    handleSelectPlace 
  } = usePlacesAutocomplete({
    apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
  })

  return (
    <div ref={containerRef}>
      <TextField
        {...getInputProps({
          label: "Search places",
          variant: "outlined"
        })}
        fullWidth
      />
      {isOpen && predictions.length > 0 && (
        <Paper elevation={3}>
          <List>
            {predictions.map((prediction) => (
              <ListItem
                key={prediction.placeId}
                button
                onClick={() => handleSelectPlace(prediction)}
              >
                {prediction.description}
              </ListItem>
            ))}
          </List>
        </Paper>
      )}
    </div>
  )
}

With Manual Control

import { usePlacesAutocomplete } from '@gralweb/use-google-places-autocomplete'

function AdvancedExample() {
  const { 
    getInputProps,
    containerRef, 
    predictions,
    isOpen,
    handleSelectPlace,
    clearSuggestions,
    setValue,
    isLoaded,
    loadError
  } = usePlacesAutocomplete({
    apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
    onPlaceSelect: (place) => {
      console.log('Selected:', place)
      setTimeout(() => clearSuggestions(), 100)
    },
    onError: (error) => {
      console.error('Error:', error)
    },
  })

  const handleClear = () => {
    setValue('')
    clearSuggestions()
  }

  if (loadError) {
    return <div>Error: {loadError.message}</div>
  }

  if (!isLoaded) {
    return <div>Loading Google Maps...</div>
  }

  return (
    <div ref={containerRef} className="relative">
      <div className="flex gap-2">
        <input {...getInputProps({ className: "flex-1" })} />
        <button onClick={handleClear}>Clear</button>
      </div>
      {isOpen && predictions.length > 0 && (
        <ul>
          {predictions.map((prediction) => (
            <li 
              key={prediction.placeId}
              onClick={() => handleSelectPlace(prediction)}
            >
              {prediction.description}
            </li>
          ))}
        </ul>
      )}
    </div>
  )
}

Restrict by Country

const { getInputProps, containerRef, predictions, isOpen } = usePlacesAutocomplete({
  apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
  options: {
    componentRestrictions: { country: 'us' },
  },
})

Filter by Type

const { getInputProps, containerRef, predictions, isOpen } = usePlacesAutocomplete({
  apiKey: process.env.VITE_GOOGLE_MAPS_API_KEY,
  options: {
    types: ['restaurant', 'cafe'],
  },
})

TypeScript

Full TypeScript support with exported types:

import type { 
  PlaceDetails, 
  PlaceResult,
  AutocompleteOptions,
  UsePlacesAutocompleteOptions,
  UsePlacesAutocompleteReturn
} from '@gralweb/use-google-places-autocomplete'

const handleSelect = (place: PlaceDetails) => {
  console.log(place.formattedAddress)
  console.log(place.geometry?.location.lat)
  console.log(place.geometry?.location.lng)
}

Advanced Usage

Using Helpers Directly

import { getPredictions, getPlaceDetails } from '@gralweb/use-google-places-autocomplete'

// Get predictions manually
const predictions = await getPredictions('New York', {
  sessionToken: myToken,
  options: { types: ['geocode'] }
})

// Get place details manually
const details = await getPlaceDetails('ChIJOwg_06VPwokRYv534QaPC8g', {
  fields: ['formattedAddress', 'location']
})

Using Constants

import { 
  DEFAULT_AUTOCOMPLETE_OPTIONS,
  DEFAULT_DEBOUNCE_MS,
  DEFAULT_MIN_CHARS
} from '@gralweb/use-google-places-autocomplete'

console.log(DEFAULT_DEBOUNCE_MS) // 300
console.log(DEFAULT_MIN_CHARS)   // 3

Getting a Google Maps API Key

  1. Go to Google Cloud Console
  2. Create a project or select an existing one
  3. Enable Places API (New)
  4. Create credentials (API Key)
  5. (Optional) Restrict the API key to your domain

⚠️ Important Legal Notice

This package is a wrapper around the Google Maps Places API. Users of this package must:

  1. Have a valid Google Maps API Key
  2. Comply with Google Maps Platform Terms of Service
  3. Follow Google Maps Platform Usage Limits
  4. Display the Google logo and attributions as required by Google's terms
  5. Be responsible for any API usage fees incurred

This package and its author are not affiliated with, endorsed by, or sponsored by Google LLC.

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

License

MIT License - see LICENSE file for details.

Disclaimer

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Users are solely responsible for:

  • Complying with Google Maps Platform Terms of Service
  • Any applicable usage fees from Google
  • Proper implementation and security of API keys
  • Meeting Google's attribution requirements

Contributing

Contributions are welcome! Please open an issue or pull request.

Support