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

react-places-suggestion

v0.1.0

Published

React hook and headless component for Google Maps Places Autocomplete (New API)

Readme

react-places-suggestion

A lightweight, headless React hook and component for Google Maps Places Autocomplete using the new AutocompleteSuggestion API.

npm TypeScript ESM gzip size License

Why This Package?

The popular react-places-autocomplete package has been unmaintained for 6+ years. Google deprecated the old AutocompleteService API (as of March 2025), requiring a migration to the new AutocompleteSuggestion API.

This package provides a modern, minimal replacement with:

  • New API support: Built on Google's AutocompleteSuggestion API
  • Modern React: Hooks-first design with full TypeScript support
  • Headless: Bring your own UI components
  • Small footprint: ~2.6KB gzipped, zero dependencies
  • Session tokens: Automatic request grouping for cost optimization
  • Caching: Built-in query result caching
  • Familiar DX: Similar to the legacy library but with current Google APIs

Installation

npm install react-places-suggestion
pnpm add react-places-suggestion
yarn add react-places-suggestion

Prerequisites

Before using this package, the Google Maps JavaScript API must be loaded with the Places library.

Important: API Version

You must use version="beta" or "weekly" to access the AutocompleteSuggestion API. The default stable version does not include this API yet.

Load via Script Tag

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

Load via @googlemaps/js-api-loader

import { Loader } from '@googlemaps/js-api-loader';

const loader = new Loader({
  apiKey: 'YOUR_API_KEY',
  version: 'beta',
  libraries: ['places'],
});

loader.load().then(() => {
  // Your app is ready to use react-places-suggestion
});

Quick Start

import { usePlacesAutocomplete } from 'react-places-suggestion';

function MyComponent() {
  const {
    value,
    setValue,
    suggestions,
    loading,
    getInputProps,
    getSuggestionItemProps,
    clearSuggestions,
  } = usePlacesAutocomplete({
    debounce: 300,
    minLength: 2,
  });

  return (
    <div>
      <input
        {...getInputProps({
          placeholder: 'Enter a place...',
        })}
      />

      {loading && <div>Loading...</div>}

      {suggestions.length > 0 && (
        <ul>
          {suggestions.map((suggestion) => (
            <li key={suggestion.placeId} {...getSuggestionItemProps(suggestion)}>
              {suggestion.description}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

Hook API: usePlacesAutocomplete

The main hook for managing places autocomplete state and logic.

Configuration

interface UsePlacesAutocompleteConfig {
  /** Debounce delay in milliseconds (default: 300) */
  debounce?: number;

  /** Minimum characters before fetching (default: 2) */
  minLength?: number;

  /** Search options for the API */
  searchOptions?: SearchOptions;

  /** Whether to fetch suggestions (default: true) */
  enabled?: boolean;

  /** Cache results for identical queries (default: true) */
  cache?: boolean;

  /** Cache duration in milliseconds (default: 300000 = 5 minutes) */
  cacheDuration?: number;

  /** Initial value for the input */
  defaultValue?: string;
}

SearchOptions

interface SearchOptions {
  /**
   * Restrict results to specific region codes (e.g., ["us", "ca"])
   * Maximum 15 codes. Replaces legacy componentRestrictions.country
   */
  includedRegionCodes?: string[];

  /**
   * Bias results toward a geographic area
   */
  locationBias?: google.maps.places.LocationBias;

  /**
   * Restrict results to a geographic area
   */
  locationRestriction?: google.maps.places.LocationRestriction;

  /**
   * Filter by primary place types (e.g., ["restaurant"])
   * Replaces legacy searchOptions.types
   */
  includedPrimaryTypes?: string[];

  /**
   * Language code for results (e.g., "en", "es")
   */
  language?: string;

  /**
   * Origin for distance calculations
   */
  origin?: google.maps.LatLng | google.maps.LatLngLiteral;
}

Return Value

interface UsePlacesAutocompleteReturn {
  /** Current input value */
  value: string;

  /**
   * Set the input value
   * @param value - New input value
   * @param shouldFetch - Whether to trigger API call (default: true)
   */
  setValue: (value: string, shouldFetch?: boolean) => void;

  /** Array of place suggestions */
  suggestions: PlaceSuggestion[];

  /** Whether a fetch is in progress */
  loading: boolean;

  /** Error from the API, if any */
  error: Error | null;

  /** Clear suggestions list */
  clearSuggestions: () => void;

  /**
   * Props to spread onto an input element
   * Includes value, onChange, role, aria attributes
   */
  getInputProps: (
    overrides?: React.InputHTMLAttributes<HTMLInputElement>
  ) => React.InputHTMLAttributes<HTMLInputElement>;

  /**
   * Props to spread onto a suggestion item element
   * Includes role, onClick handler
   */
  getSuggestionItemProps: (
    suggestion: PlaceSuggestion,
    overrides?: React.HTMLAttributes<HTMLElement>
  ) => React.HTMLAttributes<HTMLElement>;
}

Examples

Basic usage with minimal config:

const { value, setValue, suggestions, getInputProps, getSuggestionItemProps } =
  usePlacesAutocomplete();

With search restrictions:

const { suggestions, ...rest } = usePlacesAutocomplete({
  searchOptions: {
    includedRegionCodes: ['us', 'ca'],
    includedPrimaryTypes: ['restaurant', 'cafe'],
    language: 'es',
  },
});

With location bias:

const { suggestions, ...rest } = usePlacesAutocomplete({
  searchOptions: {
    locationBias: {
      center: { lat: 40.7128, lng: -74.006 },
      radius: 10000, // 10 km
    },
  },
});

Disable fetching conditionally:

const { suggestions, ...rest } = usePlacesAutocomplete({
  enabled: isSearchActive,
  debounce: 500,
});

Manual value control without API call:

// Set value without triggering fetch
setValue('some text', false);

// Or clear everything
clearSuggestions();

Component API: PlacesAutocomplete

A render-prop component wrapper around usePlacesAutocomplete for controlled component patterns.

interface PlacesAutocompleteProps {
  /** Controlled input value */
  value: string;

  /** Called when input value changes */
  onChange: (value: string) => void;

  /** Called when a suggestion is selected */
  onSelect?: (description: string, placeId: string) => void;

  /** Called on API error */
  onError?: (error: Error) => void;

  /** Search options */
  searchOptions?: SearchOptions;

  /** Debounce delay in milliseconds */
  debounce?: number;

  /** Minimum characters before fetching */
  minLength?: number;

  /** Whether to fetch (default: true) */
  shouldFetchSuggestions?: boolean;

  /** Render prop */
  children: (args: {
    getInputProps: (...) => React.InputHTMLAttributes<HTMLInputElement>;
    suggestions: PlaceSuggestion[];
    getSuggestionItemProps: (...) => React.HTMLAttributes<HTMLElement>;
    loading: boolean;
  }) => React.ReactNode;
}

Example

import { PlacesAutocomplete } from 'react-places-suggestion';
import { useState } from 'react';

function MyForm() {
  const [place, setPlace] = useState('');

  const handleSelect = (description: string, placeId: string) => {
    setPlace(description);
    console.log('Selected place:', { description, placeId });
  };

  return (
    <PlacesAutocomplete
      value={place}
      onChange={setPlace}
      onSelect={handleSelect}
      onError={(error) => console.error('Place error:', error)}
      searchOptions={{
        includedRegionCodes: ['us'],
      }}
      debounce={400}
    >
      {({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
        <div>
          <input
            {...getInputProps({
              placeholder: 'Search places...',
              className: 'input',
            })}
          />

          {loading && <div className="loading">Searching...</div>}

          {suggestions.length > 0 && (
            <ul className="suggestions">
              {suggestions.map((suggestion) => (
                <li
                  key={suggestion.placeId}
                  {...getSuggestionItemProps(suggestion, {
                    className: 'suggestion-item',
                  })}
                >
                  <strong>{suggestion.mainText}</strong>
                  {suggestion.secondaryText && (
                    <span className="secondary">{suggestion.secondaryText}</span>
                  )}
                </li>
              ))}
            </ul>
          )}
        </div>
      )}
    </PlacesAutocomplete>
  );
}

Utilities

geocodeByAddress

Convert an address string to geocoding results.

import { geocodeByAddress } from 'react-places-suggestion/utils';

try {
  const results = await geocodeByAddress('1600 Pennsylvania Ave, Washington, DC');
  console.log(results); // Array of GeocoderResult
} catch (error) {
  console.error('Geocoding failed:', error);
}

geocodeByPlaceId

Convert a place ID to geocoding results.

import { geocodeByPlaceId } from 'react-places-suggestion/utils';

// From onSelect callback
const handleSelect = async (description: string, placeId: string) => {
  const results = await geocodeByPlaceId(placeId);
  console.log('Full place details:', results[0]);
};

getLatLng

Extract latitude and longitude from a geocoding result.

import { geocodeByPlaceId, getLatLng } from 'react-places-suggestion/utils';

const handleSelect = async (description: string, placeId: string) => {
  const results = await geocodeByPlaceId(placeId);
  const { lat, lng } = await getLatLng(results[0]);
  console.log(`Coordinates: ${lat}, ${lng}`);
};

Complete Example: Address to Map Coordinates

import { usePlacesAutocomplete } from 'react-places-suggestion';
import { geocodeByPlaceId, getLatLng } from 'react-places-suggestion/utils';
import { useState } from 'react';

function LocationPicker() {
  const [coordinates, setCoordinates] = useState(null);

  const {
    value,
    setValue,
    suggestions,
    getInputProps,
    getSuggestionItemProps,
  } = usePlacesAutocomplete();

  const handleSelect = async (description: string, placeId: string) => {
    try {
      const results = await geocodeByPlaceId(placeId);
      const coords = await getLatLng(results[0]);
      setCoordinates(coords);
    } catch (error) {
      console.error('Failed to get coordinates:', error);
    }
  };

  return (
    <div>
      <input {...getInputProps({ placeholder: 'Enter address...' })} />

      {suggestions.length > 0 && (
        <ul>
          {suggestions.map((suggestion) => (
            <li
              key={suggestion.placeId}
              {...getSuggestionItemProps(suggestion, {
                onClick: () => handleSelect(suggestion.description, suggestion.placeId),
              })}
            >
              {suggestion.description}
            </li>
          ))}
        </ul>
      )}

      {coordinates && (
        <div>
          Selected: {coordinates.lat}, {coordinates.lng}
        </div>
      )}
    </div>
  );
}

PlaceSuggestion Object

Each suggestion includes detailed information about a place:

interface PlaceSuggestion {
  /** Unique place identifier (used with geocoding APIs) */
  placeId: string;

  /** Full text description of the place */
  description: string;

  /** Main text (e.g., business name or street address) */
  mainText: string;

  /** Secondary text (e.g., city, state) */
  secondaryText: string;

  /**
   * Text match ranges for highlighting in mainText
   * Format: { offset, length } for backward compatibility
   * - offset: Starting position in mainText
   * - length: Number of characters matched
   */
  mainTextMatchedSubstrings: Array<{ offset: number; length: number }>;

  /** Place types (e.g., "restaurant", "street_address", "establishment") */
  types: string[];

  /** Distance in meters from the origin (if locationBias set) */
  distanceMeters: number | null;

  /**
   * Raw PlacePrediction from Google Maps API
   * Available for advanced use cases
   */
  _raw: google.maps.places.PlacePrediction;
}

Highlighting Matched Text

The mainTextMatchedSubstrings field tells you which parts of mainText matched the user's query. Use this to highlight matching text:

function SuggestionItem({ suggestion }) {
  const renderHighlightedText = () => {
    const { mainText, mainTextMatchedSubstrings } = suggestion;
    const parts: Array<{ text: string; isMatch: boolean }> = [];

    let lastEnd = 0;

    for (const match of mainTextMatchedSubstrings) {
      if (match.offset > lastEnd) {
        parts.push({
          text: mainText.substring(lastEnd, match.offset),
          isMatch: false,
        });
      }
      parts.push({
        text: mainText.substring(match.offset, match.offset + match.length),
        isMatch: true,
      });
      lastEnd = match.offset + match.length;
    }

    if (lastEnd < mainText.length) {
      parts.push({
        text: mainText.substring(lastEnd),
        isMatch: false,
      });
    }

    return parts.map((part, idx) =>
      part.isMatch ? (
        <strong key={idx}>{part.text}</strong>
      ) : (
        <span key={idx}>{part.text}</span>
      )
    );
  };

  return (
    <div>
      {renderHighlightedText()}
      {suggestion.secondaryText && (
        <div className="secondary">{suggestion.secondaryText}</div>
      )}
    </div>
  );
}

Migration from react-places-autocomplete

If you're upgrading from the legacy library, here's what changed:

API Changes

| Aspect | Old (react-places-autocomplete) | New (react-places-suggestion) | | --- | --- | --- | | Import hook | usePlacesAutocomplete() | usePlacesAutocomplete() | | Import component | PlacesAutocomplete | PlacesAutocomplete | | Region restriction | componentRestrictions: { country: 'us' } | searchOptions: { includedRegionCodes: ['us'] } | | Place types filter | searchOptions: { types: ['restaurant'] } | searchOptions: { includedPrimaryTypes: ['restaurant'] } | | onSelect callback | (address: string, placeId: string, place: PlacesService) | (description: string, placeId: string) | | Input change handler | String value in onChange | ChangeEvent (use e.target.value) | | Highlight first | highlightFirstSuggestion: true | Manual with keyboard handlers | | Custom callback name | googleCallbackName | Not needed (handled internally) |

Example Migration

Before (react-places-autocomplete):

import usePlacesAutocomplete from 'react-places-autocomplete';

function OldComponent() {
  const { value, suggestions, setValue } = usePlacesAutocomplete();

  const handleSelect = (address, placeId, place) => {
    setValue(address, false);
    console.log(placeId, place);
  };

  return (
    <div>
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Enter address"
      />
      {/* ... */}
    </div>
  );
}

After (react-places-suggestion):

import { usePlacesAutocomplete } from 'react-places-suggestion';

function NewComponent() {
  const { value, suggestions, getInputProps, getSuggestionItemProps } =
    usePlacesAutocomplete();

  const handleSelect = (description, placeId) => {
    // setValue already called by getSuggestionItemProps
    console.log(placeId);
  };

  return (
    <div>
      <input {...getInputProps({ placeholder: 'Enter address' })} />
      {/* ... */}
    </div>
  );
}

TypeScript

All types are exported from the main package:

import type {
  PlaceSuggestion,
  SearchOptions,
  UsePlacesAutocompleteConfig,
  UsePlacesAutocompleteReturn,
  PlacesAutocompleteProps,
  LatLng,
} from 'react-places-suggestion';

Tree-shaking

This package supports deep imports for better tree-shaking:

// Just the hook (excludes component)
import { usePlacesAutocomplete } from 'react-places-suggestion/hook';

// Just the utilities (excludes hook and component)
import { geocodeByAddress, geocodeByPlaceId, getLatLng } from 'react-places-suggestion/utils';

// Everything
import {
  usePlacesAutocomplete,
  PlacesAutocomplete,
  geocodeByAddress,
} from 'react-places-suggestion';

Browser Support

  • Chrome/Edge: Latest 2 versions
  • Firefox: Latest 2 versions
  • Safari: Latest 2 versions
  • Requires ES2020+ support

License

MIT. See LICENSE file.

Contributing

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

Support

For issues, feature requests, or questions: