react-places-suggestion
v0.1.0
Published
React hook and headless component for Google Maps Places Autocomplete (New API)
Maintainers
Readme
react-places-suggestion
A lightweight, headless React hook and component for Google Maps Places Autocomplete using the new AutocompleteSuggestion API.
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
AutocompleteSuggestionAPI - 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-suggestionpnpm add react-places-suggestionyarn add react-places-suggestionPrerequisites
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:
- GitHub Issues: react-places-suggestion/issues
- Google Maps API Docs: Places Autocomplete
- AutocompleteSuggestion Docs: Beta API Reference
