Skip to content

Commit d762d9f

Browse files
committed
feat: implement session token management to optimize Google Places API billing
1 parent b188965 commit d762d9f

File tree

4 files changed

+198
-106
lines changed

4 files changed

+198
-106
lines changed

README.md

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ A customizable React Native TextInput component for Google Places Autocomplete u
1313
- RTL support
1414
- Multi-language support
1515
- TypeScript support
16+
- Session token support for reduced billing costs
1617

1718
## Preview
1819

@@ -150,6 +151,40 @@ const StyledExample = () => {
150151
```
151152
</details>
152153

154+
<details>
155+
<summary>Example Using Session Token</summary>
156+
157+
```javascript
158+
import { useRef } from 'react';
159+
160+
const SessionTokenExample = () => {
161+
const inputRef = useRef(null);
162+
163+
const handlePlaceSelect = (place, sessionToken) => {
164+
console.log('Selected place:', place);
165+
console.log('Session token used:', sessionToken);
166+
167+
// You can now use this same sessionToken when fetching place details
168+
// to benefit from reduced billing (session matching)
169+
fetchPlaceDetails(place.placeId, sessionToken);
170+
};
171+
172+
const fetchPlaceDetails = async (placeId, sessionToken) => {
173+
// Your code to fetch place details using the same sessionToken
174+
// ...
175+
};
176+
177+
return (
178+
<GooglePlacesTextInput
179+
ref={inputRef}
180+
apiKey="YOUR_GOOGLE_PLACES_API_KEY"
181+
onPlaceSelect={handlePlaceSelect}
182+
/>
183+
);
184+
};
185+
```
186+
</details>
187+
153188
## Props
154189

155190
| Prop | Type | Required | Default | Description |
@@ -174,22 +209,43 @@ const StyledExample = () => {
174209
| forceRTL | boolean | No | undefined | Force RTL layout direction |
175210
hideOnKeyboardDismiss | boolean | No | false | Hide suggestions when keyboard is dismissed
176211
| **Event Handlers** |
177-
| onPlaceSelect | (place: Place \| null) => void | Yes | - | Callback when place is selected |
212+
| onPlaceSelect | (place: Place \| null, sessionToken?: string) => void | Yes | - | Callback when place is selected |
178213
| onTextChange | (text: string) => void | No | - | Callback triggered on text input changes |
179214

215+
## Session Tokens and Billing
216+
217+
This component implements automatic session token management to help reduce your Google Places API billing costs:
218+
219+
- A session token is automatically generated when the component mounts
220+
- The same token is used for all autocomplete requests in a session
221+
- When a place is selected, the token is passed to your `onPlaceSelect` callback
222+
- Session tokens are automatically reset:
223+
- After a place is selected
224+
- When the input is manually cleared using the clear button
225+
- When the `clear()` method is called programmatically
226+
227+
**How this reduces costs:**
228+
When you make a series of autocomplete requests followed by a place details request using the same session token, Google Places API charges you only once for the entire session rather than for each individual request.
229+
230+
To benefit from this billing optimization:
231+
1. Use the session token passed to your `onPlaceSelect` handler when making subsequent place details requests
232+
2. No configuration is required - the feature works automatically
233+
180234
## Methods
181235

182236
The component exposes the following methods through refs:
183237

184238
- `clear()`: Clears the input and suggestions
185239
- `focus()`: Focuses the input field
240+
- `getSessionToken()`: Returns the current session token
186241

187242
```javascript
188243
const inputRef = useRef(null);
189244

190245
// Usage
191246
inputRef.current?.clear();
192247
inputRef.current?.focus();
248+
const token = inputRef.current?.getSessionToken();
193249
```
194250
195251
## Styling

example/App.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { SafeAreaView, StyleSheet, View, Text } from 'react-native';
22
import GooglePlacesTextInput from 'react-native-google-places-textinput';
33

44
const App = () => {
5-
const handleBasicPlaceSelect = (place) => {
5+
const handleBasicPlaceSelect = (place, token) => {
66
console.log('Basic example selected place:', place);
7+
console.log('Session token generated for this session:', token);
78
};
89

910
const handleStyledPlaceSelect = (place) => {
@@ -28,6 +29,7 @@ const App = () => {
2829
suggestionsContainer: {
2930
backgroundColor: '#FFFFFF',
3031
borderRadius: 12,
32+
maxHeight: 300,
3133
marginTop: 8,
3234
elevation: 3,
3335
shadowColor: '#000',
@@ -36,7 +38,8 @@ const App = () => {
3638
shadowRadius: 8,
3739
},
3840
suggestionItem: {
39-
padding: 16,
41+
paddingVertical: 10,
42+
paddingHorizontal: 16,
4043
borderBottomWidth: 1,
4144
borderBottomColor: '#F0F0F0',
4245
},

src/GooglePlacesTextInput.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,18 @@ const GooglePlacesTextInput = forwardRef(
4848
const [loading, setLoading] = useState(false);
4949
const [inputText, setInputText] = useState(value || '');
5050
const [showSuggestions, setShowSuggestions] = useState(false);
51+
const [sessionToken, setSessionToken] = useState(null);
5152
const debounceTimeout = useRef(null);
5253
const inputRef = useRef(null);
5354

55+
const generateSessionToken = () => {
56+
return generateUUID();
57+
};
58+
59+
// Initialize session token on mount
5460
useEffect(() => {
61+
setSessionToken(generateSessionToken());
62+
5563
return () => {
5664
if (debounceTimeout.current) {
5765
clearTimeout(debounceTimeout.current);
@@ -81,10 +89,12 @@ const GooglePlacesTextInput = forwardRef(
8189
setInputText('');
8290
setPredictions([]);
8391
setShowSuggestions(false);
92+
setSessionToken(generateSessionToken());
8493
},
8594
focus: () => {
8695
inputRef.current?.focus();
8796
},
97+
getSessionToken: () => sessionToken,
8898
}));
8999

90100
const fetchPredictions = async (text) => {
@@ -104,15 +114,19 @@ const GooglePlacesTextInput = forwardRef(
104114
if (apiKey || apiKey !== '') {
105115
headers['X-Goog-Api-Key'] = apiKey;
106116
}
117+
118+
const body = {
119+
input: processedText,
120+
languageCode,
121+
...(sessionToken && { sessionToken }),
122+
...(includedRegionCodes?.length > 0 && { includedRegionCodes }),
123+
...(types.length > 0 && { includedPrimaryTypes: types }),
124+
};
125+
107126
const response = await fetch(API_URL, {
108127
method: 'POST',
109128
headers,
110-
body: JSON.stringify({
111-
input: processedText,
112-
languageCode,
113-
...(includedRegionCodes?.length > 0 && { includedRegionCodes }),
114-
...(types.length > 0 && { includedPrimaryTypes: types }),
115-
}),
129+
body: JSON.stringify(body),
116130
});
117131

118132
const data = await response.json();
@@ -150,7 +164,12 @@ const GooglePlacesTextInput = forwardRef(
150164
setInputText(place.structuredFormat.mainText.text);
151165
setShowSuggestions(false);
152166
Keyboard.dismiss();
153-
onPlaceSelect(place); // Notify parent with selected place
167+
168+
// Pass both the place and session token to parent
169+
onPlaceSelect?.(place, sessionToken);
170+
171+
// Generate a new token after a place is selected
172+
setSessionToken(generateSessionToken());
154173
};
155174

156175
// Show suggestions on focus if text length > minCharsToFetch
@@ -288,6 +307,7 @@ const GooglePlacesTextInput = forwardRef(
288307
setShowSuggestions(false);
289308
onPlaceSelect?.(null);
290309
onTextChange?.('');
310+
setSessionToken(generateSessionToken());
291311
inputRef.current?.focus();
292312
}}
293313
>
@@ -399,4 +419,14 @@ const isRTLText = (text) => {
399419
return rtlRegex.test(text);
400420
};
401421

422+
// Helper function to generate UUID v4
423+
const generateUUID = () => {
424+
// RFC4122 version 4 compliant UUID
425+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
426+
var r = (Math.random() * 16) | 0,
427+
v = c === 'x' ? r : (r & 0x3) | 0x8;
428+
return v.toString(16);
429+
});
430+
};
431+
402432
export default GooglePlacesTextInput;

0 commit comments

Comments
 (0)