FaridSafi / react-native-google-places-autocomplete

Customizable Google Places autocomplete component for iOS and Android React-Native apps
MIT License
2.01k stars 853 forks source link

onPress not working on android #940

Open jahanzaib5244 opened 7 months ago

jahanzaib5244 commented 7 months ago

onPress not working on android

i am using a map and GooglePlacesAutocomplete input on ios it is working perfectly but on android i am unable to pick the location .

Please provide a FULLY REPRODUCIBLE example.

Click to expand! ```javascript const GooglePlacesInput: React.FC = ({onSelect, ref}) => { return ( { console.log('onFail'); }} /> ); { setGetAddress(false); const selectedRegion = { ...region, location: details?.formatted_address || '', latitude: details?.geometry?.location?.lat ?? userLocation?.latitude ?? 31.4482849, longitude: details?.geometry?.location?.lng ?? userLocation?.longitude ?? 74.3095673, }; setAddress(details?.formatted_address || ''); setRegion(selectedRegion); mapViewRef.current?.animateToRegion(selectedRegion, 1000); setvalue(pre => ({ ...pre, location: details?.formatted_address || '', latitude: details?.geometry?.location?.lat ?? userLocation?.latitude ?? 31.4482849, longitude: details?.geometry?.location?.lng ?? userLocation?.longitude ?? 74.3095673, })); }} /> ```

Please remember to remove you google API key from the code you provide here

Additional context

Add any other context about the problem here, screenshots etc

juzerdhinoj-dimiour commented 7 months ago

Facing this issue as well. Any resolutions yet?

jahanzaib5244 commented 7 months ago
index 99a2a13..6640f00 100644
--- a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
+++ b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
@@ -13,7 +13,6 @@ import React, {
 } from 'react';
 import {
   ActivityIndicator,
-  FlatList,
   Image,
   Keyboard,
   Platform,
@@ -22,8 +21,10 @@ import {
   StyleSheet,
   Text,
   TextInput,
+  TouchableOpacity,
   View,
 } from 'react-native';
+import { FlatList } from 'react-native-gesture-handler';

 const defaultStyles = {
   container: {
@@ -258,6 +259,7 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
   };

   const _onPress = (rowData) => {
+    console.log('pressed')
     if (rowData.isPredefinedPlace !== true && props.fetchDetails === true) {
       if (rowData.isLoading === true) {
         // already requesting
@@ -645,19 +647,11 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     return null;
   };

-  const _renderRow = (rowData = {}, index) => {
+  const _renderRow =useCallback((rowData = {}, index) => {
+    console.log('rendered')
     return (
-      <ScrollView
-        contentContainerStyle={
-          props.isRowScrollable ? { minWidth: '100%' } : { width: '100%' }
-        }
-        scrollEnabled={props.isRowScrollable}
-        keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
-        horizontal={true}
-        showsHorizontalScrollIndicator={false}
-        showsVerticalScrollIndicator={false}
-      >
         <Pressable
+        key={Math.random().toString(36)}
           style={({ hovered, pressed }) => [
             props.isRowScrollable ? { minWidth: '100%' } : { width: '100%' },
             {
@@ -682,9 +676,9 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
             {_renderRowData(rowData, index)}
           </View>
         </Pressable>
-      </ScrollView>
     );
-  };
+  },[props]);
+

   const _renderSeparator = (sectionID, rowID) => {
     if (rowID === dataSource.length - 1) {
@@ -785,46 +779,24 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     }
   };

-  const _getFlatList = () => {
-    const keyGenerator = () => Math.random().toString(36).substr(2, 10);
+  const _getFlatList =useCallback( () => {

     if (
-      supportedPlatform() &&
-      (stateText !== '' ||
-        props.predefinedPlaces.length > 0 ||
-        props.currentLocation === true) &&
+      stateText !== ''  &&
       listViewDisplayed === true
     ) {
       return (
-        <FlatList
-          nativeID='result-list-id'
-          scrollEnabled={!props.disableScroll}
-          style={[
-            props.suppressDefaultStyles ? {} : defaultStyles.listView,
-            props.styles.listView,
-          ]}
-          data={dataSource}
-          keyExtractor={keyGenerator}
-          extraData={[dataSource, props]}
-          ItemSeparatorComponent={_renderSeparator}
-          renderItem={({ item, index }) => _renderRow(item, index)}
-          ListEmptyComponent={
-            listLoaderDisplayed
-              ? props.listLoaderComponent
-              : stateText.length > props.minLength && props.listEmptyComponent
-          }
-          ListHeaderComponent={
-            props.renderHeaderComponent &&
-            props.renderHeaderComponent(stateText)
-          }
-          ListFooterComponent={_renderPoweredLogo}
-          {...props}
-        />
+        <View  style={{zIndex:0,}} >
+          {[...dataSource].slice(0,5).map((item,index)=>{
+            return _renderRow(item, index)
+          })}
+        </View>
+        
       );
     }

     return null;
-  };
+  },[dataSource]);

   let {
     onFocus,
@@ -835,6 +807,8 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     ...userProps
   } = props.textInputProps;
   const TextInputComp = InputComp || TextInput;
+  console.log('rendered')
+
   return (
     <View
       style={[
@@ -882,9 +856,9 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
           {_renderRightButton()}
         </View>
       )}
-      {props.inbetweenCompo}
+      {/* {props.inbetweenCompo} */}
       {_getFlatList()}
-      {props.children}
+      {/* {props.children} */}
     </View>
   );
 });

@juzerdhinoj-dimiour you can use this patch file and the issue will be resolved. I replaced Flatlist with map and it worked. Still not get why onPress was not working inside Flatlist

juzerdhinoj-dimiour commented 7 months ago

Will this disrupt the currently working iOS functionality?

index 99a2a13..6640f00 100644
--- a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
+++ b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
@@ -13,7 +13,6 @@ import React, {
 } from 'react';
 import {
   ActivityIndicator,
-  FlatList,
   Image,
   Keyboard,
   Platform,
@@ -22,8 +21,10 @@ import {
   StyleSheet,
   Text,
   TextInput,
+  TouchableOpacity,
   View,
 } from 'react-native';
+import { FlatList } from 'react-native-gesture-handler';

 const defaultStyles = {
   container: {
@@ -258,6 +259,7 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
   };

   const _onPress = (rowData) => {
+    console.log('pressed')
     if (rowData.isPredefinedPlace !== true && props.fetchDetails === true) {
       if (rowData.isLoading === true) {
         // already requesting
@@ -645,19 +647,11 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     return null;
   };

-  const _renderRow = (rowData = {}, index) => {
+  const _renderRow =useCallback((rowData = {}, index) => {
+    console.log('rendered')
     return (
-      <ScrollView
-        contentContainerStyle={
-          props.isRowScrollable ? { minWidth: '100%' } : { width: '100%' }
-        }
-        scrollEnabled={props.isRowScrollable}
-        keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
-        horizontal={true}
-        showsHorizontalScrollIndicator={false}
-        showsVerticalScrollIndicator={false}
-      >
         <Pressable
+        key={Math.random().toString(36)}
           style={({ hovered, pressed }) => [
             props.isRowScrollable ? { minWidth: '100%' } : { width: '100%' },
             {
@@ -682,9 +676,9 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
             {_renderRowData(rowData, index)}
           </View>
         </Pressable>
-      </ScrollView>
     );
-  };
+  },[props]);
+

   const _renderSeparator = (sectionID, rowID) => {
     if (rowID === dataSource.length - 1) {
@@ -785,46 +779,24 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     }
   };

-  const _getFlatList = () => {
-    const keyGenerator = () => Math.random().toString(36).substr(2, 10);
+  const _getFlatList =useCallback( () => {

     if (
-      supportedPlatform() &&
-      (stateText !== '' ||
-        props.predefinedPlaces.length > 0 ||
-        props.currentLocation === true) &&
+      stateText !== ''  &&
       listViewDisplayed === true
     ) {
       return (
-        <FlatList
-          nativeID='result-list-id'
-          scrollEnabled={!props.disableScroll}
-          style={[
-            props.suppressDefaultStyles ? {} : defaultStyles.listView,
-            props.styles.listView,
-          ]}
-          data={dataSource}
-          keyExtractor={keyGenerator}
-          extraData={[dataSource, props]}
-          ItemSeparatorComponent={_renderSeparator}
-          renderItem={({ item, index }) => _renderRow(item, index)}
-          ListEmptyComponent={
-            listLoaderDisplayed
-              ? props.listLoaderComponent
-              : stateText.length > props.minLength && props.listEmptyComponent
-          }
-          ListHeaderComponent={
-            props.renderHeaderComponent &&
-            props.renderHeaderComponent(stateText)
-          }
-          ListFooterComponent={_renderPoweredLogo}
-          {...props}
-        />
+        <View  style={{zIndex:0,}} >
+          {[...dataSource].slice(0,5).map((item,index)=>{
+            return _renderRow(item, index)
+          })}
+        </View>
+        
       );
     }

     return null;
-  };
+  },[dataSource]);

   let {
     onFocus,
@@ -835,6 +807,8 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     ...userProps
   } = props.textInputProps;
   const TextInputComp = InputComp || TextInput;
+  console.log('rendered')
+
   return (
     <View
       style={[
@@ -882,9 +856,9 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
           {_renderRightButton()}
         </View>
       )}
-      {props.inbetweenCompo}
+      {/* {props.inbetweenCompo} */}
       {_getFlatList()}
-      {props.children}
+      {/* {props.children} */}
     </View>
   );
 });

@juzerdhinoj-dimiour you can use this patch file and the issue will be resolved. I replaced Flatlist with map and it worked. Still not get why onPress was not working inside Flatlist

jahanzaib5244 commented 7 months ago

Will this disrupt the currently working iOS functionality?

index 99a2a13..6640f00 100644
--- a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
+++ b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js
@@ -13,7 +13,6 @@ import React, {
 } from 'react';
 import {
   ActivityIndicator,
-  FlatList,
   Image,
   Keyboard,
   Platform,
@@ -22,8 +21,10 @@ import {
   StyleSheet,
   Text,
   TextInput,
+  TouchableOpacity,
   View,
 } from 'react-native';
+import { FlatList } from 'react-native-gesture-handler';

 const defaultStyles = {
   container: {
@@ -258,6 +259,7 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
   };

   const _onPress = (rowData) => {
+    console.log('pressed')
     if (rowData.isPredefinedPlace !== true && props.fetchDetails === true) {
       if (rowData.isLoading === true) {
         // already requesting
@@ -645,19 +647,11 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     return null;
   };

-  const _renderRow = (rowData = {}, index) => {
+  const _renderRow =useCallback((rowData = {}, index) => {
+    console.log('rendered')
     return (
-      <ScrollView
-        contentContainerStyle={
-          props.isRowScrollable ? { minWidth: '100%' } : { width: '100%' }
-        }
-        scrollEnabled={props.isRowScrollable}
-        keyboardShouldPersistTaps={props.keyboardShouldPersistTaps}
-        horizontal={true}
-        showsHorizontalScrollIndicator={false}
-        showsVerticalScrollIndicator={false}
-      >
         <Pressable
+        key={Math.random().toString(36)}
           style={({ hovered, pressed }) => [
             props.isRowScrollable ? { minWidth: '100%' } : { width: '100%' },
             {
@@ -682,9 +676,9 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
             {_renderRowData(rowData, index)}
           </View>
         </Pressable>
-      </ScrollView>
     );
-  };
+  },[props]);
+

   const _renderSeparator = (sectionID, rowID) => {
     if (rowID === dataSource.length - 1) {
@@ -785,46 +779,24 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     }
   };

-  const _getFlatList = () => {
-    const keyGenerator = () => Math.random().toString(36).substr(2, 10);
+  const _getFlatList =useCallback( () => {

     if (
-      supportedPlatform() &&
-      (stateText !== '' ||
-        props.predefinedPlaces.length > 0 ||
-        props.currentLocation === true) &&
+      stateText !== ''  &&
       listViewDisplayed === true
     ) {
       return (
-        <FlatList
-          nativeID='result-list-id'
-          scrollEnabled={!props.disableScroll}
-          style={[
-            props.suppressDefaultStyles ? {} : defaultStyles.listView,
-            props.styles.listView,
-          ]}
-          data={dataSource}
-          keyExtractor={keyGenerator}
-          extraData={[dataSource, props]}
-          ItemSeparatorComponent={_renderSeparator}
-          renderItem={({ item, index }) => _renderRow(item, index)}
-          ListEmptyComponent={
-            listLoaderDisplayed
-              ? props.listLoaderComponent
-              : stateText.length > props.minLength && props.listEmptyComponent
-          }
-          ListHeaderComponent={
-            props.renderHeaderComponent &&
-            props.renderHeaderComponent(stateText)
-          }
-          ListFooterComponent={_renderPoweredLogo}
-          {...props}
-        />
+        <View  style={{zIndex:0,}} >
+          {[...dataSource].slice(0,5).map((item,index)=>{
+            return _renderRow(item, index)
+          })}
+        </View>
+        
       );
     }

     return null;
-  };
+  },[dataSource]);

   let {
     onFocus,
@@ -835,6 +807,8 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
     ...userProps
   } = props.textInputProps;
   const TextInputComp = InputComp || TextInput;
+  console.log('rendered')
+
   return (
     <View
       style={[
@@ -882,9 +856,9 @@ export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
           {_renderRightButton()}
         </View>
       )}
-      {props.inbetweenCompo}
+      {/* {props.inbetweenCompo} */}
       {_getFlatList()}
-      {props.children}
+      {/* {props.children} */}
     </View>
   );
 });

@juzerdhinoj-dimiour you can use this patch file and the issue will be resolved. I replaced Flatlist with map and it worked. Still not get why onPress was not working inside Flatlist

No, it will not reflect the ios.

juzerdhinoj-dimiour commented 7 months ago

@jahanzaib5244 Instead of the change list, can you please share the actual file? It will be a better reference point.

jahanzaib5244 commented 7 months ago

@jahanzaib5244 Instead of the change list, can you please share the actual file? It will be a better reference point.


import PropTypes from 'prop-types';
import Qs from 'qs';
import React, {
  forwardRef,
  useMemo,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
  useCallback,
} from 'react';
import {
  ActivityIndicator,
  Image,
  Keyboard,
  Platform,
  Pressable,
  StyleSheet,
  Text,
  TextInput,
  View,
} from 'react-native';

const defaultStyles = {
  container: {
    flex: 1,
  },
  textInputContainer: {
    flexDirection: 'row',
  },
  textInput: {
    backgroundColor: '#FFFFFF',
    height: 44,
    borderRadius: 5,
    paddingVertical: 5,
    paddingHorizontal: 10,
    fontSize: 15,
    flex: 1,
    marginBottom: 5,
  },
  listView: {},
  row: {
    backgroundColor: '#FFFFFF',
    padding: 13,
    minHeight: 44,
    flexDirection: 'row',
  },
  loader: {
    flexDirection: 'row',
    justifyContent: 'flex-end',
    height: 20,
  },
  description: {},
  separator: {
    height: StyleSheet.hairlineWidth,
    backgroundColor: '#c8c7cc',
  },
  poweredContainer: {
    justifyContent: 'flex-end',
    alignItems: 'center',
    borderBottomRightRadius: 5,
    borderBottomLeftRadius: 5,
    borderColor: '#c8c7cc',
    borderTopWidth: 0.5,
  },
  powered: {},
};

export const GooglePlacesAutocomplete = forwardRef((props, ref) => {
  let _results = [];
  let _requests = [];

  const hasNavigator = () => {
    if (navigator?.geolocation) {
      return true;
    } else {
      console.warn(
        'If you are using React Native v0.60.0+ you must follow these instructions to enable currentLocation: https://git.io/Jf4AR',
      );
      return false;
    }
  };

  const buildRowsFromResults = useCallback(
    (results, text) => {
      let res = [];
      const shouldDisplayPredefinedPlaces = text
        ? results.length === 0 && text.length === 0
        : results.length === 0;
      if (
        shouldDisplayPredefinedPlaces ||
        props.predefinedPlacesAlwaysVisible === true
      ) {
        res = [
          ...props.predefinedPlaces.filter(
            (place) => place?.description.length,
          ),
        ];

        if (props.currentLocation === true && hasNavigator()) {
          res.unshift({
            description: props.currentLocationLabel,
            isCurrentLocation: true,
          });
        }
      }

      res = res.map((place) => ({
        ...place,
        isPredefinedPlace: true,
      }));

      return [...res, ...results];
    },
    [
      props.currentLocation,
      props.currentLocationLabel,
      props.predefinedPlaces,
      props.predefinedPlacesAlwaysVisible,
    ],
  );

  const getRequestUrl = useCallback((requestUrl) => {
    if (requestUrl) {
      if (requestUrl.useOnPlatform === 'all') {
        return requestUrl.url;
      }
      if (requestUrl.useOnPlatform === 'web') {
        return Platform.select({
          web: requestUrl.url,
          default: 'https://maps.googleapis.com/maps/api',
        });
      }
    } else {
      return 'https://maps.googleapis.com/maps/api';
    }
  }, []);

  const getRequestHeaders = (requestUrl) => {
    return requestUrl?.headers || {};
  };

  const setRequestHeaders = (request, headers) => {
    Object.keys(headers).map((headerKey) =>
      request.setRequestHeader(headerKey, headers[headerKey]),
    );
  };

  const [stateText, setStateText] = useState('');
  const [dataSource, setDataSource] = useState(buildRowsFromResults([]));
  const [listViewDisplayed, setListViewDisplayed] = useState(
    props.listViewDisplayed === 'auto' ? false : props.listViewDisplayed,
  );
  const [url, setUrl] = useState(getRequestUrl(props.requestUrl));
  const [listLoaderDisplayed, setListLoaderDisplayed] = useState(false);

  const inputRef = useRef();

  useEffect(() => {
    setUrl(getRequestUrl(props.requestUrl));
  }, [getRequestUrl, props.requestUrl]);

  useEffect(() => {
    // This will load the search results after the query object ref gets changed
    _handleChangeText(stateText);
    return () => {
      _abortRequests();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [props.query]);

  useEffect(() => {
    // Update dataSource if props.predefinedPlaces changed
    setDataSource(buildRowsFromResults([]));
  }, [buildRowsFromResults, props.predefinedPlaces]);

  useImperativeHandle(ref, () => ({
    setAddressText: (address) => {
      setStateText(address);
    },
    getAddressText: () => stateText,
    blur: () => inputRef.current.blur(),
    focus: () => inputRef.current.focus(),
    isFocused: () => inputRef.current.isFocused(),
    clear: () => inputRef.current.clear(),
    getCurrentLocation,
  }));

  const requestShouldUseWithCredentials = () =>
    url === 'https://maps.googleapis.com/maps/api';

  const _abortRequests = () => {
    _requests.map((i) => {
      i.onreadystatechange = null;
      i.abort();
    });
    _requests = [];
  };

  const supportedPlatform = () => {
    if (Platform.OS === 'web' && !props.requestUrl) {
      console.warn(
        'This library cannot be used for the web unless you specify the requestUrl prop. See https://git.io/JflFv for more for details.',
      );
      return false;
    } else {
      return true;
    }
  };

  const getCurrentLocation = () => {
    let options = {
      enableHighAccuracy: false,
      timeout: 20000,
      maximumAge: 1000,
    };

    if (props.enableHighAccuracyLocation && Platform.OS === 'android') {
      options = {
        enableHighAccuracy: true,
        timeout: 20000,
      };
    }
    const getCurrentPosition =
      navigator.geolocation.getCurrentPosition ||
      navigator.geolocation.default.getCurrentPosition;

    getCurrentPosition &&
      getCurrentPosition(
        (position) => {
          if (props.nearbyPlacesAPI === 'None') {
            let currentLocation = {
              description: props.currentLocationLabel,
              geometry: {
                location: {
                  lat: position.coords.latitude,
                  lng: position.coords.longitude,
                },
              },
            };

            _disableRowLoaders();
            props.onPress(currentLocation, currentLocation);
          } else {
            _requestNearby(position.coords.latitude, position.coords.longitude);
          }
        },
        (error) => {
          _disableRowLoaders();
          console.error(error.message);
        },
        options,
      );
  };

  const _onPress = (rowData) => {
    if (rowData.isPredefinedPlace !== true && props.fetchDetails === true) {
      if (rowData.isLoading === true) {
        // already requesting
        return;
      }

      Keyboard.dismiss();

      _abortRequests();

      // display loader
      _enableRowLoader(rowData);

      // fetch details
      const request = new XMLHttpRequest();
      _requests.push(request);
      request.timeout = props.timeout;
      request.ontimeout = props.onTimeout;
      request.onreadystatechange = () => {
        if (request.readyState !== 4) return;

        if (request.status === 200) {
          const responseJSON = JSON.parse(request.responseText);

          if (responseJSON.status === 'OK') {
            // if (_isMounted === true) {
            const details = responseJSON.result;
            _disableRowLoaders();
            _onBlur();

            setStateText(_renderDescription(rowData));

            delete rowData.isLoading;
            props.onPress(rowData, details);
            // }
          } else {
            _disableRowLoaders();

            if (props.autoFillOnNotFound) {
              setStateText(_renderDescription(rowData));
              delete rowData.isLoading;
            }

            if (!props.onNotFound) {
              console.warn(
                'google places autocomplete: ' + responseJSON.status,
              );
            } else {
              props.onNotFound(responseJSON);
            }
          }
        } else {
          _disableRowLoaders();

          if (!props.onFail) {
            console.warn(
              'google places autocomplete: request could not be completed or has been aborted',
            );
          } else {
            props.onFail('request could not be completed or has been aborted');
          }
        }
      };

      request.open(
        'GET',
        `${url}/place/details/json?` +
          Qs.stringify({
            key: props.query.key,
            placeid: rowData.place_id,
            language: props.query.language,
            ...props.GooglePlacesDetailsQuery,
          }),
      );

      request.withCredentials = requestShouldUseWithCredentials();
      setRequestHeaders(request, getRequestHeaders(props.requestUrl));

      request.send();
    } else if (rowData.isCurrentLocation === true) {
      // display loader
      _enableRowLoader(rowData);

      setStateText(_renderDescription(rowData));

      delete rowData.isLoading;
      getCurrentLocation();
    } else {
      setStateText(_renderDescription(rowData));

      _onBlur();
      delete rowData.isLoading;
      let predefinedPlace = _getPredefinedPlace(rowData);

      // sending predefinedPlace as details for predefined places
      props.onPress(predefinedPlace, predefinedPlace);
    }
  };

  const _enableRowLoader = (rowData) => {
    let rows = buildRowsFromResults(_results);
    for (let i = 0; i < rows.length; i++) {
      if (
        rows[i].place_id === rowData.place_id ||
        (rows[i].isCurrentLocation === true &&
          rowData.isCurrentLocation === true)
      ) {
        rows[i].isLoading = true;
        setDataSource(rows);
        break;
      }
    }
  };

  const _disableRowLoaders = () => {
    // if (_isMounted === true) {
    for (let i = 0; i < _results.length; i++) {
      if (_results[i].isLoading === true) {
        _results[i].isLoading = false;
      }
    }

    setDataSource(buildRowsFromResults(_results));
    // }
  };

  const _getPredefinedPlace = (rowData) => {
    if (rowData.isPredefinedPlace !== true) {
      return rowData;
    }

    for (let i = 0; i < props.predefinedPlaces.length; i++) {
      if (props.predefinedPlaces[i].description === rowData.description) {
        return props.predefinedPlaces[i];
      }
    }

    return rowData;
  };

  const _filterResultsByTypes = (unfilteredResults, types) => {
    if (types.length === 0) return unfilteredResults;

    const results = [];
    for (let i = 0; i < unfilteredResults.length; i++) {
      let found = false;

      for (let j = 0; j < types.length; j++) {
        if (unfilteredResults[i].types.indexOf(types[j]) !== -1) {
          found = true;
          break;
        }
      }

      if (found === true) {
        results.push(unfilteredResults[i]);
      }
    }
    return results;
  };

  const _requestNearby = (latitude, longitude) => {
    _abortRequests();

    if (
      latitude !== undefined &&
      longitude !== undefined &&
      latitude !== null &&
      longitude !== null
    ) {
      const request = new XMLHttpRequest();
      _requests.push(request);
      request.timeout = props.timeout;
      request.ontimeout = props.onTimeout;
      request.onreadystatechange = () => {
        if (request.readyState !== 4) {
          setListLoaderDisplayed(true);
          return;
        }

        setListLoaderDisplayed(false);
        if (request.status === 200) {
          const responseJSON = JSON.parse(request.responseText);

          _disableRowLoaders();

          if (typeof responseJSON.results !== 'undefined') {
            // if (_isMounted === true) {
            var results = [];
            if (props.nearbyPlacesAPI === 'GoogleReverseGeocoding') {
              results = _filterResultsByTypes(
                responseJSON.results,
                props.filterReverseGeocodingByTypes,
              );
            } else {
              results = responseJSON.results;
            }

            setDataSource(buildRowsFromResults(results));
            // }
          }
          if (typeof responseJSON.error_message !== 'undefined') {
            if (!props.onFail)
              console.warn(
                'google places autocomplete: ' + responseJSON.error_message,
              );
            else {
              props.onFail(responseJSON.error_message);
            }
          }
        } else {
          // console.warn("google places autocomplete: request could not be completed or has been aborted");
        }
      };

      let requestUrl = '';
      if (props.nearbyPlacesAPI === 'GoogleReverseGeocoding') {
        // your key must be allowed to use Google Maps Geocoding API
        requestUrl =
          `${url}/geocode/json?` +
          Qs.stringify({
            latlng: latitude + ',' + longitude,
            key: props.query.key,
            ...props.GoogleReverseGeocodingQuery,
          });
      } else {
        requestUrl =
          `${url}/place/nearbysearch/json?` +
          Qs.stringify({
            location: latitude + ',' + longitude,
            key: props.query.key,
            ...props.GooglePlacesSearchQuery,
          });
      }

      request.open('GET', requestUrl);

      request.withCredentials = requestShouldUseWithCredentials();
      setRequestHeaders(request, getRequestHeaders(props.requestUrl));

      request.send();
    } else {
      _results = [];
      setDataSource(buildRowsFromResults([]));
    }
  };

  const _request = (text) => {
    _abortRequests();
    if (!url) {
      return;
    }
    if (supportedPlatform() && text && text.length >= props.minLength) {
      const request = new XMLHttpRequest();
      _requests.push(request);
      request.timeout = props.timeout;
      request.ontimeout = props.onTimeout;
      request.onreadystatechange = () => {
        if (request.readyState !== 4) {
          setListLoaderDisplayed(true);
          return;
        }

        setListLoaderDisplayed(false);
        if (request.status === 200) {
          const responseJSON = JSON.parse(request.responseText);
          if (typeof responseJSON.predictions !== 'undefined') {
            // if (_isMounted === true) {
            const results =
              props.nearbyPlacesAPI === 'GoogleReverseGeocoding'
                ? _filterResultsByTypes(
                    responseJSON.predictions,
                    props.filterReverseGeocodingByTypes,
                  )
                : responseJSON.predictions;

            _results = results;
            setDataSource(buildRowsFromResults(results, text));
            // }
          }
          if (typeof responseJSON.error_message !== 'undefined') {
            if (!props.onFail)
              console.warn(
                'google places autocomplete: ' + responseJSON.error_message,
              );
            else {
              props.onFail(responseJSON.error_message);
            }
          }
        } else {
          // console.warn("google places autocomplete: request could not be completed or has been aborted");
        }
      };

      if (props.preProcess) {
        setStateText(props.preProcess(text));
      }

      request.open(
        'GET',
        `${url}/place/autocomplete/json?input=` +
          encodeURIComponent(text) +
          '&' +
          Qs.stringify(props.query),
      );

      request.withCredentials = requestShouldUseWithCredentials();
      setRequestHeaders(request, getRequestHeaders(props.requestUrl));

      request.send();
    } else {
      _results = [];
      setDataSource(buildRowsFromResults([]));
    }
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const debounceData = useMemo(() => debounce(_request, props.debounce), [
    props.query,
    url,
  ]);

  const _onChangeText = (text) => {
    setStateText(text);
    debounceData(text);
  };

  const _handleChangeText = (text) => {
    _onChangeText(text);

    const onChangeText = props?.textInputProps?.onChangeText;

    if (onChangeText) {
      onChangeText(text);
    }
  };

  const _getRowLoader = () => {
    return <ActivityIndicator animating={true} size='small' />;
  };

  const _renderRowData = (rowData, index) => {
    if (props.renderRow) {
      return props.renderRow(rowData, index);
    }

    return (
      <Text
        style={[
          props.suppressDefaultStyles ? {} : defaultStyles.description,
          props.styles.description,
          rowData.isPredefinedPlace
            ? props.styles.predefinedPlacesDescription
            : {},
        ]}
        numberOfLines={props.numberOfLines}
      >
        {_renderDescription(rowData)}
      </Text>
    );
  };

  const _renderDescription = (rowData) => {
    if (props.renderDescription) {
      return props.renderDescription(rowData);
    }

    return rowData.description || rowData.formatted_address || rowData.name;
  };

  const _renderLoader = (rowData) => {
    if (rowData.isLoading === true) {
      return (
        <View
          style={[
            props.suppressDefaultStyles ? {} : defaultStyles.loader,
            props.styles.loader,
          ]}
        >
          {_getRowLoader()}
        </View>
      );
    }

    return null;
  };

  const _renderRow =useCallback((rowData = {}, index) => {
    return (
        <Pressable
         key={Math.random().toString(36)}
          style={({ hovered, pressed }) => [
            props.isRowScrollable ? { minWidth: '100%' } : { width: '100%' },
            {
              backgroundColor: pressed
                ? props.listUnderlayColor
                : hovered
                ? props.listHoverColor
                : undefined,
            },
          ]}
          onPress={() =>_onPress(rowData)}
          onBlur={_onBlur}
        >
          <View
            style={[
              props.suppressDefaultStyles ? {} : defaultStyles.row,
              props.styles.row,
              rowData.isPredefinedPlace ? props.styles.specialItemRow : {},
            ]}
          >
            {_renderLoader(rowData)}
            {_renderRowData(rowData, index)}
          </View>
        </Pressable>
    );
  },[props]);

  const _renderSeparator = (sectionID, rowID) => {
    if (rowID === dataSource.length - 1) {
      return null;
    }

    return (
      <View
        key={`${sectionID}-${rowID}`}
        style={[
          props.suppressDefaultStyles ? {} : defaultStyles.separator,
          props.styles.separator,
        ]}
      />
    );
  };

  const isNewFocusInAutocompleteResultList = ({
    relatedTarget,
    currentTarget,
  }) => {
    if (!relatedTarget) return false;

    var 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;

    if (!props.keepResultsAfterBlur) {
      setListViewDisplayed(false);
    }
    inputRef?.current?.blur();
  };

  const _onFocus = () => setListViewDisplayed(true);

  const _renderPoweredLogo = () => {
    if (!_shouldShowPoweredLogo()) {
      return null;
    }

    return (
      <View
        style={[
          props.suppressDefaultStyles ? {} : defaultStyles.row,
          defaultStyles.poweredContainer,
          props.styles.poweredContainer,
        ]}
      >
        <Image
          style={[
            props.suppressDefaultStyles ? {} : defaultStyles.powered,
            props.styles.powered,
          ]}
          resizeMode='contain'
          source={require('./images/powered_by_google_on_white.png')}
        />
      </View>
    );
  };

  const _shouldShowPoweredLogo = () => {
    if (!props.enablePoweredByContainer || dataSource.length === 0) {
      return false;
    }

    for (let i = 0; i < dataSource.length; i++) {
      let row = dataSource[i];

      if (
        !row.hasOwnProperty('isCurrentLocation') &&
        !row.hasOwnProperty('isPredefinedPlace')
      ) {
        return true;
      }
    }

    return false;
  };

  const _renderLeftButton = () => {
    if (props.renderLeftButton) {
      return props.renderLeftButton();
    }
  };

  const _renderRightButton = () => {
    if (props.renderRightButton) {
      return props.renderRightButton();
    }
  };

  const _getFlatList =useCallback( () => {

    if (
      stateText !== ''  &&
      listViewDisplayed === true
    ) {
      return (
        <View  style={{zIndex:0,}} >
          {[...dataSource].slice(0,5).map((item,index)=>{
            return _renderRow(item, index)
          })}
        </View>

      );
    }

    return null;
  },[dataSource]);

  let {
    onFocus,
    onBlur,
    onChangeText, 
    clearButtonMode,
    InputComp,
    ...userProps
  } = props.textInputProps;
  const TextInputComp = InputComp || TextInput;

  return (
    <View
      style={[
        props.suppressDefaultStyles ? {} : defaultStyles.container,
        props.styles.container,
      ]}
      pointerEvents='box-none'
    >
      {!props.textInputHide && (
        <View
          style={[
            props.suppressDefaultStyles ? {} : defaultStyles.textInputContainer,
            props.styles.textInputContainer,
          ]}
        >
          {_renderLeftButton()}
          <TextInputComp
            ref={inputRef}
            style={[
              props.suppressDefaultStyles ? {} : defaultStyles.textInput,
              props.styles.textInput,
            ]}
            value={stateText}
            placeholder={props.placeholder}
            onFocus={
              onFocus
                ? (e) => {
                    _onFocus();
                    onFocus(e);
                  }
                : _onFocus
            }
            onBlur={
              onBlur
                ? (e) => {
                    _onBlur(e);
                    onBlur(e);
                  }
                : _onBlur
            }
            clearButtonMode={clearButtonMode || 'while-editing'}
            onChangeText={_handleChangeText}
            {...userProps}
          />
          {_renderRightButton()}
        </View>
      )}
      {props.inbetweenCompo}
      {_getFlatList()}
      {props.children}
    </View>
  );
});

GooglePlacesAutocomplete.propTypes = {
  autoFillOnNotFound: PropTypes.bool,
  currentLocation: PropTypes.bool,
  currentLocationLabel: PropTypes.string,
  debounce: PropTypes.number,
  disableScroll: PropTypes.bool,
  enableHighAccuracyLocation: PropTypes.bool,
  enablePoweredByContainer: PropTypes.bool,
  fetchDetails: PropTypes.bool,
  filterReverseGeocodingByTypes: PropTypes.array,
  GooglePlacesDetailsQuery: PropTypes.object,
  GooglePlacesSearchQuery: PropTypes.object,
  GoogleReverseGeocodingQuery: PropTypes.object,
  inbetweenCompo: PropTypes.object,
  isRowScrollable: PropTypes.bool,
  keyboardShouldPersistTaps: PropTypes.oneOf(['never', 'always', 'handled']),
  listEmptyComponent: PropTypes.element,
  listLoaderComponent: PropTypes.element,
  listHoverColor: PropTypes.string,
  listUnderlayColor: PropTypes.string,
  // Must write it this way: https://stackoverflow.com/a/54290946/7180620
  listViewDisplayed: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.oneOf(['auto']),
  ]),
  keepResultsAfterBlur: PropTypes.bool,
  minLength: PropTypes.number,
  nearbyPlacesAPI: PropTypes.string,
  numberOfLines: PropTypes.number,
  onFail: PropTypes.func,
  onNotFound: PropTypes.func,
  onPress: PropTypes.func,
  onTimeout: PropTypes.func,
  placeholder: PropTypes.string,
  predefinedPlaces: PropTypes.array,
  predefinedPlacesAlwaysVisible: PropTypes.bool,
  preProcess: PropTypes.func,
  query: PropTypes.object,
  renderDescription: PropTypes.func,
  renderHeaderComponent: PropTypes.func,
  renderLeftButton: PropTypes.func,
  renderRightButton: PropTypes.func,
  renderRow: PropTypes.func,
  requestUrl: PropTypes.shape({
    url: PropTypes.string,
    useOnPlatform: PropTypes.oneOf(['web', 'all']),
    headers: PropTypes.objectOf(PropTypes.string),
  }),
  styles: PropTypes.object,
  suppressDefaultStyles: PropTypes.bool,
  textInputHide: PropTypes.bool,
  textInputProps: PropTypes.object,
  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 };````
juzerdhinoj-dimiour commented 6 months ago

@jahanzaib5244 the patch works but it affects the onPress functionality.

After some testing, I found that original code seems to work as well when you turn the keyboard off in Android emulator. Now I need to explore what is causing the conflict with the keyboard popup.

sahilkashyap64 commented 6 months ago

have you tried adding the keyboardShouldPersistTaps="handled" in the parent component

 <ScrollView keyboardShouldPersistTaps="handled">
        <View keyboardShouldPersistTaps="handled" >
        <GooglePlacesAutocomplete 
        // your everything
        />
        </View>
        </ScrollView>
Jheysson commented 3 months ago

Same issue, keyboardShouldPersistTaps="handled" doesn't work for me EDIT: nvm, it did 🚀

<ScrollView keyboardShouldPersistTaps='handled'>
<FlatList
          keyboardShouldPersistTaps='handled'
...
jahanzaib5244 commented 3 months ago

@Jheysson You can use this Patch file code `index 99a2a13..6640f00 100644 --- a/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js +++ b/node_modules/react-native-google-places-autocomplete/GooglePlacesAutocomplete.js @@ -13,7 +13,6 @@ import React, { } from 'react'; import { ActivityIndicator,

OFD16 commented 3 months ago

https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/940#issuecomment-2080432973

this works for me thanks a lot @jahanzaib5244 but why there is problem still exist? Why not fixed do you know?

I think i got the problem cause of i wrap the google-places-autocomplete with modal so onPress is not working fine but if you add keepResultsAfterBlur={true} prop to autocomplete it work fine. But still there is exist problem because in my case in first press it hide the keyboard after i need to press again to work onPress function.

gesaleh commented 2 months ago

@jahanzaib5244 i have the same issue working on ios but not andriod is there a file or package patch that i can use I'm using react-native-google-places-autocomplete@2.5.6 when i used the code provided i'm getting this error : ReferenceError: Property 'debounce' doesn't exist

Sulaiman122 commented 1 month ago

Same issue @jahanzaib5244 I tried your solution, it work but still not consistent, only first time

Sulaiman122 commented 1 month ago

@jahanzaib5244 i have the same issue working on ios but not andriod is there a file or package patch that i can use I'm using react-native-google-places-autocomplete@2.5.6 when i used the code provided i'm getting this error : ReferenceError: Property 'debounce' doesn't exist

You could add import debounce from 'lodash.debounce'

JackJBlundell commented 3 weeks ago

have you tried adding the keyboardShouldPersistTaps="handled" in the parent component

 <ScrollView keyboardShouldPersistTaps="handled">
        <View keyboardShouldPersistTaps="handled" >
        <GooglePlacesAutocomplete 
        // your everything
        />
        </View>
        </ScrollView>

Thanks, this was the simple and obvious (in retrospect) issue. All fixed!

rybaier commented 2 weeks ago

@OFD16

I have the same issue with keepResultsAfterBlur having to tap away the keyboard and then select with the onPress

    <GooglePlacesAutocomplete
            placeholder="Search for an address"
            fetchDetails
            keepResultsAfterBlur // This ensures that a user can tap the dropdown on android after using a tap to dismiss the keyboard
            keyboardShouldPersistTaps="handled"
         // rest of the code
         </GooglePlacesAutocomplete>