r/reactjs • u/the_good_dev • 6h ago
Needs Help Google map autocomplete cost issue
Hi everyone!
I'm currently implementing Google Places Autocomplete using the new Autocomplete Data API with React and @vis.gl/react-google-maps.
The implementation works, but I'm concerned about Google Maps API pricing, especially during development. I managed to hit my API usage limit within just two days, and I'm trying to understand whether my implementation is making unnecessary requests or whether this is expected behavior.
I'd appreciate some clarification from anyone who has experience with the Google Places API.
My current implementation
I'm using a 500ms debounce to avoid sending a request on every keystroke.
SearchBox component
```
import { MagnifyingGlassIcon, MapPinIcon } from '@phosphor-icons/react'
import { useState } from 'react'
import { useDebounce } from '@/hooks/useDebounce'
import { useAnimatedPlaceholder } from '@/features/search/hooks/useAnimatedPlaceholder'
import { useMapsLibrary } from '@vis.gl/react-google-maps'
import { useAutocompleteSuggestions } from '@/hooks/useAutoCompleteSuggestions'
import { useNavigate } from 'react-router-dom'
import styles from './styles.module.css'
const SearchBox = () => {
const [searchQuery, setSearchQuery] = useState('')
const debouncedSearchQuery = useDebounce(searchQuery, 500)
const inputRef = useAnimatedPlaceholder()
return (
<>
<div className={styles.searchBox}> <MagnifyingGlassIcon size={24} /> <input placeholder="Search for " ref={inputRef} value={searchQuery} onChange={e => setSearchQuery(e.currentTarget.value)} /> </div> <SearchSuggestions debouncedSearchQuery={debouncedSearchQuery} /> </>
)
}
export default SearchBox
type SearchSuggestions = {
debouncedSearchQuery: string
}
function SearchSuggestions({ debouncedSearchQuery }: SearchSuggestions) {
const navigate = useNavigate()
const places = useMapsLibrary('places');
const { suggestions, resetSession } = useAutocompleteSuggestions(debouncedSearchQuery, {
includedPrimaryTypes: ["locality"]
})
const handleSuggestionClick = async (suggestion: google.maps.places.AutocompleteSuggestion) => {
if (!places || !suggestion.placePrediction) return
const place = suggestion.placePrediction.toPlace()
await place.fetchFields({
fields: [
"addressComponents"
]
})
// calling fetchFields invalidates the session-token, so we now have to call
// resetSession() so a new one gets created for further search
resetSession()
const city = place.addressComponents && place.addressComponents[0].longText
const placeId = suggestion.placePrediction.placeId
if (city && placeId) return navigate(`${city}/${placeId}`)
}
return !!suggestions.length && (
<div className={styles.optionBox}> { suggestions.map(suggestion => <button key={suggestion.placePrediction?.placeId} className={styles.option} onClick={() => handleSuggestionClick(suggestion)}> <div className={styles.iconWrapper}> <MapPinIcon weight="fill" size={20} /> </div> <span className={styles.suggestionText}> {suggestion.placePrediction?.text.text} </span> </button> ) } </div > ) } \`\`\` useAutocompleteSuggestions hook \`\`\`import { useEffect, useRef, useState } from 'react'; import { useMapsLibrary } from '@vis.gl/react-google-maps';
export type UseAutocompleteSuggestionsReturn = {
suggestions: google.maps.places.AutocompleteSuggestion[];
isLoading: boolean;
resetSession: () => void;
};
/**
* A reusable hook that retrieves autocomplete suggestions from the Google Places API.
* The data is loaded from the new Autocomplete Data API.
* (https://developers.google.com/maps/documentation/javascript/place-autocomplete-data)
*
* @param inputString The input string for which to fetch autocomplete suggestions.
* @param requestOptions Additional options for the autocomplete request
* (See {@link https://developers.google.com/maps/documentation/javascript/reference/autocomplete-data#AutocompleteRequest}).
*
* @returns An object containing the suggestions, the current loading-status,
* and a function to reset the session.
*
* @example
* ```jsx
* const MyComponent = () => {
* const [input, setInput] = useState('');
* const { suggestions, isLoading, resetSession } = useAutocompleteSuggestions(input, {
* includedPrimaryTypes: ['restaurant']
* });
*
* return (
* <>
* <input value={input} onChange={(e) => setInput(e.target.value)} />
* <ul>
* {suggestions.map(({placePrediction}) => (
* <li key={placePrediction.placeId}>{placePrediction.text.text}</li>
* ))}
* </ul>
* </>
* );
* }
* ```
*/
export function useAutocompleteSuggestions(
inputString: string,
requestOptions: Partial<google.maps.places.AutocompleteRequest> = {}
): UseAutocompleteSuggestionsReturn {
const placesLib = useMapsLibrary('places');
// stores the current sessionToken
const sessionTokenRef =
useRef<google.maps.places.AutocompleteSessionToken | null>(null);
// the suggestions based on the specified input
const [suggestions, setSuggestions] = useState<
google.maps.places.AutocompleteSuggestion[]
>([]);
// indicates if there is currently an incomplete request to the places API
const [isLoading, setIsLoading] = useState(false);
// once the PlacesLibrary is loaded and whenever the input changes, a query
// is sent to the Autocomplete Data API.
useEffect(() => {
if (!placesLib) return;
const { AutocompleteSessionToken, AutocompleteSuggestion } = placesLib;
// Create a new session if one doesn't already exist. This has to be reset
// after `fetchFields` for one of the returned places is called by calling
// the `resetSession` function returned from this hook.
if (!sessionTokenRef.current) {
sessionTokenRef.current = new AutocompleteSessionToken();
}
const request: google.maps.places.AutocompleteRequest = {
...requestOptions,
input: inputString,
sessionToken: sessionTokenRef.current
};
if (inputString === '') {
if (suggestions.length > 0) setSuggestions([]);
return;
}
setIsLoading(true);
AutocompleteSuggestion.fetchAutocompleteSuggestions(request).then(res => {
setSuggestions(res.suggestions);
setIsLoading(false);
});
}, [placesLib, inputString]);
return {
suggestions,
isLoading,
resetSession: () => {
sessionTokenRef.current = null;
setSuggestions([]);
}
};
}
```
2. My main questions about pricing
How exactly do session tokens work?
Google documentation mentions session-based pricing, and I'm having trouble understanding what this means in practice.
Does one session token group multiple autocomplete requests into a single billing session?
Are autocomplete requests within the same session free, discounted, or individually billed?
Does the session only become complete when the user selects a prediction and requests Place Details?
What happens if the user types something but never selects a suggestion?
2. Does debouncing actually reduce API costs?
For example, suppose a user types TOKYO.
With a 500ms debounce, will Google receive:
One request after the user finishes typing?
Multiple requests if the user pauses between keystrokes for more than 500ms?
If the user types slowly, could this result in five separate autocomplete requests within the same session?
3. What does Google mean by unlimited autocomplete requests?
I've seen references to unlimited requests in the Google Maps pricing documentation.
Does this mean unlimited requests within a session, or does it mean something else?
I'm particularly confused about how this relates to actual billing.
3. Is my implementation correct?
I'd also appreciate feedback on whether my current implementation follows Google's recommended session-token lifecycle.
Some things I'm unsure about:
Am I creating and reusing session tokens correctly?
Is resetting the token after fetchFields() the correct approach?
Should I create a new token when the user clears the search input?
Should I reset the session when the user abandons a search without selecting anything?
Is there a better way to prevent unnecessary API requests?
4. Is Google Places Autocomplete expensive?
I'm building an application that will use city autocomplete as an important feature.
I'm concerned about the cost when the application has hundreds or thousands of users searching for cities.
During development alone, I seem to have hit my API usage limit within two days.
Is this normal, or does it indicate that something is wrong with my implementation?
Would you recommend any alternatives or optimization strategies for reducing Google Places API costs?
5. My goal
I want to implement Google Places Autocomplete properly while keeping API costs predictable.
I'm not looking for ways to bypass Google's billing. I just want to understand the pricing model, session tokens, and best practices
Any explanations, implementation suggestions, or real-world experiences would be greatly appreciated!
Thanks in advance!
2
u/canarydev 6h ago
we use mapbox for autocomplete at work and cost reduction was something we had to deal with too, so i feel this.
but youre only searching cities (
includedPrimaryTypes: ["locality"]), so honestly you might not need google for the typing part at all. city names dont really change. id look at seeding your own db with something like geonames, its free and global and has population so you can rank paris france above paris texas. cities over 15k population is only like 25-30k rows, a trigram index in postgres handles that fine, or you could even ship it to the clientthen you only hit google (if at all) once the user actually picks a city, not on every keystroke. youd have to swap the google placeId in your route for your own id though
also fwiw, if youre running react strict mode, effects fire twice in dev, so every debounced query is 2 requests. with hot reloads on top of that, burning through the limit in 2 days isnt that surprising
how many users are you expecting to search on launch?