react-native-google-places-autocomplete
react-native-google-places-autocomplete copied to clipboard
Example to resolve "VirtualizedLists should never be nested inside plain..."
If you add RNGooglePlacesAutocomplete inside a ScrollView, it throws an error:
VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead.
This PR adds an example in the README to fix this by wrapping another ScrollView.
This fix is being currently added in this PR https://github.com/Expensify/App/issues/7547 to get the component working inside a ScrollView
This removes the error but now the field dynamically adjusts the width based on the contents. How do I keep a fixed input field width?
This removes the error but now the field dynamically adjusts the width based on the contents. How do I keep a fixed input field width?
container style in scrollview should fix it
import React, { useState, useRef,useEffect } from 'react';
import { View, Text, TouchableOpacity, FlatList, Modal, StyleSheet, Dimensions,ScrollView } from 'react-native';
import { GooglePlacesAutocomplete } from 'react-native-google-places-autocomplete';
import { GOOGLE_MAPS_AUTOCOMPLETE_API_KEY } from '../../constants';
const { width, height } = Dimensions.get('window');
const TennisCourtSearch = ({ onSelect, latitude, longitude }) => {
const searchRef = useRef();
const [selectedCourts, setSelectedCourts] = useState([]);
const [showModal, setShowModal] = useState(false);
useEffect(() => {
searchRef.current?.setAddressText('Search for tennis courts');
}, []);
useEffect(() => {
onSelect(selectedCourts); // Notify parent component whenever selectedCourts changes
}, [selectedCourts]);
const handleSelectCourt = (detail) => {
const newCourt = {
formattedAddress: detail.formatted_address,
geometry: detail.geometry
};
const index = selectedCourts.findIndex(court => court.geometry.location === newCourt.geometry.location);
if (index === -1) {
setSelectedCourts(prevCourts => [...prevCourts, newCourt]);
} else {
setSelectedCourts(prevCourts => prevCourts.filter(court => court.geometry.location !== newCourt.geometry.location));
}
};
const renderCourtItem = ({ item, index }) => (
<View style={styles.courtItem}>
<Text style={styles.courtText} ellipsizeMode='tail' numberOfLines={1}>{`${index + 1}. ${item.formattedAddress}`}</Text>
<TouchableOpacity onPress={() => handleSelectCourt(item)} style={styles.removeButton}>
<Text style={styles.removeButtonText}>Remove</Text>
</TouchableOpacity>
</View>
);
console.log("selectedCourts",JSON.stringify(selectedCourts));
return (
<View style={{ marginTop: 50 }} keyboardShouldPersistTaps="handled">
<ScrollView
horizontal
style={{ flex: 1, width: '100%', height: '100%' }}
contentContainerStyle={{ flexGrow: 1, justifyContent: 'center', alignItems: 'center' }}
scrollEnabled={false}
>
<GooglePlacesAutocomplete
ref={searchRef}
placeholder="Search for tennis courts"
minLength={2}
fetchDetails={true}
disableScroll={true}
onPress={(data, details = null) => {
if (data && details) {
handleSelectCourt(details);
}
}}
query={{
key: GOOGLE_MAPS_AUTOCOMPLETE_API_KEY,
language: 'en',
location: `${latitude},${longitude}`,
radius: 10000,
type: 'establishment',
keyword: 'tennis court',
}}
// disableScroll={true}
nearbyPlacesAPI="GooglePlacesSearch"
debounce={300}
suppressDefaultStyles={true}
styles={{
container: {
flex: 1,
},
textInputContainer: {
flexDirection: 'row',
},
textInput: {
color: '#5d5d5d',
backgroundColor: '#FFFFFF',
height: 44,
borderRadius: 5,
paddingVertical: 5,
paddingHorizontal: 10,
fontSize: 15,
flex: 1,
marginBottom: 5,
},
listView: {color : 'black'},
row: {
backgroundColor: '#FFFFFF',
padding: 13,
minHeight: 44,
flexDirection: 'row',
},
loader: {
flexDirection: 'row',
justifyContent: 'flex-end',
height: 20,
},
description : {color : 'black'},
separator: {
height: StyleSheet.hairlineWidth,
backgroundColor: '#c8c7cc',
},
poweredContainer: {
justifyContent: 'flex-end',
alignItems: 'center',
borderBottomRightRadius: 5,
borderBottomLeftRadius: 5,
borderColor: '#c8c7cc',
borderTopWidth: 0.5,
},
}}
enablePoweredByContainer={false}
/>
</ScrollView>
<TouchableOpacity onPress={() => setShowModal(true)} style={styles.button}>
<Text>Show Selected Courts ({selectedCourts.length})</Text>
</TouchableOpacity>
<Modal
visible={showModal}
animationType="slide"
onRequestClose={() => setShowModal(false)}
>
<View style={styles.modalView}>
<FlatList
data={selectedCourts}
renderItem={renderCourtItem}
keyExtractor={item => item.place_id}
ListEmptyComponent={<Text>No courts selected.</Text>}
/>
<TouchableOpacity onPress={() => setShowModal(false)} style={styles.button}>
<Text>Close</Text>
</TouchableOpacity>
</View>
</Modal>
</View>
);
};
const styles = StyleSheet.create({
button: {
marginTop: 10,
backgroundColor: 'lightgrey',
padding: 10,
alignItems: 'center',
},
courtItem: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#ddd',
},
courtText: {
color:'black',
fontSize: 16,
width:200,
},
removeButton: {
padding: 5,
backgroundColor: 'red',
borderRadius: 5,
},
removeButtonText: {
color: 'white',
},
modalView: {
width: width * 0.9,
height: height * 0.7,
alignSelf: 'center',
marginTop: height * 0.15,
backgroundColor: 'white',
borderRadius: 20,
padding: 20,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
});
export default TennisCourtSearch;