diff --git a/package-lock.json b/package-lock.json
index dfdf6fd28836..e2ff69cad626 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -107,7 +107,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.28.0",
"react-native-get-random-values": "^1.11.0",
- "react-native-google-places-autocomplete": "2.5.6",
+ "react-native-google-places-autocomplete": "2.6.4",
"react-native-haptic-feedback": "^2.3.3",
"react-native-image-picker": "^7.1.2",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8a1c3b7359977462cdd4e63b09ea45afbd3d77c7",
@@ -33859,20 +33859,27 @@
}
},
"node_modules/react-native-google-places-autocomplete": {
- "version": "2.5.6",
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/react-native-google-places-autocomplete/-/react-native-google-places-autocomplete-2.6.4.tgz",
+ "integrity": "sha512-/DjXEa20doSJYiykqLbTAPL3GtIHVRdjpAoPpxnLXK6uFi103wdqXZoqXnKdqlov/lJLIO8Wex6GIu6MZz3Osw==",
"license": "MIT",
"dependencies": {
"lodash.debounce": "^4.0.8",
- "prop-types": "^15.7.2",
- "qs": "~6.9.1"
+ "qs": "^6.14.1",
+ "react-native-uuid": "^2.0.3"
},
"peerDependencies": {
"react-native": ">= 0.59"
}
},
"node_modules/react-native-google-places-autocomplete/node_modules/qs": {
- "version": "6.9.7",
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
"engines": {
"node": ">=0.6"
},
@@ -34534,6 +34541,16 @@
"react-native": "*"
}
},
+ "node_modules/react-native-uuid": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/react-native-uuid/-/react-native-uuid-2.0.3.tgz",
+ "integrity": "sha512-f/YfIS2f5UB+gut7t/9BKGSCYbRA9/74A5R1MDp+FLYsuS+OSWoiM/D8Jko6OJB6Jcu3v6ONuddvZKHdIGpeiw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0",
+ "npm": ">=6.0.0"
+ }
+ },
"node_modules/react-native-view-shot": {
"version": "4.0.0",
"license": "MIT",
diff --git a/package.json b/package.json
index 73067434d827..26f5ac830707 100644
--- a/package.json
+++ b/package.json
@@ -170,7 +170,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "2.28.0",
"react-native-get-random-values": "^1.11.0",
- "react-native-google-places-autocomplete": "2.5.6",
+ "react-native-google-places-autocomplete": "2.6.4",
"react-native-haptic-feedback": "^2.3.3",
"react-native-image-picker": "^7.1.2",
"react-native-image-size": "git+https://github.com/Expensify/react-native-image-size#8a1c3b7359977462cdd4e63b09ea45afbd3d77c7",
diff --git a/patches/react-native-google-places-autocomplete/details.md b/patches/react-native-google-places-autocomplete/details.md
index 3e6c08def754..9472b3b05f5a 100644
--- a/patches/react-native-google-places-autocomplete/details.md
+++ b/patches/react-native-google-places-autocomplete/details.md
@@ -1,13 +1,112 @@
# `react-native-google-places-autocomplete` patches
-### [react-native-google-places-autocomplete+2.5.6+001+react-19-support.patch](react-native-google-places-autocomplete+2.5.6+001+react-19-support.patch)
+### [react-native-google-places-autocomplete+2.6.4+001+fix-tdz-crash.patch](react-native-google-places-autocomplete+2.6.4+001+fix-tdz-crash.patch)
- Reason:
-
+
```
- This patch supports for React 19 by removing propTypes.
+ Fixes TDZ crashes and reorders forward-referenced declarations in v2.6.4:
+
+ 1. `useRef(_request)` on line 161 references `_request` before its `const`
+ declaration on line 466, causing
+ `ReferenceError: Cannot access '_request' before initialization`.
+ Fix: replace the initial value with `null` (safe because
+ `requestRef.current` is reassigned to `_request` every render before
+ it can be invoked).
+
+ 2. `_disableRowLoaders` (a `const useCallback`) was declared on line 653
+ but referenced in the `useCallback` dependency arrays of
+ `_requestNearby` (line 450) and `getCurrentLocation` (line 629),
+ causing `ReferenceError: Cannot access '_disableRowLoaders' before
+ initialization`. Fix: move `_disableRowLoaders` above
+ `_requestNearby` so it is defined before first use.
+
+ 3. Several functions were declared after their callers, creating forward
+ references. While these only execute lazily (not during render), they
+ are reordered for clarity and consistency:
+ - `_renderDescription`, `hideListView`, `isNewFocusInAutocompleteResultList`,
+ `_onBlur`, `_onFocus` moved before `_onPress` (which calls them).
+ - `debounceData` moved before `_onChangeText` (which calls it).
+
+ 4. v2.6.4 added `hideListView(true)` at the start of `_onPress` in the
+ `fetchDetails` branch. This immediately hides the entire FlatList
+ before `_enableRowLoader` can display the per-row loading spinner,
+ causing the loading indicator to be invisible during place detail
+ fetches. Fix: remove `hideListView(true)` from the `fetchDetails`
+ branch of `_onPress`, restoring v2.5.6 behavior where the list
+ stays visible with the row spinner until the detail request completes.
+ The `hideListView(true)` calls in the `isCurrentLocation` and
+ predefined-place branches are left intact since those don't need
+ to show a row-level loading indicator.
+
+ 5. v2.6.4 changed the `_getFlatList` visibility condition from
+ `stateText !== ''` (v2.5.6) to `dataSource.length > 0`. This
+ prevents the FlatList from mounting when the user is typing but
+ results haven't arrived yet, so the `ListEmptyComponent` loading
+ spinner is never shown during in-flight requests. Fix: replace
+ `dataSource.length > 0` with the v2.5.6-style condition
+ `(stateText !== '' || predefinedPlaces.length > 0 || currentLocation === true)`
+ so the FlatList renders while results are loading. This matches
+ v2.5.6 production behavior where existing results stay visible
+ while new results load — `_request` does NOT clear `dataSource`
+ before sending the XHR, so previous results remain visible until
+ the new response arrives.
+
+ 6. v2.6.4 added `stateText` to the dependency array of the
+ query-change `useEffect` (the effect that reloads search when
+ `props.query` changes). This causes the cleanup function
+ (`_abortRequests`) to fire on every keystroke, aborting in-flight
+ XHRs before results can arrive. When the user types fast, results
+ from previous keystrokes never complete, `dataSource` stays empty,
+ and the `ListEmptyComponent` loading spinner flashes on every key.
+ Fix: remove `stateText` from the dependency array. Per-keystroke
+ requests are already handled by `_onChangeText` → `debounceData`,
+ and `_request` itself calls `_abortRequests()` at the top, so old
+ requests are properly aborted when a new one fires.
+
+ 7. In v2.6.4, `requestsRef` is a `useRef` that persists across renders
+ (unlike v2.5.6 where `_requests` was a plain `let` re-initialized
+ every render). This means `_abortRequests()` inside `_request` now
+ actually aborts the previous in-flight XHR. When the user types
+ faster than the API responds, each keystroke aborts the prior XHR
+ before it populates `dataSource`, keeping `dataSource` empty and
+ causing `ListEmptyComponent` to show the loading spinner on every
+ keystroke. Fix: only call `setListLoaderDisplayed(true)` in the
+ `onreadystatechange` handler when `resultsRef.current.length === 0`
+ (i.e., no prior results exist). When prior results already exist
+ they remain visible in the FlatList while the new request is
+ in-flight, matching v2.5.6 production behavior. Applied to both
+ `_request` and `_requestNearby`.
+
+ 8. When `resultsRef.current.length === 0` and the loading spinner is
+ enabled, `dataSource` may still contain predefined places (populated
+ by `buildRowsFromResults([])` when the user cleared the input).
+ Since `ListEmptyComponent` only renders when `data` is empty,
+ the loading spinner was invisible even though `listLoaderDisplayed`
+ was true. Fix: call `setDataSource([])` alongside
+ `setListLoaderDisplayed(true)` to clear stale predefined places
+ from the FlatList data, allowing `ListEmptyComponent` to render
+ the loading spinner. Applied to both `_request` and
+ `_requestNearby`.
+
+ 9. v2.6.4 replaced `TouchableHighlight` with `Pressable` for suggestion
+ rows AND added `onBlur={_onBlur}` to the Pressable. This causes two
+ problems on iOS:
+ (a) `Pressable` does not participate in the gesture responder system
+ the same way `TouchableHighlight` does. When a parent `ScrollView`
+ uses `keyboardShouldPersistTaps="handled"` (as `FormWrapper` does),
+ `TouchableHighlight` counts as "handling" the tap so the keyboard
+ stays up and `onPress` fires. `Pressable` does not, so the first
+ tap dismisses the keyboard without firing `onPress`, making
+ suggestion selection appear broken.
+ (b) The `onBlur={_onBlur}` on the Pressable fires when the keyboard
+ dismisses, calling `hideListView()` and `inputRef.current.blur()`,
+ which cascades to AddressSearch collapsing the list to zero height.
+ Fix: replace `Pressable` with `TouchableHighlight` (restoring v2.5.6
+ behavior) and remove the `onBlur` handler. `TouchableHighlight` uses
+ `underlayColor` for press feedback instead of function-style styles.
```
-
-- Upstream PR/issue: https://github.com/FaridSafi/react-native-google-places-autocomplete/pull/970
-- E/App issue: https://github.com/Expensify/App/issues/57511
-- PR introducing patch: https://github.com/Expensify/App/pull/60421
\ No newline at end of file
+
+- Upstream PR/issue: 🛑, library is unmaintained (https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/978)
+- E/App issue: https://github.com/Expensify/App/pull/82233
+- PR introducing patch: https://github.com/Expensify/App/pull/82233
diff --git a/patches/react-native-google-places-autocomplete/react-native-google-places-autocomplete+2.5.6+001+react-19-support.patch b/patches/react-native-google-places-autocomplete/react-native-google-places-autocomplete+2.5.6+001+react-19-support.patch
deleted file mode 100644
index 0c72fb0dac68..000000000000
--- a/patches/react-native-google-places-autocomplete/react-native-google-places-autocomplete+2.5.6+001+react-19-support.patch
+++ /dev/null
@@ -1,122 +0,0 @@
-diff --git a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
-index 99a2a13..f733e49 100644
---- a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
-+++ b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
-@@ -70,7 +70,56 @@ const defaultStyles = {
- powered: {},
- };
-
--export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
-+const defaultProps = {
-+ autoFillOnNotFound: false,
-+ currentLocation: false,
-+ currentLocationLabel: 'Current location',
-+ debounce: 0,
-+ disableScroll: false,
-+ enableHighAccuracyLocation: true,
-+ enablePoweredByContainer: true,
-+ fetchDetails: false,
-+ filterReverseGeocodingByTypes: [],
-+ GooglePlacesDetailsQuery: {},
-+ GooglePlacesSearchQuery: {
-+ rankby: 'distance',
-+ type: 'restaurant',
-+ },
-+ GoogleReverseGeocodingQuery: {},
-+ isRowScrollable: true,
-+ keyboardShouldPersistTaps: 'always',
-+ listHoverColor: '#ececec',
-+ listUnderlayColor: '#c8c7cc',
-+ listViewDisplayed: 'auto',
-+ keepResultsAfterBlur: false,
-+ minLength: 0,
-+ nearbyPlacesAPI: 'GooglePlacesSearch',
-+ numberOfLines: 1,
-+ onFail: () => {},
-+ onNotFound: () => {},
-+ onPress: () => {},
-+ onTimeout: () => console.warn('google places autocomplete: request timeout'),
-+ placeholder: '',
-+ predefinedPlaces: [],
-+ predefinedPlacesAlwaysVisible: false,
-+ query: {
-+ key: 'missing api key',
-+ language: 'en',
-+ types: 'geocode',
-+ },
-+ styles: {},
-+ suppressDefaultStyles: false,
-+ textInputHide: false,
-+ textInputProps: {},
-+ timeout: 20000,
-+};
-+
-+export const GooglePlacesAutocomplete = ({ ref, ...rest }) => {
-+ const props = {
-+ ...defaultProps,
-+ ...rest,
-+ };
-+
- let _results = [];
- let _requests = [];
-
-@@ -887,7 +936,7 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
- {props.children}
-
- );
--});
-+};
-
- GooglePlacesAutocomplete.propTypes = {
- autoFillOnNotFound: PropTypes.bool,
-@@ -944,50 +993,6 @@ GooglePlacesAutocomplete.propTypes = {
- timeout: PropTypes.number,
- };
-
--GooglePlacesAutocomplete.defaultProps = {
-- autoFillOnNotFound: false,
-- currentLocation: false,
-- currentLocationLabel: 'Current location',
-- debounce: 0,
-- disableScroll: false,
-- enableHighAccuracyLocation: true,
-- enablePoweredByContainer: true,
-- fetchDetails: false,
-- filterReverseGeocodingByTypes: [],
-- GooglePlacesDetailsQuery: {},
-- GooglePlacesSearchQuery: {
-- rankby: 'distance',
-- type: 'restaurant',
-- },
-- GoogleReverseGeocodingQuery: {},
-- isRowScrollable: true,
-- keyboardShouldPersistTaps: 'always',
-- listHoverColor: '#ececec',
-- listUnderlayColor: '#c8c7cc',
-- listViewDisplayed: 'auto',
-- keepResultsAfterBlur: false,
-- minLength: 0,
-- nearbyPlacesAPI: 'GooglePlacesSearch',
-- numberOfLines: 1,
-- onFail: () => {},
-- onNotFound: () => {},
-- onPress: () => {},
-- onTimeout: () => console.warn('google places autocomplete: request timeout'),
-- placeholder: '',
-- predefinedPlaces: [],
-- predefinedPlacesAlwaysVisible: false,
-- query: {
-- key: 'missing api key',
-- language: 'en',
-- types: 'geocode',
-- },
-- styles: {},
-- suppressDefaultStyles: false,
-- textInputHide: false,
-- textInputProps: {},
-- timeout: 20000,
--};
--
- GooglePlacesAutocomplete.displayName = 'GooglePlacesAutocomplete';
-
- export default { GooglePlacesAutocomplete };
diff --git a/patches/react-native-google-places-autocomplete/react-native-google-places-autocomplete+2.6.4+001+fix-tdz-crash.patch b/patches/react-native-google-places-autocomplete/react-native-google-places-autocomplete+2.6.4+001+fix-tdz-crash.patch
new file mode 100644
index 000000000000..3b28eb374cf5
--- /dev/null
+++ b/patches/react-native-google-places-autocomplete/react-native-google-places-autocomplete+2.6.4+001+fix-tdz-crash.patch
@@ -0,0 +1,302 @@
+diff --git a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
+index 9fedd7e..258a17a 100644
+--- a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
++++ b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
+@@ -17,11 +17,11 @@ import {
+ Image,
+ Keyboard,
+ Platform,
+- Pressable,
+ ScrollView,
+ StyleSheet,
+ Text,
+ TextInput,
++ TouchableHighlight,
+ View,
+ } from 'react-native';
+
+@@ -158,7 +158,7 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ const prevQueryStringRef = useRef(JSON.stringify(query));
+
+ // Store latest _request function - ensures debounced function always calls current version with latest closures
+- const requestRef = useRef(_request);
++ const requestRef = useRef(null);
+ const queryString = useMemo(() => JSON.stringify(query), [query]);
+
+ const [stateText, setStateText] = useState('');
+@@ -347,6 +347,16 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ return rowData;
+ };
+
++ const _disableRowLoaders = useCallback(() => {
++ for (let i = 0; i < resultsRef.current.length; i++) {
++ if (resultsRef.current[i].isLoading === true) {
++ resultsRef.current[i].isLoading = false;
++ }
++ }
++
++ setDataSource(buildRowsFromResults(resultsRef.current));
++ }, [buildRowsFromResults]);
++
+ // ==========================================================================
+ // API REQUEST FUNCTIONS
+ // ==========================================================================
+@@ -367,7 +377,13 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ request.ontimeout = onTimeout;
+ request.onreadystatechange = () => {
+ if (request.readyState !== 4) {
+- setListLoaderDisplayed(true);
++ // Only show the loading spinner when there are no prior results.
++ if (resultsRef.current.length === 0) {
++ setListLoaderDisplayed(true);
++ // Clear dataSource so FlatList's ListEmptyComponent can render
++ // the loading spinner (it only renders when data is empty).
++ setDataSource([]);
++ }
+ return;
+ }
+
+@@ -478,7 +494,15 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ request.ontimeout = onTimeout;
+ request.onreadystatechange = () => {
+ if (request.readyState !== 4) {
+- setListLoaderDisplayed(true);
++ // Only show the loading spinner when there are no prior results.
++ // When prior results exist they stay visible while the new request
++ // is in-flight, matching v2.5.6 production behavior.
++ if (resultsRef.current.length === 0) {
++ setListLoaderDisplayed(true);
++ // Clear dataSource so FlatList's ListEmptyComponent can render
++ // the loading spinner (it only renders when data is empty).
++ setDataSource([]);
++ }
+ return;
+ }
+
+@@ -650,15 +674,51 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ }
+ };
+
+- const _disableRowLoaders = useCallback(() => {
+- for (let i = 0; i < resultsRef.current.length; i++) {
+- if (resultsRef.current[i].isLoading === true) {
+- resultsRef.current[i].isLoading = false;
++ const _renderDescription = (rowData) => {
++ if (props.renderDescription) {
++ return props.renderDescription(rowData);
++ }
++
++ return rowData.description || rowData.formatted_address || rowData.name;
++ };
++
++ const hideListView = useCallback(
++ (force = false) => {
++ if (!keepResultsAfterBlur || force) {
++ setListWasDismissed(true);
++ setListViewDisplayed(false);
+ }
++ },
++ [keepResultsAfterBlur],
++ );
++
++ const isNewFocusInAutocompleteResultList = ({
++ relatedTarget,
++ currentTarget,
++ }) => {
++ if (!relatedTarget) return false;
++
++ let node = relatedTarget.parentNode;
++
++ while (node) {
++ if (node.id === 'result-list-id') return true;
++ node = node.parentNode;
+ }
+
+- setDataSource(buildRowsFromResults(resultsRef.current));
+- }, [buildRowsFromResults]);
++ return false;
++ };
++
++ const _onBlur = (e) => {
++ if (e && isNewFocusInAutocompleteResultList(e)) return;
++
++ hideListView();
++ inputRef?.current?.blur();
++ };
++
++ const _onFocus = () => {
++ setListWasDismissed(false);
++ setListViewDisplayed(true);
++ };
+
+ const _onPress = (rowData) => {
+ if (rowData.isPredefinedPlace !== true && fetchDetails === true) {
+@@ -667,7 +727,6 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ return;
+ }
+
+- hideListView(true);
+ Keyboard.dismiss();
+
+ _abortRequests();
+@@ -776,7 +835,21 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ }
+ };
+
++ // Debounce setup
++ const debounceData = useMemo(() => {
++ return debounce((text) => requestRef.current(text), debounceMs);
++ }, [debounceMs]);
++
+ const _onChangeText = (text) => {
++ // When the search text is cleared, immediately clear stale results so
++ // that the next search shows the loading spinner. We cannot rely on
++ // _request("") via debounce because a fast follow-up keystroke cancels
++ // the pending debounce call.
++ if (!text) {
++ resultsRef.current = [];
++ setDataSource(buildRowsFromResults([]));
++ }
++
+ setListWasDismissed(false);
+ setStateText(text);
+ debounceData(text);
+@@ -792,56 +865,10 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ }
+ };
+
+- const hideListView = useCallback(
+- (force = false) => {
+- if (!keepResultsAfterBlur || force) {
+- setListWasDismissed(true);
+- setListViewDisplayed(false);
+- }
+- },
+- [keepResultsAfterBlur],
+- );
+-
+- const isNewFocusInAutocompleteResultList = ({
+- relatedTarget,
+- currentTarget,
+- }) => {
+- if (!relatedTarget) return false;
+-
+- let node = relatedTarget.parentNode;
+-
+- while (node) {
+- if (node.id === 'result-list-id') return true;
+- node = node.parentNode;
+- }
+-
+- return false;
+- };
+-
+- const _onBlur = (e) => {
+- if (e && isNewFocusInAutocompleteResultList(e)) return;
+-
+- hideListView();
+- inputRef?.current?.blur();
+- };
+-
+- const _onFocus = () => {
+- setListWasDismissed(false);
+- setListViewDisplayed(true);
+- };
+-
+ // ==========================================================================
+ // RENDER FUNCTIONS
+ // ==========================================================================
+
+- const _renderDescription = (rowData) => {
+- if (props.renderDescription) {
+- return props.renderDescription(rowData);
+- }
+-
+- return rowData.description || rowData.formatted_address || rowData.name;
+- };
+-
+ const _getRowLoader = () => {
+ return ;
+ };
+@@ -894,19 +921,12 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ showsHorizontalScrollIndicator={false}
+ showsVerticalScrollIndicator={false}
+ >
+- [
++ _onPress(rowData)}
+- onBlur={_onBlur}
++ underlayColor={listUnderlayColor}
+ >
+ {
+ {_renderLoader(rowData)}
+ {_renderRowData(rowData, index)}
+
+-
++
+
+ );
+ };
+@@ -1010,15 +1030,17 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ return `item_${index}`;
+ };
+
+- // Show list if:
+- // 1. Platform is supported
+- // 2. There's data to show (dataSource has items)
+- // 3. listViewDisplayed is true OR we're in 'auto' mode (auto-shows when data exists)
++ // Show list when the user is actively searching, has predefined places, or
++ // uses current location. We check stateText instead of dataSource.length so
++ // that the FlatList mounts while results are still loading — this lets
++ // ListEmptyComponent render the loading spinner.
+ const isAutoMode =
+ listViewDisplayedProp === 'auto' || listViewDisplayedProp === undefined;
+ const shouldShowList =
+ supportedPlatform() &&
+- dataSource.length > 0 &&
++ (stateText !== '' ||
++ predefinedPlaces.length > 0 ||
++ currentLocation === true) &&
+ (listViewDisplayed === true || (isAutoMode && !listWasDismissed));
+
+ if (shouldShowList) {
+@@ -1078,11 +1100,6 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ // Keep requestRef updated
+ requestRef.current = _request;
+
+- // Debounce setup
+- const debounceData = useMemo(() => {
+- return debounce((text) => requestRef.current(text), debounceMs);
+- }, [debounceMs]);
+-
+ useEffect(() => {
+ return () => {
+ // Cleanup debounced function on unmount
+@@ -1106,7 +1123,13 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
+ return () => {
+ _abortRequests();
+ };
+- }, [queryString, debounceData, stateText, minLength, _abortRequests]);
++ // Note: stateText is intentionally excluded — per-keystroke requests are
++ // handled by _onChangeText → debounceData. Including stateText here
++ // causes the cleanup (_abortRequests) to fire on every keystroke, aborting
++ // in-flight XHRs before results arrive and making the loading spinner
++ // flash repeatedly.
++ // eslint-disable-next-line react-hooks/exhaustive-deps
++ }, [queryString, debounceData, minLength, _abortRequests]);
+
+ // Auto-show list when dataSource has items in 'auto' mode
+ useEffect(() => {
diff --git a/src/components/AddressSearch/index.tsx b/src/components/AddressSearch/index.tsx
index ff8bf9746bfe..3e5be6ec98bc 100644
--- a/src/components/AddressSearch/index.tsx
+++ b/src/components/AddressSearch/index.tsx
@@ -447,6 +447,7 @@ function AddressSearch({
description: [styles.googleSearchText],
separator: [styles.googleSearchSeparator, styles.overflowAuto],
container: [styles.mh100],
+ loader: [styles.dNone],
}}
numberOfLines={2}
isRowScrollable={false}
diff --git a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx
index 1a4558966668..5fed55b7c851 100644
--- a/src/pages/iou/request/step/IOURequestStepWaypoint.tsx
+++ b/src/pages/iou/request/step/IOURequestStepWaypoint.tsx
@@ -4,6 +4,7 @@ import type {TextInput} from 'react-native';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import AddressSearch from '@components/AddressSearch';
+import type {PredefinedPlace} from '@components/AddressSearch/types';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import Button from '@components/Button';
import FormProvider from '@components/Form/FormProvider';
@@ -37,7 +38,7 @@ import withWritableReportOrNotFound from './withWritableReportOrNotFound';
// Only grab the most recent 20 waypoints because that's all that is shown in the UI. This also puts them into the format of data
// that the google autocomplete component expects for it's "predefined places" feature.
-function recentWaypointsSelector(waypoints: RecentWaypoint[] = []) {
+function recentWaypointsSelector(waypoints: RecentWaypoint[] = []): PredefinedPlace[] {
return waypoints
.slice(0, CONST.RECENT_WAYPOINTS_NUMBER)
.filter((waypoint) => waypoint.keyForList?.includes(CONST.YOUR_LOCATION_TEXT) !== true)
@@ -48,6 +49,8 @@ function recentWaypointsSelector(waypoints: RecentWaypoint[] = []) {
location: {
lat: waypoint.lat ?? 0,
lng: waypoint.lng ?? 0,
+ latitude: waypoint.lat ?? 0,
+ longitude: waypoint.lng ?? 0,
},
},
}));