Skip to content

Commit 89a55ae

Browse files
committed
feat: add onFocus/onBlur events
1 parent 51679af commit 89a55ae

File tree

2 files changed

+81
-54
lines changed

2 files changed

+81
-54
lines changed

README.md

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ yarn add react-native-google-places-textinput
3838
## Prerequisites
3939

4040
1. **Enable the Places API (New)** in your Google Cloud Project
41+
4142
- This component specifically requires the new `Places API (New)`, not the legacy `Places API`
42-
- In the [Google Cloud Console](https://console.cloud.google.com/), go to `APIs & Services` > `Library` and search for "*Places API (New)*"
43+
- In the [Google Cloud Console](https://console.cloud.google.com/), go to `APIs & Services` > `Library` and search for "_Places API (New)_"
4344

4445
2. **Create an API key**
4546
- Go to "APIs & Services" > "Credentials" and create a new API key
@@ -48,6 +49,7 @@ yarn add react-native-google-places-textinput
4849
## Usage
4950

5051
### Basic Example
52+
5153
```javascript
5254
import GooglePlacesTextInput from 'react-native-google-places-textinput';
5355

@@ -86,6 +88,7 @@ const ConfiguredExample = () => {
8688
);
8789
};
8890
```
91+
8992
</details>
9093

9194
<details>
@@ -122,14 +125,14 @@ const StyledExample = () => {
122125
secondary: {
123126
fontSize: 14,
124127
color: '#666',
125-
}
128+
},
126129
},
127130
loadingIndicator: {
128131
color: '#999',
129132
},
130133
placeholder: {
131134
color: '#999',
132-
}
135+
},
133136
};
134137

135138
return (
@@ -142,6 +145,7 @@ const StyledExample = () => {
142145
);
143146
};
144147
```
148+
145149
</details>
146150

147151
<details>
@@ -151,8 +155,8 @@ const StyledExample = () => {
151155
const PlaceDetailsExample = () => {
152156
const handlePlaceSelect = (place) => {
153157
console.log('Selected place:', place);
154-
155-
// Access detailed place information
158+
159+
// Access detailed place information
156160
if (place.details) {
157161
console.log(place.details);
158162
}
@@ -174,22 +178,20 @@ const PlaceDetailsExample = () => {
174178
'location',
175179
'viewport',
176180
'photos',
177-
'types'
181+
'types',
178182
]}
179183
/>
180184
);
181185
};
182186
```
187+
183188
</details>
184189

185190
<details>
186191
<summary>Example embed in vertical ScrollView</summary>
187192

188193
```javascript
189-
<ScrollView
190-
keyboardShouldPersistTaps="handled"
191-
nestedScrollEnabled={true}
192-
>
194+
<ScrollView keyboardShouldPersistTaps="handled" nestedScrollEnabled={true}>
193195
<Text>Fill in your address</Text>
194196
<GooglePlacesTextInput
195197
apiKey="YOUR_GOOGLE_PLACES_API_KEY"
@@ -199,41 +201,44 @@ const PlaceDetailsExample = () => {
199201
/>
200202
</ScrollView>
201203
```
204+
202205
</details>
203206

204207
## Props
205208

206-
| Prop | Type | Required | Default | Description |
207-
|------|------|----------|---------|-------------|
208-
| **Basic Configuration** |
209-
| apiKey | string | Yes | - | Your Google Places API key |
210-
| value | string | No | - | Controlled input value |
211-
| placeHolderText | string | No | - | Placeholder text for the input |
212-
| onPlaceSelect | (place: Place, sessionToken?: string) => void | Yes | - | Callback when a place is selected |
213-
| onTextChange | (text: string) => void | No | - | Callback when input text changes |
214-
| **Search Configuration** |
215-
| proxyUrl | string | No | - | Custom proxy URL for API requests (Required for Expo web) |
216-
| languageCode | string | No | - | Language code (e.g., 'en', 'fr') |
217-
| includedRegionCodes | string[] | No | - | Array of region codes to filter results |
218-
| types | string[] | No | [] | Array of place types to filter |
219-
| biasPrefixText | (text: string) => string | No | - | Optional function to modify the input text before sending to the Places API |
220-
| minCharsToFetch | number | No | 1 | Minimum characters before triggering search |
221-
| debounceDelay | number | No | 200 | Delay in milliseconds before triggering search |
209+
| Prop | Type | Required | Default | Description |
210+
| ------------------------------- | -------------------------------------------------------------- | -------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
211+
| **Basic Configuration** |
212+
| apiKey | string | Yes | - | Your Google Places API key |
213+
| value | string | No | - | Controlled input value |
214+
| placeHolderText | string | No | - | Placeholder text for the input |
215+
| onPlaceSelect | (place: Place, sessionToken?: string) => void | Yes | - | Callback when a place is selected |
216+
| onTextChange | (text: string) => void | No | - | Callback when input text changes |
217+
| onFocus | (event: NativeSyntheticEvent<TextInputFocusEventData>) => void | No | - | Callback when input is focused |
218+
| onBlur | (event: NativeSyntheticEvent<TextInputFocusEventData>) => void | No | - | Callback when input is blurred |
219+
| **Search Configuration** |
220+
| proxyUrl | string | No | - | Custom proxy URL for API requests (Required for Expo web) |
221+
| languageCode | string | No | - | Language code (e.g., 'en', 'fr') |
222+
| includedRegionCodes | string[] | No | - | Array of region codes to filter results |
223+
| types | string[] | No | [] | Array of place types to filter |
224+
| biasPrefixText | (text: string) => string | No | - | Optional function to modify the input text before sending to the Places API |
225+
| minCharsToFetch | number | No | 1 | Minimum characters before triggering search |
226+
| debounceDelay | number | No | 200 | Delay in milliseconds before triggering search |
222227
| **Place Details Configuration** |
223-
| fetchDetails | boolean | No | false | Automatically fetch place details when a place is selected |
224-
| detailsProxyUrl | string | No | null | Custom proxy URL for place details requests (Required on Expo web)|
225-
| detailsFields | string[] | No | ['displayName', 'formattedAddress', 'location', 'id'] | Array of fields to include in the place details response. see [Valid Fields](https://developers.google.com/maps/documentation/places/web-service/place-details#fieldmask) |
226-
| **UI Customization** |
227-
| showLoadingIndicator | boolean | No | true | Show loading spinner during API requests |
228-
| showClearButton | boolean | No | true | Show clear (×) button when input has text |
229-
| forceRTL | boolean | No | - | Force RTL layout (auto-detected if not provided) |
230-
| style | GooglePlacesTextInputStyles | No | {} | Custom styling object |
231-
| hideOnKeyboardDismiss | boolean | No | false | Hide suggestions when keyboard is dismissed |
232-
| scrollEnabled | boolean | No | true | Enable/disable scrolling in the suggestions list |
233-
| nestedScrollEnabled | boolean | No | true | Enable/disable nested scrolling for the suggestions list |
234-
| **Error Handling & Debugging** |
235-
| onError | (error: any) => void | No | - | Callback when API errors occur |
236-
| enableDebug | boolean | No | false | Enable detailed console logging for troubleshooting |
228+
| fetchDetails | boolean | No | false | Automatically fetch place details when a place is selected |
229+
| detailsProxyUrl | string | No | null | Custom proxy URL for place details requests (Required on Expo web) |
230+
| detailsFields | string[] | No | ['displayName', 'formattedAddress', 'location', 'id'] | Array of fields to include in the place details response. see [Valid Fields](https://developers.google.com/maps/documentation/places/web-service/place-details#fieldmask) |
231+
| **UI Customization** |
232+
| showLoadingIndicator | boolean | No | true | Show loading spinner during API requests |
233+
| showClearButton | boolean | No | true | Show clear (×) button when input has text |
234+
| forceRTL | boolean | No | - | Force RTL layout (auto-detected if not provided) |
235+
| style | GooglePlacesTextInputStyles | No | {} | Custom styling object |
236+
| hideOnKeyboardDismiss | boolean | No | false | Hide suggestions when keyboard is dismissed |
237+
| scrollEnabled | boolean | No | true | Enable/disable scrolling in the suggestions list |
238+
| nestedScrollEnabled | boolean | No | true | Enable/disable nested scrolling for the suggestions list |
239+
| **Error Handling & Debugging** |
240+
| onError | (error: any) => void | No | - | Callback when API errors occur |
241+
| enableDebug | boolean | No | false | Enable detailed console logging for troubleshooting |
237242

238243
## Place Details Fetching
239244

@@ -249,6 +254,7 @@ You can automatically fetch detailed place information when a user selects a pla
249254
```
250255

251256
When `fetchDetails` is enabled:
257+
252258
1. The component fetches place details immediately when a user selects a place suggestion
253259
2. The details are attached to the place object passed to your `onPlaceSelect` callback in the `details` property
254260
3. Use the `detailsFields` prop to specify which fields to include in the response, reducing API costs
@@ -319,7 +325,7 @@ type Styles = {
319325
placeholder?: {
320326
color?: string;
321327
};
322-
}
328+
};
323329
```
324330
325331
For detailed styling examples and a complete guide, see our [Styling Guide](./docs/styling-guide.md).
@@ -336,4 +342,4 @@ MIT
336342
337343
Written by Amit Palomo
338344
339-
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
345+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)

src/GooglePlacesTextInput.tsx

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,14 @@ import {
55
useRef,
66
useState,
77
} from 'react';
8-
import type { StyleProp, TextStyle, ViewStyle } from 'react-native';
8+
import type {
9+
StyleProp,
10+
TextStyle,
11+
ViewStyle,
12+
TextInputProps,
13+
TextInputFocusEventData,
14+
NativeSyntheticEvent,
15+
} from 'react-native';
916
import {
1017
ActivityIndicator,
1118
FlatList,
@@ -72,7 +79,9 @@ interface GooglePlacesTextInputStyles {
7279
};
7380
}
7481

75-
interface GooglePlacesTextInputProps {
82+
type TextInputInheritedProps = Pick<TextInputProps, 'onFocus' | 'onBlur'>;
83+
84+
interface GooglePlacesTextInputProps extends TextInputInheritedProps {
7685
apiKey: string;
7786
value?: string;
7887
placeHolderText?: string;
@@ -139,6 +148,8 @@ const GooglePlacesTextInput = forwardRef<
139148
detailsFields = [],
140149
onError,
141150
enableDebug = false,
151+
onFocus,
152+
onBlur,
142153
},
143154
ref
144155
) => {
@@ -398,7 +409,11 @@ const GooglePlacesTextInput = forwardRef<
398409
setSessionToken(generateSessionToken());
399410
};
400411

401-
const handleFocus = (): void => {
412+
const handleFocus = (
413+
event: NativeSyntheticEvent<TextInputFocusEventData>
414+
): void => {
415+
onFocus?.(event);
416+
402417
if (skipNextFocusFetch.current) {
403418
skipNextFocusFetch.current = false;
404419
return;
@@ -409,6 +424,20 @@ const GooglePlacesTextInput = forwardRef<
409424
}
410425
};
411426

427+
const handleBlur = (
428+
event: NativeSyntheticEvent<TextInputFocusEventData>
429+
): void => {
430+
onBlur?.(event);
431+
432+
setTimeout(() => {
433+
if (suggestionPressing.current) {
434+
suggestionPressing.current = false;
435+
} else {
436+
setShowSuggestions(false);
437+
}
438+
}, 10);
439+
};
440+
412441
const renderSuggestion = ({ item }: { item: PredictionItem }) => {
413442
const { mainText, secondaryText } = item.placePrediction.structuredFormat;
414443

@@ -521,15 +550,7 @@ const GooglePlacesTextInput = forwardRef<
521550
value={inputText}
522551
onChangeText={handleTextChange}
523552
onFocus={handleFocus}
524-
onBlur={() => {
525-
setTimeout(() => {
526-
if (suggestionPressing.current) {
527-
suggestionPressing.current = false;
528-
} else {
529-
setShowSuggestions(false);
530-
}
531-
}, 10);
532-
}}
553+
onBlur={handleBlur}
533554
clearButtonMode="never" // Disable iOS native clear button
534555
/>
535556

0 commit comments

Comments
 (0)