Styling conditional FlatList? - react-native

I want to show FlatList only if there are results from an axios call. Unfortunately when there are some results, they are showing beneath the components below it, like TextArea, etc. I tried a lot of styling combinations but nothing works. Any help is appriciated!
const CreateScreen = () => {
const [searchKeyword, setSearchKeyword] = useState("");
const [searchResults, setSearchResults] = useState("");
const [isShowingResults, setIsShowingResults] = useState(false);
searchLocation = async (text) => {
setSearchKeyword(text);
axios
.request({
method: "post",
url:
"https://maps.googleapis.com/maps/api/place/autocomplete/json?key=" +
apiKey +
"&input=" +
searchKeyword +
"&types=(cities)&components=country:bg&language=bg",
})
.then((response) => {
console.log(response.data);
setSearchResults(response.data.predictions);
setIsShowingResults(true);
})
.catch((e) => {
console.log(e.response);
});
};
return (
<ScrollView>
<Text style={{ marginBottom: 3 }}>Address</Text>
<View style={styles.autocompleteContainer}>
<TextInput
returnKeyType="search"
placeholderTextColor="#000"
onChangeText={(text) => searchLocation(text)}
value={searchKeyword}
/>
{isShowingResults && (
<FlatList
data={searchResults}
renderItem={({ item, index }) => {
return (
<TouchableOpacity
style={styles.resultItem}
onPress={() => {
setSearchKeyword(item.description);
setIsShowingResults(false);
}}
>
<Text>{item.description}</Text>
</TouchableOpacity>
);
}}
keyExtractor={(item) => item.id}
style={styles.searchResultsContainer}
/>
)}
</View>
<Text style={{ marginBottom: 3 }}>Title</Text>
<TextInput />
</ScrollView>
);
}
const styles = StyleSheet.create({
autocompleteContainer: {
zIndex: 1,
},
searchResultsContainer: {
width: "100%",
backgroundColor: "#fff",
position: "absolute",
top: 50,
},
resultItem: {
justifyContent: "center",
height: 40,
borderBottomColor: "#ccc",
borderBottomWidth: 1,
paddingLeft: 15,
},
});

{isShowingResults? (
<FlatList
data={searchResults}
renderItem={({ item, index }) => {
return (
<TouchableOpacity
style={styles.resultItem}
onPress={() => {
setSearchKeyword(item.description);
setIsShowingResults(false);
}}
>
<Text>{item.description}</Text>
</TouchableOpacity>
);
}}
keyExtractor={(item) => item.id}
style={styles.searchResultsContainer}
/>
):null}
You can try this, or you can just pass ListEmptyComponent to null, nothing will display when you have empty array in data of flatlist

Related

How to Filter List Without Losing Data

The filter buttons on my React Native work in terms of filtering the data, but it doesn't keep the data that was filtered out, so I can only filter the data once and would have to input them again. I would like it to be able to filter without losing the data, so I can continuously switch between the different filters and show that they all work without needing to add new data.
import React, { useState } from 'react';
import {StyleSheet,Text,View,TextInput,TouchableOpacity,FlatList,Alert,Modal, Dimensions} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import AsyncStorage from '#react-native-async-storage/async-storage';
function App() {
const [todoInput, settodoInput] = React.useState('')
const [Todos, setTodos] = React.useState([]);
const [FilterTodos, setFilterTodos] = React.useState([Todos]);
const StatusTab = [
{
status: 'All',
completed: 'All'
},
{
status: 'Complete',
completed: true
},
{
status: 'Incomplete',
completed: false
}
]
const [Status,setStatus] = useState('All');
const setStatusFilter = Status => {
if(Status !== 'All') {
setTodos([...Todos.filter(e => e.completed === Status)])
} else {
setTodos(Todos)
}
setStatus(Status);
};
const [isRender,setisRender] = useState(false);
const [modalVisible, setmodalVisible] = useState(false);
const [editText, seteditText] = useState();
const [editItem, seteditItem] = useState();
React.useEffect(()=>{
GetTasks();
},[]);
React.useEffect(()=>{
SaveTasks(Todos);
},[Todos]);
const ListItem = ({Todo}) => {
return <View style={styles.ListItem}>
<View style={{flex:1}}>
<Text style={[styles.TaskText,{textDecorationLine: Todo?.completed?"line-through":"none"}]}>{Todo?.task}</Text>
</View>
{!Todo?.completed && (
<TouchableOpacity style={[styles.EditIcon]} onPress={()=>completeTodo(Todo?.id)}>
<Icon name='done' size={20} color='#FFFFFF'/>
</TouchableOpacity>
)}
<TouchableOpacity style={[styles.EditIcon,{backgroundColor:'#5D76E8'}]}
onPress={()=>editTodo(Todo?.id)}>
<Icon name='edit' size={20} color='#FFFFFF'/>
</TouchableOpacity>
<TouchableOpacity style={[styles.EditIcon,{backgroundColor:'#D30404'}]}
onPress={()=>deleteTodo(Todo?.id)}>
<Icon name='delete' size={20} color='#FFFFFF'/>
</TouchableOpacity>
</View>
}
const SaveTasks = async Todos =>{
try {
const stringifyTodos = JSON.stringify(Todos)
await AsyncStorage.setItem('Todos', stringifyTodos)
} catch (e) {
console.log(e);
// saving error
}
};
const GetTasks = async () => {
try {
const Todos = await AsyncStorage.getItem('Todos');
if(Todos != null){
setTodos(JSON.parse(Todos));
}
} catch (error) {
console.log(error);
}
};
const addTodo = () =>{
if(todoInput == ""){
Alert.alert("Error","Please Input Task");
}
else{
// console.log(todoInput);
const newTodo = {
id:Math.random(),
task: todoInput,
completed: false,
};
setTodos([...Todos,newTodo])
settodoInput('');
}
}
const completeTodo = (todoID) => {
console.log(todoID);
const newTodos = Todos.map((item)=>{
if(item.id == todoID){
return {...item,completed:true}
}
return item;
});
setTodos(newTodos);
};
const editTodo = (item) => {
setmodalVisible(true);
seteditText(item.text);
seteditItem(item.id);
};
const handleEditItem = (editItem) =>{
const newData =Todos.map(item =>{
if (item.id == editItem) {
item.text = editText;
return item
}
return item;
})
setTodos(newData);
setisRender(!isRender);
}
const onPressSaveEdit = () => {
handleEditItem(editItem);
setmodalVisible(false);
}
const deleteTodo = (todoID) =>{
Alert.alert("Confirm","Delete Task?",[{
text:"Yes",
onPress: () => {const newTodos = Todos.filter(item => item.id != todoID);
setTodos(newTodos)}
},
{text:"No"}
])
};
return (
<View style={styles.Container}>
<View style={styles.Header}>
<TextInput style={styles.SearchBar} placeholder='Add Task'
value={todoInput} onChangeText={(text)=>settodoInput(text)}/>
<TouchableOpacity onPress={addTodo}>
<View style={styles.IconContainer}>
<Icon name="add" color='#FFFFFF' size={30}/>
</View>
</TouchableOpacity>
</View>
<View style={styles.TaskList}>
<FlatList
showsVerticalScrollIndicator={false}
contentContainerStyle={{padding:20,paddingBottom:100}}
data={Todos}
keyExtractor={(item) => item.id.toString()}
renderItem={({item})=><ListItem Todo={item}/>}
extraData={isRender}/>
<Modal
animationType='fade'
visible={modalVisible}
onRequestClose={() => setmodalVisible(false)}
>
<View style={styles.ModalView}>
<Text style={styles.ModalText}>Change Task:</Text>
<TextInput style={styles.ModalInput}
onChangeText={(text) => seteditText(text)}
defaultValue={editText}
editable={true}
multiline={false}
maxLength={200}/>
<Text style={styles.ModalText}>Task Status:</Text>
<TouchableOpacity style={styles.FilterTab}>
<Text style={styles.TabText}>Complete</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.FilterTab}>
<Text style={styles.TabText}>Incomplete</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={()=> onPressSaveEdit()}
style={styles.SaveButton}>
<Text style={styles.ModalText}>Save</Text>
</TouchableOpacity>
</View>
</Modal>
</View>
<View style={styles.Footer}>
{
StatusTab.map(e => (
<TouchableOpacity style={[styles.FilterTab, Status === e.completed && styles.FilterTabActive]}
onPress={() => setStatusFilter(e.completed)}>
<Text style={[styles.TabText, Status === e.status && styles.TabTextActive]}>{e.status}</Text>
</TouchableOpacity>
))
}
</View>
</View>
);
}
const styles = StyleSheet.create({
Container:{
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
Header:{
flexDirection: 'row',
backgroundColor:'#000000',
alignItems: "center",
justifyContent: 'center',
width:'100%'
},
SearchBar:{
borderColor: "#FFFFFF",
backgroundColor: "#DDDDDD",
marginHorizontal: 5,
width: '40%',
height:'80%',
borderRadius: 30
},
IconContainer:{
height: 50,
width: 50,
backgroundColor: '#061380',
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center'
},
TaskList:{
flex: 10,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-around',
alignItems: 'center'
},
ListItem:{
padding:20,
backgroundColor:'#DEDEDE',
flexDirection:'row',
elevation:12,
borderRadius:7,
marginVertical:10
},
TaskText:{
fontWeight:'bold',
fontSize:15,
color:'#000000',
},
EditIcon:{
height:25,
width:25,
backgroundColor:'#1BCC48',
justifyContent:'center',
alignItems:'center',
marginLeft:5,
borderRadius:3
},
ModalView:{
flex:1,
alignItems:'center',
justifyContent:'center'
},
ModalText:{
fontSize:25,
fontWeight:'bold',
marginVertical:30,
marginLeft:10
},
ModalInput:{
width:'90%',
height:70,
borderColor:'#000000',
borderWidth:1,
fontSize:25
},
SaveButton:{
backgroundColor:'#3AE3A0',
paddingHorizontal:100,
alignItems:'center',
marginTop:20
},
Footer:{
flexDirection:'row',
alignSelf:'center',
marginBottom:10
},
FilterTab:{
width:Dimensions.get('window').width / 3.5,
flexDirection:'row',
borderWidth:1,
borderColor:'#4A4A4A',
padding:10,
justifyContent:'center'
},
FilterTabActive:{
backgroundColor:'#F87861'
},
TabText:{
fontSize:16,
},
TabTextActive:{
color:'#FFFFFF'
},
});
export default App;
added a new method and passing it to the Flatlist
const getTodos = (status) => {
return status === 'All'
? Todos
: Todos.filter((item) => item.completed === status);
};
<FlatList
showsVerticalScrollIndicator={false}
contentContainerStyle={{ padding: 20, paddingBottom: 100 }}
data={getTodos(Status)}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => <ListItem Todo={item} />}
extraData={isRender}
/>
please check this
demo code
I think you are changing the data array when filtering with:
setTodos([...Todos.filter(e => e.completed === Status)])
How I did it (similar) in my app:
const [filtered, setFiltered] = useState(setStatusFilter());
then you should
setFiltered([...Todos.filter(e => e.completed === Status)])
to have the filtered values in const filtered and the source list stay the same as before.. and you can then use the filtered to show in a mapping ..

Force BottomSheetFlatList open from first position every time useCallback called in react-native-bottom-sheet

I am using react-native-bottom-sheet with BottomSheetFlatList.
I want to open BottomSheetFlatList at initialPosition every time it opened but I am not able to achieve that. When I open it it's always open at last scrolling position.
<BottomSheet
ref={bottomSheetRef}
index={0}
snapPoints={snapPoints}
enableDismissOnClose={true}
handleIndicatorStyle={{display: 'none'}}
handleStyle={{padding: 0}}>
<View style={{backgroundColor: '#b2b2b2', alignItems: 'center'}}>
{!isOpen && (
<Pressable onPress={() => handleSnapPress(1)}>
<Image
source={IconCollapse}
style={{width: 30, height: 20, backgroundColor: '#b2b2b2'}}
/>
</Pressable>
)}
{isOpen && (
<Pressable onPress={() => handleClosePress()}>
<Image
source={IconExpand}
style={{width: 30, height: 20, backgroundColor: '#b2b2b2'}}
/>
</Pressable>
)}
</View>
<View style={{backgroundColor: '#b2b2b2'}}>
<Text
style={{
color: 'white',
fontSize: 20,
textTransform: 'uppercase',
paddingStart: 10,
}}>
Select Zone
</Text>
<View
style={{
borderBottomColor: '#fff',
borderBottomWidth: 1,
marginEnd: 10,
marginStart: 10,
}}
/>
</View>
<BottomSheetFlatList
data={zoneList}
keyExtractor={(item, index) => `${index}`}
renderItem={renderItem}
contentContainerStyle={styles.contentContainer}
/>
</BottomSheet>
you can try
this.flatListRef.scrollToOffset({ animated: true, offset: 0 });
hope this is work
use Prop enableDismissOnClose
as mentioned here
As Mohammad Momin said you can try scrollToIndex({index: 0, offset: 0}).
In order to do that you have to declare a ref for your FlatList and also specify the getItemLayout prop. Then you can call scrollToIndex.
So every time that you open the bottomSheet you have to call scrollToIndex.
The full working example I created is available in this github repositor which is something like this:
export const CustomBottomSheet = forwardRef(({
zoneList = [],
selectedZone,
onZoneSelected
}, ref) => {
const bottomSheetRef = useRef(null);
const flatListRef = useRef(null);
const scrollToItem = () => {
if (!selectedZone) return;
if (zoneList.length < 1) return;
// find item index
const index = zoneList.findIndex(value => value.id === selectedZone?.id);
// scroll to destination index
// it's better to set animated to true (experimental)
flatListRef.current.scrollToIndex({
animated: true,
index: index, // 0
})
console.log('scrollToItem called by index:' + index)
}
const handleSnapPress = (index = 0) => {
bottomSheetRef.current.snapToIndex(index);
// call this method after each time user opens the bottom sheet
scrollToItem();
}
const handleClosePress = () => {
bottomSheetRef.current.snapToIndex(index);
}
const renderItem = ({ item }) => {
return (
<ListItem
{...item}
onPress={() => onZoneSelected(item)}
isSelected={item.id === selectedZone?.id}
/>
)
}
const getItemLayout = (_, index) => (
{
length: ListItem.HEIGHT,
offset: (ListItem.HEIGHT + ListItem.VERTICAL_SPACING) * index,
index: index,
}
)
// forwarding methods via ref
useImperativeHandle(ref, () => ({
open: handleSnapPress,
close: handleClosePress,
snapToIndex: handleSnapPress,
}));
return (
<BottomSheet
ref={bottomSheetRef}
index={-1}
snapPoints={SNAP_POINTS}
enableDismissOnClose={true}
// onChange={handleOnChange}
enablePanDownToClose={true}
>
<View style={{ backgroundColor: '#b2b2b2', alignItems: 'center', marginTop: 16 }}>
<Text
style={{
color: 'white',
fontSize: 20,
textTransform: 'uppercase',
paddingStart: 10,
}}>
{'Select Zone'}
</Text>
</View>
<BottomSheetFlatList
// add ref and getItemLayout in order to use scrollToIndex method
ref={flatListRef}
getItemLayout={getItemLayout}
data={zoneList}
keyExtractor={(item) => item.id}
renderItem={renderItem}
contentContainerStyle={styles.contentContainer}
/>
</BottomSheet>
)
})

How can I filter results when one item can have one or more categories?

I can see the items on the list on the screen but there are some of the items missing from the list when no filters are selected but items belongs still to those categories. I have hardcoded list of categories for the category filter picker. Main issue I have here is that how can I take in account now that item can have one or more categories for example I have item on list that can have category: "Cars" and now with latest changes item can also have category: "Cars, Insurance".
How do I update the logic to handle this kind of change?
const route = useRoute()
const { data } = route.params
const [services, setServices] = useState([])
const [selectedCategory, setSelectedCategory] = useState(undefined)
const [categories, setCategories] = useState([])
const [citiesData] = useState(cities)
const [city, setCity] = useState()
const [isSelected, setIsSelected] = useState(false)
const [cardStyle, setCardStyle] = useState(servicesCards)
useEffect(() => {
(async () => {
let res = await fetch(`${DEV_API_URL}/cars`)
let resData = await res.json()
setServices(resData)
setCategories(data.map((item) => item.service))
})()
}, [data])
useEffect(() => {
selectedCategory || city ? setIsSelected(true) : setIsSelected(false)
}, [selectedCategory, city])
const resetFilters = () => {
setSelectedCategory(null)
setCity(null)
}
const ListItem = ({ data }) => {
return (
<View key={data.id} style={{ flexDirection: 'row' }}>
<Text key={data.service} style={optionText}>
{data.service}
</Text>
</View>
)
}
const CityItem = ({ data }) => {
const { optionText } = GlobalStyle
return (
<View key={data.id} style={{ flexDirection: 'row' }}>
<Text style={optionText}>{data.city}</Text>
</View>
)
}
const filteredServices = selectedCategory || selectedCategory && city
? services.filter((company) => company.category === selectedCategory || company.location === city)
: services.filter((company) => categories.includes(company.category))
<ModalSelector
data={data}
keyExtractor={(x) => x.id}
labelExtractor={(x) => x.service}
initValue={
!selectedCategory ? 'Kategoria' : selectedCategory
}
initValueTextStyle={{ color: colors.white, fontSize: 18 }}
selectStyle={categoryPicker}
selectTextStyle={{ color: colors.white_087 }}
optionContainerStyle={{ backgroundColor: colors.black_021 }}
cancelStyle={{ backgroundColor: colors.red }}
cancelText='Peruuta'
cancelTextStyle={{
color: colors.white_087,
fontSize: 16,
fontWeight: 'bold',
}}
onChange={(option) => setSelectedCategory(option.service)}
componentExtractor={(option) => <ListItem data={option} />}
/>
<ModalSelector
data={citiesData}
keyExtractor={(x) => x.city}
labelExtractor={(x) => x.city}
initValue={!city ? 'Kaupunki' : city}
initValueTextStyle={{ color: colors.white, fontSize: 18 }}
selectStyle={cityPicker}
selectTextStyle={{ color: colors.white_087 }}
optionContainerStyle={{ backgroundColor: colors.black_021 }}
cancelStyle={{ backgroundColor: colors.red }}
cancelText='Peruuta'
cancelTextStyle={{
color: colors.white_087,
fontSize: 16,
fontWeight: 'bold',
}}
onChange={(option) => setCity(option.city)}
componentExtractor={(option) => <CityItem data={option} />}
/>
{isSelected ? (
<Pressable onPress={resetFilters}>
<Ionicons
testID='removeFilters'
name='trash-outline'
size={35}
color={colors.red}
/>
</Pressable>) : null}
</View>
<View style={{ flex: 2 }}>
<FlatList
data={filteredServices}
contentContainerStyle={{
flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
}}
keyExtractor={(item) => item.id}
horizontal={true}
renderItem={({ item }) => {
return !selectedCategory ||
!city ||
item.category === selectedCategory && city === item.location ? (
<Pressable onPress={() => Linking.openURL(`${item.homepage}`)}>
<Card key={item.id} style={[cardStyle]}>
<CardContainer
key={item.img_url}
style={{
flex: 1,
}}
>
<Image
key={item.img_url}
style={{ width: '100%', height: '100%' }}
resizeMode='contain'
source={{ uri: `${item.img_url}` }}
/>
</CardContainer>
</Card>
</Pressable>
) : null
}}
ListEmptyComponent={() => {
return (
<Text style={{ color: colors.white, fontSize: 20 }}>
Ei palveluita saatavilla
</Text>
)
}}
/>

How can I autonomously link a sub-collection to an existing doc

So I have been trying to add products to certain machines but I am having issues linking the product with the desired/selected machine. Below you can see my code for listing card that I use on my home screen. I am linking my home screen with my product listing screen through a touchable icon on my listing cards which triggers a modal that with an "Add Product" button.
Code for Home Screen
const width = Dimensions.get('window').width / 2 - 30;
const HomeScreen = ({navigation}) => {
const [listings, setListings] = useState(null);
const [storefronts, setStorefronts] = useState(null);
const [machines, setMachines] = useState(null);
const [products, setProducts] = useState(null);
const [locations, setLocations] = useState(null);
const [landlords, setLandlords] = useState(null);
const [loading, setLoading] = useState(true);
const [deleted, setDeleted] = useState(false);
const [wholesaleProducts, setWholesaleProducts] = useState(null);
const [manufacturers, setManufacturers] = useState(null);
const user = auth().currentUser;
const fetchListings = async () => {
try {
const list = [];
console.log('Profile User:', user);
if (!user) return;
await firestore()
.collection('listings')
.where('userId', '!=', user.uid)
.get()
.then(querySnapshot => {
// console.log('Total Listings: ', querySnapshot.size);
querySnapshot.forEach(doc => {
const {
userId,
listing,
listingImg,
listingTime,
likes,
comments,
listingName,
listingTheme,
barcode,
price,
units,
quantity,
description,
} = doc.data();
list.push({
id: doc.id,
userId,
userName: 'Test Name',
userImg:
'https://lh5.googleusercontent.com/-b0PKyNuQv5s/AAAAAAAAAAI/AAAAAAAAAAA/AMZuuclxAM4M1SCBGAO7Rp-QP6zgBEUkOQ/s96-c/photo.jpg',
listingTime,
listing,
listingImg,
liked: false,
likes,
comments,
price,
listingName,
listingTheme,
barcode,
units,
quantity,
description,
});
});
});
setListings(list);
if (loading) {
setLoading(false);
}
console.log('Listings: ', listings);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
fetchListings();
setDeleted(false);
}, [deleted]);
const fetchMachines = async () => {
try {
const list = [];
await firestore()
.collection('oem machines')
.orderBy('machineName', 'desc')
.get()
.then(querySnapshot => {
// console.log('Total Listings: ', querySnapshot.size);
querySnapshot.forEach(doc => {
const {
userId,
machineIQ,
machineImg,
machineId,
machineName,
machineTheme,
machinePrice,
description,
machineType,
machineModel,
createdAt,
active,
} = doc.data();
list.push({
id: doc.id,
userId,
machineId: doc.id,
userName: 'Test Name',
userImg: '',
machineIQ,
active,
machineImg,
machineType,
machinePrice,
machineName,
machineTheme,
machineModel,
description,
});
});
});
setMachines(list);
if (loading) {
setLoading(false);
}
console.log('Machines: ', machines);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
fetchMachines();
setDeleted(false);
}, [deleted]);
const fetchLocations = async () => {
});
setLocations(list);
if (loading) {
setLoading(false);
}
console.log('Locations: ', locations);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
fetchLocations();
setDeleted(false);
}, [deleted]);
const fetchStorefronts = async () => {
});
setStorefronts(list);
if (loading) {
setLoading(false);
}
console.log('Storefronts: ', storefronts);
} catch (e) {
console.log(e);
}
};
useEffect(() => {
fetchStorefronts();
setDeleted(false);
}, [deleted]);
const handleDelete = listingId => {
Alert.alert(
'Delete Listing',
'Are you sure?',
[
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed!'),
style: 'cancel',
},
{
text: 'Confirm',
onPress: () => deleteListing(listingId),
},
],
{cancelable: false},
);
};
const deleteListing = listingId => {
console.log('Current Listing Id: ', listingId);
firestore()
.collection('listings')
.doc(listingId)
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
const {listingImg} = documentSnapshot.data();
if (listingImg != null) {
const storageRef = storage().refFromURL(listingImg);
const imageRef = storage().ref(storageRef.fullPath);
imageRef
.delete()
.then(() => {
console.log(`${listingImg} has been deleted successfully.`);
deleteFirestoreData(listingId);
})
.catch(e => {
console.log('Error while deleting the image. ', e);
});
// If the Listing image is not available
} else {
deleteFirestoreData(listingId);
}
}
});
};
const deleteFirestoreData = listingId => {
firestore()
.collection('listings')
.doc(listingId)
.delete()
.then(() => {
Alert.alert(
'Listing deleted!',
'Your Listing has been deleted successfully!',
);
setDeleted(true);
})
.catch(e => console.log('Error deleting posst.', e));
};
const Card = ({
listings,
onDelete,
machines,
locations,
storefronts,
item,
onPress,
}) => {
const [userData, setUserData] = useState(null);
const [modalVisible, setModalVisible] = useState(false);
const getUser = async () => {
await firestore()
.collection('users')
.doc(listings.userId)
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
console.log('User Data', documentSnapshot.data());
setUserData(documentSnapshot.data());
}
});
};
const getManufacturer = async () => {
await firestore()
.collection('users')
.doc(machines.userId)
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
console.log('User Data', documentSnapshot.data());
setUserData(documentSnapshot.data());
}
});
};
const getLandlord = async () => {
await firestore()
.collection('users')
.doc(locations.userId)
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
console.log('User Data', documentSnapshot.data());
setUserData(documentSnapshot.data());
}
});
};
const getShopOwner = async () => {
await firestore()
.collection('users')
.doc(storefronts.userId)
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
console.log('User Data', documentSnapshot.data());
setUserData(documentSnapshot.data());
}
});
};
useEffect(() => {
getUser();
getLandlord();
getShopOwner();
getManufacturer();
}, []);
return (
<TouchableOpacity activeOpacity={0.8} onPress={onPress}>
<View style={style.card}>
<View
style={{
alignItems: 'flex-start',
flexDirection: 'row',
justifyContent: 'center',
}}>
<UserImg
source={{
uri: userData
? userData.userImg
: 'https://lh5.googleusercontent.com/-b0PKyNuQv5s/AAAAAAAAAAI/AAAAAAAAAAA/AMZuuclxAM4M1SCBGAO7Rp-QP6zgBEUkOQ/s96-c/photo.jpg',
}}
style={{marginTop: 20, marginLeft: 10}}
/>
<UserInfoText style={{marginTop: 25}}>
<TouchableOpacity>
<UserName>
{userData ? userData.fname || 'Test' : 'Test'}
{userData ? userData.lname || 'User' : 'User'}
</UserName>
</TouchableOpacity>
<ListingTime>
{listings
? listings.listingTime &&
moment(listings.listingTime.toDate()).fromNow()
: null}
</ListingTime>
</UserInfoText>
<TouchableOpacity style={{alignItems: 'flex-end'}}>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
setModalVisible(!modalVisible);
}}>
<View style={style.centeredView}>
<View style={style.modalView}>
<Pressable
style={[style.button, style.buttonClose]}
onPress={async () =>
navigation.navigate('Add Listing') &&
setModalVisible(!modalVisible)
}>
<Text style={style.textStyle}>Add Product</Text>
</Pressable>
<Pressable
style={[style.button, style.buttonClose]}
onPress={() => setModalVisible(!modalVisible)}>
<Text style={style.textStyle}>Hide Modal</Text>
</Pressable>
</View>
</View>
</Modal>
<Ionicons
name="add-circle"
size={22}
color={COLORS.purple}
onPress={() => setModalVisible(true)}
/>
</TouchableOpacity>
</View>
<View
style={{
height: 100,
alignItems: 'center',
}}>
<Image
source={[
{
uri: listings ? listings.listingImg : null,
},
{
uri: machines ? machines.machineImg : null,
},
{
uri: locations ? locations.locationImg : null,
},
{
uri: storefronts ? storefronts.storefrontImg : null,
},
]}
style={{flex: 1, width: 150, resizeMode: 'contain', marginTop: 5}}
/>
</View>
<Text
style={{
fontWeight: 'bold',
fontSize: 15,
marginTop: 8,
marginLeft: 2,
}}>
{[
listings ? listings.listingName : null,
machines ? machines.machineName : null,
locations ? locations.locationName : null,
storefronts ? storefronts.storefrontName : null,
]}
</Text>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 1,
}}>
<Text style={{fontSize: 16, fontWeight: 'bold', marginLeft: 6}}>
{[
// listings ? '$' + listings.price : null,
machines ? '$' + machines.machinePrice : null,
]}
</Text>
<View style={{flexDirection: 'row', marginRight: 4}}>
<TouchableOpacity>
<Ionicons name="heart" size={22} color={COLORS.red} />
</TouchableOpacity>
</View>
</View>
</View>
</TouchableOpacity>
);
};
const ListHeader = () => {
return null;
};
return (
<SafeAreaView style={{flex: 1}}>
<View style={style.header}>
<Text style={{fontSize: 25, fontWeight: 'bold'}}>
Until Next Time :) BYE!
</Text>
<Text style={{fontSize: 38, color: COLORS.purple, fontWeight: 'bold'}}>
{/* Lula */}
</Text>
<TouchableOpacity>
<Ionicons
name="add-circle"
size={28}
color={COLORS.purple}
onPress={() => navigation.navigate('Form Screen')}
/>
</TouchableOpacity>
</View>
<ScrollView>
<View>
<Text style={{fontSize: 20, fontWeight: 'bold', marginTop: 35}}>
All Listings
</Text>
<FlatList
contentContainerStyle={{
marginTop: 15,
paddingBottom: 50,
alignItems: 'center',
padding: 10,
}}
backgroundColor={COLORS.light}
showsVerticalScrollIndicator={false}
horizontal={true}
data={listings}
renderItem={({item}) => (
<Card
listings={item}
onDelete={handleDelete}
key={item.id}
onPress={() => navigation.navigate('Details', item)}
/>
)}
keyExtractor={item => item.id}
ListHeaderComponent={ListHeader}
ListFooterComponent={ListHeader}
showsVerticalScrollIndicator={true}
/>
</View>
<View>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Machines</Text>
<FlatList
contentContainerStyle={{
marginTop: 10,
paddingBottom: 50,
alignItems: 'center',
padding: 10,
}}
backgroundColor={COLORS.light}
showsVerticalScrollIndicator={false}
horizontal={true}
data={machines}
renderItem={({item}) => (
<Card
machines={item}
onDelete={handleDelete}
key={item.id}
onPress={() => navigation.navigate('Details', item)}
/>
)}
keyExtractor={item => item.id}
ListHeaderComponent={ListHeader}
ListFooterComponent={ListHeader}
showsVerticalScrollIndicator={true}
/>
</View>
<View>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Locations</Text>
<FlatList
contentContainerStyle={{
marginTop: 10,
paddingBottom: 50,
alignItems: 'center',
padding: 10,
}}
backgroundColor={COLORS.light}
showsVerticalScrollIndicator={false}
horizontal={true}
data={locations}
renderItem={({item}) => (
<Card
locations={item}
onDelete={handleDelete}
key={item.id}
onPress={() => navigation.navigate('Details', item)}
/>
)}
keyExtractor={item => item.id}
ListHeaderComponent={ListHeader}
ListFooterComponent={ListHeader}
showsVerticalScrollIndicator={true}
/>
</View>
<View>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>Storefronts</Text>
<FlatList
contentContainerStyle={{
marginTop: 10,
paddingBottom: 50,
alignItems: 'center',
padding: 10,
}}
backgroundColor={COLORS.light}
showsVerticalScrollIndicator={false}
horizontal={true}
data={storefronts}
renderItem={({item}) => (
<Card
storefronts={item}
onDelete={handleDelete}
key={item.id}
onPress={() => navigation.navigate('Details', item)}
/>
)}
keyExtractor={item => item.id}
ListHeaderComponent={ListHeader}
ListFooterComponent={ListHeader}
showsVerticalScrollIndicator={true}
/>
</View>
</ScrollView>
)}
</SafeAreaView>
};
My submitProductListing function is able to add both a listing and a product to their respective collections within firestore but the machineRef doc doesn't exist so firestore generates a non-existent ancestor document for the products parent doc (doc of desired machine). Below I have my Product Listing Screen and some screenshots of my firestore collections.
const listingValidationSchema = yup.object().shape({
listingName: yup.string().required(),
description: yup.string().required(),
listingTheme: yup.string().required(),
barcode: yup.number().required(),
price: yup.number().required(),
quantity: yup.number().required(),
units: yup.string().required(),
});
const ProductListingScreen = ({navigation, route}) => {
const listings = route.params;
const storefronts = route.params;
const machines = route.params;
const locations = route.params;
const [image, setImage] = useState(null);
const [uploading, setUploading] = useState(false);
const choosePhotoFromLibrary = () => {
ImagePicker.openPicker({
width: 1200,
height: 780,
cropping: true,
}).then(image => {
console.log(image);
const imageUri = Platform.OS === 'ios' ? image.sourceURL : image.path;
setImage(imageUri);
});
};
const takePhotoFromCamera = () => {
ImagePicker.openCamera({
width: 1200,
height: 780,
cropping: true,
}).then(image => {
console.log(image);
const imageUri = Platform.OS === 'ios' ? image.sourceURL : image.path;
setImage(imageUri);
});
};
const uploadImage = async imageUri => {
console.log('Uploading Image');
if (image == null) {
Alert.alert('Please select an image');
return null;
}
const uploadUri = image;
console.log('Upload URI:', uploadUri);
let filename = uploadUri.substring(uploadUri.lastIndexOf('/') + 1);
// Add timestamp to File Name
const extension = filename.split('.').pop();
const name = filename.split('.').slice(0, -1).join('.');
filename = name + Date.now() + '.' + extension;
setUploading(true);
const storageRef = storage().ref(`photos/${filename}`);
try {
const task = await storageRef.putFile(uploadUri);
} catch (e) {
console.error(e);
return null;
}
try {
let imageUrl = await storageRef.getDownloadURL();
setUploading(false);
return imageUrl;
} catch (e) {
setUploading(false);
console.error(e);
return null;
}
};
const submitProductListing = async (values, actions, machines, item) => {
let imageUrl = await uploadImage();
console.log('Image Url: ', imageUrl);
const listing = {
userId: auth().currentUser.uid,
listingId: '',
listingName: values.listingName,
description: values.description,
listingTheme: values.listingTheme,
barcode: values.barcode,
price: values.price,
quantity: values.quantity,
units: values.units,
listingImg: imageUrl,
listingTime: firestore.Timestamp.fromDate(new Date()),
active: '',
machineId: '',
};
console.log(listing);
firestore()
.collection('listings')
.add(listing)
.then(() => {
console.log('Listing Added!');
Alert.alert(
'Listing published!',
'Your Listing has been published Successfully!',
).catch(error => {
console.log(
'Something went wrong with added Listing to firestore.',
error,
);
});
});
const db = firebase.firestore();
const machineRef = db.collection('machines').doc();
const productRef = machineRef.collection('products');
// In a transaction, add the new product and update the aggregate totals
return db.runTransaction(async transaction => {
const res = await transaction.get(machineRef);
if (!res.exists) {
console.log("Document doesn't exist");
}
// Create a product
const product = {
productName: values.listingName,
productTheme: values.listingTheme,
description: values.description,
productImg: imageUrl,
listingTime: firestore.Timestamp.fromDate(new Date()),
price: values.price,
productId: '',
userId: auth().currentUser.uid,
machineId: '',
};
// Commit to Firestore
productRef.add(product).then(() => {
console.log('Product Added!');
});
});
};
Machine Document(non-existent ancestor document)
Sub-collection with newly added product
My thought process is to try to link product and machines through a shared id. Each machine will have a userId, machineId, and maybe an array of productId's (I haven't added this field yet because I don't know if this is the right way to go about it) and each product will have a userId, machineId, and productId. My main issue is though, I don't know how to set the Id of a doc with auto-generated. So as you can see on my product listing screen, I can set the userId because of "auth().currentUser.uid" but I don't know how to call/link the id of the selected machine on the home page.
So my main questions are:
How can I add doc id as a doc field for auto-generated id's
How can I route/link selected machine to my product listing screen
How can I then add new product to the selected machine
I also want to show the code for my details page where I use route.params to link listings to their respective details page so I don't know if I need to use a process similar to this but on the home page I used navigation.navigate('Details', item). Do I need to use item again to link machine to products? Someone please help!
Full Code for Details Screen
import React, {useState, useEffect} from 'react';
import {View, SafeAreaView, Image, Text, StyleSheet} from 'react-native';
import {TouchableOpacity} from 'react-native-gesture-handler';
import Ionicons from 'react-native-vector-icons/Ionicons';
import COLORS from '../components/colors';
const DetailsScreen = ({navigation, route}) => {
const listings = route.params;
const storefronts = route.params;
const machines = route.params;
const locations = route.params;
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: COLORS.white,
}}>
<View style={style.header}>
<Ionicons
name="arrow-back"
size={28}
onPress={() => navigation.goBack()}
/>
<Ionicons
name="cart"
size={28}
onPress={() => navigation.navigate('Cart')}
/>
</View>
<View style={style.imageContainer}>
<Image
defaultImageSource={require('../assets/default-img.jpeg')}
source={[
{
uri: listings ? listings.listingImg : null,
},
{
uri: machines ? machines.machineImg : null,
},
{
uri: locations ? locations.locationImg : null,
},
{
uri: storefronts ? storefronts.storefrontImg : null,
},
]}
style={{width: '100%', height: 250}}
resizeMode="cover"></Image>
</View>
<View style={style.detailsContainer}>
<View
style={{
marginLeft: 20,
flexDirection: 'row',
alignItems: 'flex-end',
}}>
<View style={style.line} />
<Text style={{fontSize: 18, fontWeight: 'bold'}}>Best choice</Text>
</View>
<View
style={{
marginLeft: 20,
marginTop: 20,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<Text style={{fontSize: 22, fontWeight: 'bold'}}>
{[
listings ? listings.listingName : null,
machines ? machines.machineName : null,
locations ? locations.locationName : null,
storefronts ? storefronts.storefrontName : null,
]}
</Text>
<View style={style.priceTag}>
<Text
style={{
marginLeft: 15,
color: COLORS.white,
fontWeight: 'bold',
fontSize: 16,
}}>
$
{[
listings ? listings.price : null,
machines ? machines.machinePrice : null,
]}
</Text>
</View>
</View>
<View style={{paddingHorizontal: 20, marginTop: 10}}>
<Text style={{fontSize: 20, fontWeight: 'bold'}}>About</Text>
<Text
style={{
color: 'grey',
fontSize: 16,
lineHeight: 22,
marginTop: 10,
}}>
{[
listings ? listings.description : null,
machines ? machines.description : null,
locations ? locations.description : null,
storefronts ? storefronts.description : null,
]}
</Text>
<View
style={{
marginTop: 20,
flexDirection: 'row',
justifyContent: 'space-between',
}}>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
}}>
<TouchableOpacity>
<View style={style.borderBtn}>
<Text style={style.borderBtnText}>-</Text>
</View>
</TouchableOpacity>
<Text
style={{
fontSize: 20,
marginHorizontal: 10,
fontWeight: 'bold',
}}>
1
</Text>
<TouchableOpacity>
<View style={style.borderBtn}>
<Text style={style.borderBtnText}>+</Text>
</View>
</TouchableOpacity>
</View>
<TouchableOpacity>
<View style={style.buyBtn}>
<Text
style={{
color: COLORS.white,
fontSize: 18,
fontWeight: 'bold',
}}>
Buy
</Text>
</View>
</TouchableOpacity>
</View>
</View>
</View>
</SafeAreaView>
);
};
export default DetailsScreen;

flatlist slow performance on large data react native

I'm new to react native and I'm having some performance problem with flatlist in React Native Expo. On my screen I'm displaying a list of contacts and I want them to be selected or deselected and I have managed to achieve that part but the problem is that it takes a lot of visibility time to check the contacts or to uncheck them. Making the screen not very user friendly. I have tried a lot of solutions that I've found on internet regarding this problem but none of them had worked. Hope someone can help me. Below I'll attach the code!
function ContactList() {
const [itemChecked, setItemChecked] = useState([]);
const [checked, setChecked] = useState(false);
const [contacts, setContacts] = useState([]);
const [filter, setFilter] = useState([]);
const [search, setSearch] = useState('');
const [checkedBox, setCheckedBox] = useState(false);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === 'granted') {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
// fields: [Contacts.Fields.Name],
});
if (data.length > 0) {
setContacts(data);
setFilter(data);
// console.log('contact', contacts[1]);
// console.log('filter', filter);
}
}
})();
}, []);
const searchFilter = (text) => {
if (text) {
const newData = contacts.filter((item) => {
const itemData = item.name ? item.name.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilter(newData);
setSearch(text);
} else {
setFilter(contacts);
setSearch(text);
}
};
const onChangeValue = (item, index) => {
if (itemChecked.includes(item.phoneNumbers[0].digits)) {
itemChecked.splice(itemChecked.indexOf(item.phoneNumbers[0].digits), 1);
} else {
itemChecked.push(item.phoneNumbers[0].digits);
setCheckedBox(true);
}
setItemChecked(itemChecked);
console.log(itemChecked);
// console.log(item);
};
const renderItem = ({ item, index }) => {
return (
<SafeAreaView>
<ScrollView>
<TouchableOpacity style={{ flexDirection: 'row', flex: 1 }}>
<View style={{ flex: 1, borderTopWidth: 0.5, borderTopColor: 'grey', marginBottom: 15 }}>
<Text onPress={() => setChecked(true)} style={{ fontSize: 20, marginHorizontal: 10 }}>
{item.name + ' '}
</Text>
<Text style={{ fontSize: 17, marginHorizontal: 10, marginTop: 5, color: 'grey' }}>
{item.phoneNumbers && item.phoneNumbers[0] && item.phoneNumbers[0].number}
</Text>
</View>
<View style={{ flex: 1, borderTopWidth: 0.5, borderTopColor: 'grey' }}>
{itemChecked.includes(item.phoneNumbers[0].digits) === false ? (
<CheckBox
style={{ width: 15, height: 15 }}
right={true}
checked={false}
onPress={() => {
onChangeValue(item, index);
}}
/>
) : (
<CheckBox
style={{ width: 15, height: 15, paddingTop: 8 }}
right={true}
checked={true}
onPress={() => {
onChangeValue(item, index);
}}
/>
)}
</View>
{/* <Post postJson={item} isGroupAdmin={isGroupAdmin} user={user} /> */}
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<View
style={{
height: 40,
justifyContent: 'center',
backgroundColor: '#eeeeee',
width: '90%',
marginHorizontal: 20,
marginTop: 15,
borderRadius: 10,
}}
>
<Feather name="search" size={20} color="grey" style={{ position: 'absolute', left: 32 }} />
<TextInput
placeholder="Search"
placeholderTextColor="#949494"
style={{
left: 20,
paddingHorizontal: 35,
fontSize: 20,
}}
value={search}
onChangeText={(text) => {
searchFilter(text);
setSearch(text);
}}
/>
</View>
<FlatList
style={{ marginTop: 15 }}
data={contacts && filter}
keyExtractor={(item) => `key-${item.id.toString()}`}
renderItem={renderItem}
refreshing={true}
initialNumToRender={10}
ListEmptyComponent={<Text message="No contacts found." />}
/>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
export default ContactList;