Related
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 ..
ı am trying to create a title property for my recording. When ı type the name of the recording, if it's first on the list the title property became blank. After creating the second recording and typing the name it gets the title of the previous recording object. Tried to change the calling order of the modal function in stopRecording function, but it didn't work. I hope you can help. My code is not clean for now ı will arrange it later, please ignore it
import {
StyleSheet,
Text,
View,
Button,
FlatList,
TextInput,
Modal,
} from "react-native";
import { Audio } from "expo-av";
import { useState } from "react";
import * as Sharing from "expo-sharing";
export default function App() {
const [recording, setRecording] = useState();
const [recordings, setRecordings] = useState([]);
const [message, setMessage] = useState("");
const [modalVisible, setModalVisible] = useState(false);
const [title, setTitle] = useState("");
async function startRecording() {
try {
const permission = await Audio.requestPermissionsAsync();
if (permission.status === "granted") {
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
const { recording } = await Audio.Recording.createAsync(
Audio.RecordingOptionsPresets.HIGH_QUALITY
);
setRecording(recording);
} else {
setMessage("Please grant permission to app to access microphone");
}
} catch (err) {
console.error("Failed to start recording", err);
}
}
async function stopRecording() {
setModalVisible(!modalVisible);
setRecording(undefined);
await recording.stopAndUnloadAsync();
let updatedRecordings = [...recordings];
const { sound, status } = await recording.createNewLoadedSoundAsync();
updatedRecordings.push({
sound: sound,
file: recording.getURI(),
id: parseInt(Math.random() * 1000),
title: title,
});
setRecordings(updatedRecordings);
setTitle("");
}
function deleteItem(id) {
const filteredData = recordings.filter((item) => item.id !== id);
setRecordings(filteredData);
}
function recordingView(item) {
console.log(item);
return (
<View style={styles.row}>
<Text style={styles.fill}>{item.item.title}</Text>
<Button
style={styles.button}
onPress={() => item.item.sound.replayAsync()}
title="Play"
></Button>
<Button
style={styles.button}
onPress={() => Sharing.shareAsync(item.item.file)}
title="Share"
></Button>
<Button
style={styles.button}
onPress={() => {
deleteItem(item.item.id);
}}
title="Delete"
></Button>
</View>
);
}
return (
<>
<View style={styles.container}>
<Text>{message}</Text>
<FlatList
data={recordings}
keyExtractor={(item, index) => index}
renderItem={recordingView}
/>
<Button
title={recording ? "Stop Recording" : "Start Recording"}
onPress={recording ? stopRecording : startRecording}
/>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
setModalVisible(!modalVisible);
}}
>
<View>
<View style={styles.generalContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="Recording name"
onChangeText={(title) => {
setTitle(title);
}}
/>
<View style={styles.buttonContainer}>
<Button
title="Add"
onPress={() => setModalVisible(!modalVisible)}
/>
</View>
</View>
</View>
</View>
</Modal>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
margin: 10,
},
row: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
},
fill: {
flex: 1,
margin: 16,
},
button: {
margin: 16,
},
input: {
textAlignVertical: "top",
fontSize: 17,
width: "100%",
height: 40,
marginTop: 10,
marginBottom: 10,
borderColor: "#ffffff",
width: "100%",
borderWidth: 2,
borderRadius: 15,
padding: 10,
color: "#ffffff",
},
generalContainer: {
margin: 30,
paddingTop: 10,
paddingBottom: 10,
backgroundColor: "gray",
borderRadius: 15,
elevation: 4,
},
inputContainer: {
padding: 20,
alignItems: "center",
},
});
The problem with your current code is that in your stopRecording() function, you tried to get the title by having the user key in the name from the modal but it will not work because that portion is synchronous meaning that the code below setModalVisable will already have ran before the user can even input a title. To fix this you must first get the user to key in the title before the recording can be saved.
I have modified the code to fix the issue.
import {
StyleSheet,
Text,
View,
Button,
FlatList,
TextInput,
Modal,
} from 'react-native';
import { Audio } from 'expo-av';
import { useState, useEffect } from 'react';
export default function App() {
const [recording, setRecording] = useState();
const [recordings, setRecordings] = useState([]);
const [isRecording, setIsRecording] = useState(false);
const [message, setMessage] = useState('');
const [modalVisible, setModalVisible] = useState(false);
const [title, setTitle] = useState('');
useEffect(() => {
(async () => {
if (!modalVisible && title != '') {
saveRecording();
}
})();
}, [modalVisible]);
async function startRecording() {
try {
const permission = await Audio.requestPermissionsAsync();
if (permission.status === 'granted') {
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
});
const { recording } = await Audio.Recording.createAsync(
Audio.RecordingOptionsPresets.HIGH_QUALITY
);
setRecording(recording);
setIsRecording(true);
} else {
setMessage('Please grant permission to app to access microphone');
}
} catch (err) {
console.error('Failed to start recording', err);
}
}
async function stopRecording() {
try {
await recording.stopAndUnloadAsync();
} catch (error) {
console.log(error);
}
setIsRecording(false);
setModalVisible(!modalVisible);
}
async function saveRecording() {
try {
// setRecording(undefined);
// await recording.stopAndUnloadAsync();
let updatedRecordings = [...recordings];
const { sound, status } = await recording.createNewLoadedSoundAsync();
updatedRecordings.push({
sound: sound,
file: recording.getURI(),
id: parseInt(Math.random() * 1000),
title: title,
});
setRecordings(updatedRecordings);
setTitle('');
} catch (error) {
console.log(error);
}
}
function deleteItem(id) {
const filteredData = recordings.filter((item) => item.id !== id);
setRecordings(filteredData);
}
function recordingView(item) {
console.log(item);
return (
<View style={styles.row}>
<Text style={styles.fill}>{item.item.title}</Text>
<Button
style={styles.button}
onPress={() => item.item.sound.replayAsync()}
title="Play"></Button>
<Button
style={styles.button}
onPress={() => Sharing.shareAsync(item.item.file)}
title="Share"></Button>
<Button
style={styles.button}
onPress={() => {
deleteItem(item.item.id);
}}
title="Delete"></Button>
</View>
);
}
return (
<>
<View style={styles.container}>
<Text>{message}</Text>
<FlatList
data={recordings}
keyExtractor={(item, index) => index}
renderItem={recordingView}
/>
<Button
title={isRecording ? 'Stop Recording' : 'Start Recording'}
onPress={isRecording ? stopRecording : startRecording}
/>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
setModalVisible(!modalVisible);
}}>
<View>
<View style={styles.generalContainer}>
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
placeholder="Recording name"
onChangeText={(title) => {
setTitle(title);
}}
/>
<View style={styles.buttonContainer}>
<Button
title="Add"
onPress={() => setModalVisible(!modalVisible)}
/>
</View>
</View>
</View>
</View>
</Modal>
</View>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
margin: 10,
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
fill: {
flex: 1,
margin: 16,
},
button: {
margin: 16,
},
input: {
textAlignVertical: 'top',
fontSize: 17,
width: '100%',
height: 40,
marginTop: 10,
marginBottom: 10,
borderColor: '#ffffff',
width: '100%',
borderWidth: 2,
borderRadius: 15,
padding: 10,
color: '#ffffff',
},
generalContainer: {
margin: 30,
paddingTop: 10,
paddingBottom: 10,
backgroundColor: 'gray',
borderRadius: 15,
elevation: 4,
},
inputContainer: {
padding: 20,
alignItems: 'center',
},
});
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;
Code:
import React, { Component,useState } from 'react';
import {
StyleSheet,
View,
TouchableHighlight,
Text,
ToastAndroid,
ScrollView,
Modal,Button
} from 'react-native';
import ProgressCircle from 'react-native-progress-circle'
import AutoTags from 'react-native-tag-autocomplete';
import firebase from '../config';
import { TabView, SceneMap } from 'react-native-tab-view';
const db= firebase.database();
let itemsRef = db.ref('/dataset');
let input = db.ref('/tags');
let disease=[];
itemsRef.limitToFirst(10).on('value', (snapshot) => {
snapshot.forEach((childSnapshot) => {
disease.push(childSnapshot.val().Disease);
});
});
let addItem = item => {
input.push({
userSymptom: item
});
};
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
tagsSelected:[],
storedTags:[],
index: 0,
routes: [
{ key: 'first', title: 'First' },
{ key: 'second', title: 'Second' },
{ key: 'third', title: 'Third'}],
modalVisible: false,
};
}
componentDidMount(){
let storedTags=[];
itemsRef.on('value', (snapshot) => {
snapshot.forEach((childSnapshot) => {
storedTags.push({'name':childSnapshot.val().Symptom});
});
});
let userSymptoms=[];
input.limitToLast(1).on('value', (snapshot)=>{
snapshot.forEach((childSnapshot)=>{
childSnapshot.val().userSymptom.forEach(element=>{
userSymptoms.push(element)
})
})
})
this.setState({ tagsSelected:userSymptoms})
this.setState({ storedTags: storedTags})
}
setModalVisible(visible){
this.setState({ modalVisible: visible });
}
Modal=()=>(
<View style={styles.centeredView}>
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
onRequestClose={() => {
console.log("Modal has been closed.");
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalText}>Hello World!</Text>
<TouchableHighlight
style={{ ...styles.openButton, backgroundColor: "#2196F3" }}
onPress={() => {
this.setModalVisible(!this.state.modalVisible);
}}
>
<Text style={styles.textStyle}>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
</View>
);
FirstRoute = () => (
<ScrollView style={[styles.container, { backgroundColor: '#ff4081' }]} >
{this.Modal()}
<TouchableHighlight style={styles.back} onPress={() => {
this.setModalVisible(true);
console.log('Pressed')
}}>
<View style={styles.DisEntry}>
<Text style={styles.text}>{disease[0]}</Text>
<ProgressCircle
percent={30}
radius={50}
borderWidth={8}
color="#3399FF"
shadowColor="#999"
bgColor="#fff"
>
<Text style={{ fontSize: 18 }}>{'30%'}</Text>
</ProgressCircle>
</View>
</TouchableHighlight>
</ScrollView>
);
SecondRoute = () => (
<View style={[styles.container, { backgroundColor: '#673ab7' }]}>
</View>
);
ThirdRoute = () => (
<View style={[styles.container, { backgroundColor: '#673ab7' }]}>
</View>
);
handleSubmit = () => {
addItem(this.state.tagsSelected);
ToastAndroid.show('Symptoms saved successfully', ToastAndroid.SHORT)
};
handleDelete = index => {
let tagsSelected = this.state.tagsSelected;
tagsSelected.splice(index, 1);
this.setState({ tagsSelected });
}
handleAddition = suggestion => {
this.setState({ tagsSelected: this.state.tagsSelected.concat([suggestion]) });
}
_handleIndexChange = index => this.setState({ index });
_renderScene = SceneMap({
first: this.FirstRoute,
second: this.SecondRoute,
third: this.ThirdRoute,
});
render() {
return (
<View style={styles.container}>
<View style={styles.row}>
<View style={styles.autocompleteContainer}>
<AutoTags
suggestions={this.state.storedTags}
tagsSelected={this.state.tagsSelected}
handleAddition={this.handleAddition}
handleDelete={this.handleDelete}
placeholder="Add a Symptom.." />
</View>
<TouchableHighlight
style={styles.button}
underlayColor="blue"
onPress={this.handleSubmit}>
<Text style={styles.buttonText}>Add</Text>
</TouchableHighlight>
</View>
<TabView
style={styles.tab}
navigationState={this.state}
renderScene={this._renderScene}
renderTabBar={this._renderTabBar}
onIndexChange={this._handleIndexChange}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex:1,
},
row:{
flexDirection:"row"
},
DisEntry:{
flexDirection:"row",
backgroundColor:'white',
borderColor: 'black',
borderWidth: 1,
borderRadius: 5,
padding: 2,
},
back:{
backgroundColor: 'blue'
},
text:{
flex:1,
},
button:{
flex:0,
backgroundColor:'white',
borderColor: 'black',
borderWidth: 1,
borderRadius: 5,
padding: 2,
justifyContent:'flex-end',
height:30,
justifyContent:'center',
},
autocompleteContainer: {
flex:1,
justifyContent:'flex-start',
marginBottom:10
},
tab:{
flex:1,
},
centeredView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
marginTop: 22
},
modalView: {
margin: 20,
backgroundColor: "white",
borderRadius: 20,
padding: 35,
alignItems: "center",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.25,
shadowRadius: 3.84,
elevation: 5
},
openButton: {
backgroundColor: "#F194FF",
borderRadius: 20,
padding: 10,
elevation: 2
},
textStyle: {
color: "white",
fontWeight: "bold",
textAlign: "center"
},
modalText: {
marginBottom: 15,
textAlign: "center"
}
});
I am facing the problem that i am unable to toggle the Modal visibility when i click the touchable highlight. Please tell me where is the issue in my code as when i change the state of my modalVisible to true the modal shows finely
.....................................................................................................
If i'm not mistaken Modal has to be inside of return()
class App extends Component {
state = {
modalVisible: false
};
setModalVisible = (visible) => {
this.setState({ modalVisible: visible });
}
render() {
const { modalVisible } = this.state;
return (
<View style={styles.centeredView}>
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
}}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalText}>Hello World!</Text>
<TouchableHighlight
style={{ ...styles.openButton, backgroundColor: "#2196F3" }}
onPress={() => {
this.setModalVisible(!modalVisible);
}}
>
<Text style={styles.textStyle}>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
<TouchableHighlight
style={styles.openButton}
onPress={() => {
this.setModalVisible(true);
}}
>
<Text style={styles.textStyle}>Show Modal</Text>
</TouchableHighlight>
</View>
);
}
}
Hi how can I access the state where I'm fetching all the data in an other component ? I have a component Accueil where I'm fetching the data and a component UserItem where I'm styling and showing the info. So I'm in my Accueil component I have a flatlist and inside this flatlist in the renderItem function I'm passing the . So how can I access the state dataSource in UserItem ?
class UserItem extends React.Component {
render() {
const user = this.props.user;
const displayDetailForUser = this.props.displayDetailForUser;
var colorConnected;
if (user.Statut == "ON") {
colorConnected = "#1fbc26";
}
else if (user.Statut == "OFF") {
colorConnected = "#ff0303";
}
else {
colorConnected = "#ffd200";
}
return (
<TouchableOpacity style={styles.view_container} onPress={() =>
displayDetailForUser(user.MembreId, user.Pseudo, user.Photo, user.Age, user.Genre, user.Description, user.distance, user.Statut, user.localisation, user.principale )}>
<ImageBackground source={{ uri : user.Photo}} style={{ flex: 1, aspectRatio: 1, position: 'relative', borderColor: '#d6d6d6', borderWidth: 1}}>
<View style={[styles.bulle_presence, { backgroundColor: colorConnected } ]}></View>
<TouchableOpacity>
<Image style = {{aspectRatio:0.6, position:'absolute', resizeMode:'contain', height:40, width:40, right:15, top:15 }} source = {require("../Images/chat-bg.png")}/>
</TouchableOpacity>
</ImageBackground>
<View style={{ backgroundColor: '#d6d6d6', padding: 6 }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View>
<Text style={{ fontSize: 25 }}>{user.Age}</Text>
</View>
<View style={{ marginRight: 7, marginLeft: 7, backgroundColor: '#000000', width: 1, height: 26 }}></View>
<View style={{ flexDirection: 'column', flex:1 }}>
<Text style={{ fontSize: 13, fontWeight: '600' }}>{user.Pseudo}</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between'}}>
<Text style={{ fontSize: 12 }}>{user.distance}</Text>
<Text style={{ fontSize: 12 }}>{user.Genre}</Text>
</View>
</View>
</View>
</View>
</TouchableOpacity>
)
}
}
I've tried with const user = this.props.dataSource but it's not working. Thanks
Edit:
lass Accueil extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
refreshing: false,
location: null,
latitude:null,
longitude:null,
dataSource:[]
}
this.displayPosition = this.displayPosition.bind(this);
}
handleRefresh = () => {
this.setState (
{
refreshing: true,
},
() => {
setTimeout(() => {this.setState({refreshing: false,})}, 1000)
}
);
};
componentDidMount() {
const url = "SomeUrl";
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
dataSource: res
});
})
.catch(error => {
console.log("get data error:" + error);
});
}
static navigationOptions = ({ navigation }) => {
return {
headerRight: () => (
<View style={{marginLeft: 8, marginRight: 8, flexDirection: 'row', alignItems: 'center' }}>
<TouchableOpacity style={{ marginRight: 10 }} onPress={ () =>{ }}>
<Image style={{ width: 22, height: 22 }} source={require("../Images/search.png")} />
</TouchableOpacity>
<TouchableOpacity style={{marginLeft: 10, marginRight: 10 }} onPress={ () =>{{this.displayPosition()}}}>
<Image style={{ width: 22, height: 22 }} source={require("../Images/localisation.png")} />
</TouchableOpacity>
<TouchableOpacity style={{marginLeft: 10 }} onPress={ () =>{ }}>
<Image style={{ width: 22, height: 22 }} source={require("../Images/refresh.png")} />
</TouchableOpacity>
</View>
),
};
};
displayPosition = () => {
Geolocation.getCurrentPosition(
(position) => {
this.setState({
latitude: JSON.stringify(position.coords.latitude),
longitude: JSON.stringify(position.coords.longitude),
error: null,
});
console.log(this.state.latitude)
},
(error) => this.setState({ error: error.message }),
{ enableHighAccuracy: true, timeout: 20000, maximumAge: 1000 },
);
}
_displayDetailForUser = (idUser, name, photo, age, pratique, description, distance, Statut, localisation, principale ) => {
//console.log("Display user with id " + idUser);
this.props.navigation.navigate("UserProfil", { idUser: idUser, name:name, photo: photo, age:age, pratique:pratique, description:description, distance:distance, Statut:Statut, localisation:localisation, principale:principale });
}
render() {
return (
<SafeAreaView style={{ flex:1 }}>
<View style={styles.main_container}>
<FlatList style={styles.flatList}
data={this.state.dataSource}
keyExtractor={(item) => item.MembreId}
renderItem={() => <UserItem user={this.state.dataSource} displayDetailForUser={this._displayDetailForUser} />}
numColumns={numColumns}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh} />
</View>
</SafeAreaView>
)
}
}