How can I autonomously link a sub-collection to an existing doc - react-native
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;
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 ..
Flatlist item title display the previous items title property
ı 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', }, });
Styling conditional FlatList?
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
How to Individual selection?
I'd like to make the entire selection into individual choices in the process of using the map method. But I don't know how. My code: let bouncyCheckboxRef = null; const [bodyType, setBodyType] = useState([]); const [standardSelected, setStandardSelected] = useState(false); const [isSelected, setSelected] = useState([]); const bodyGetData = async () => { let body = []; try { let config = { method: "get", url: "http://everyweardev-env.eba-azpdvh2m.ap-northeast-2.elasticbeanstalk.com/api/v1/user/bodyType/female", headers: {}, }; axios(config).then(function (response) { response.data.data.map((u) => { switch (u.bodyType) { case "standard": body.push({ bodyType: "스탠다드", imgUrl: u.imgUrl, key: 1, isChecked: true, }); break; case "reverseTriangle": body.push({ bodyType: "역삼각형", imgUrl: u.imgUrl, key: 2, isChecked: true, }); break; case "triangle": body.push({ bodyType: "삼각형", imgUrl: u.imgUrl, key: 3, isChecked: true, }); break; case "circle": body.push({ bodyType: "원형", imgUrl: u.imgUrl, key: 4, isChecked: true, }); break; case "hourglass": body.push({ bodyType: "직사각형", imgUrl: u.imgUrl, key: 5, isChecked: true, }); break; } }); return setBodyType(body); }); } catch (err) { console.log(err); } }; useEffect(() => { const unsubscribe = navigation.addListener("focus", () => { bodyGetData(); }); return unsubscribe; }, [navigation]); const checkBoxChange = (checked) => { let newCheckBoxes = [...bodyType]; newCheckBoxes.forEach((item) => { if (item.key) { item.isChecked = checked; } }); setBodyType(newCheckBoxes); }; return ( <View style={styles.container}> <Text style={{ fontSize: wp("5.5%"), fontWeight: "bold", marginBottom: 21 }} > 자신의 체형을 선택해 주세요 </Text> <View style={{ flexDirection: "row", marginBottom: hp("10%") }}> <Text style={{ fontSize: wp("4.5%"), marginRight: wp("1%") }}> 더 정확한 평가를 받으실 수 있습니다 </Text> <Image source={require("../../images/smile.png")} style={{ marginTop: hp("0.5%") }} /> </View> <ScrollView horizontal={true} showsHorizontalScrollIndicator={true}> {bodyType.map((data) => ( <> <TouchableHighlight style={styles.bodySelect} value={data.bodyType} onChange={(e) => { console.log(e); }} onPress={() => bouncyCheckboxRef?.onPress()} > <> <BouncyCheckbox checked={data.isChecked} key={data.key} onPress={checkBoxChange} ref={(ref) => (bouncyCheckboxRef = ref)} size={25} fillColor="black" unfillColor="#FFFFFF" iconStyle={{ borderColor: "white" }} textStyle={{ fontFamily: "JosefinSans-Regular" }} disableBuiltInState style={{ alignSelf: "flex-start", marginLeft: wp("3%"), marginTop: hp("2%"), }} /> <SvgCssUri uri={data.imgUrl} width="90%" height="60%" marginTop="-5%" key={data.key} /> <Text style={styles.bodytype}>{data.bodyType}</Text> </> </TouchableHighlight> </> ))} </ScrollView> <TouchableHighlight style={styles.button} onPress={() => { navigation.navigate("얼굴형 정보 입력"); }} underlayColor="gray" > <> <Text style={styles.text}>선택 완료</Text> </> </TouchableHighlight> </View>
Clarifai Custom Model in React Native
Can anyone help with integration of Clarifai Custom trained model in React Native? I found in docs that if you want to use custom instead of general model, you should pass object with model_id and model_version_id. As I created this custom model on their web page it gave me that parameters, but the algorithm won't really work and it returns the error of "request failed status code 400". Did anyone tried to implement this API in React Native? const ScanMachine = props => { const {id} = props.route.params; const selectedCategory = CATEGORIES.find(cat => cat.id === id); const camRef = useRef(null); const [hasPermission, setHasPermission] = useState(null); const [ratio, setRatio] = useState(null); const [capturedPhoto, setCapturedPhoto] = useState(null); const [prediction, setPrediction] = useState([]); useEffect(() => { (async () => { const { status } = await Camera.requestPermissionsAsync(); setHasPermission(status === 'granted'); setRatio('16:9'); })(); }, []); if (hasPermission === null) { return <View />; } if (hasPermission === false) { return <Text>No access to camera</Text>; } async function takePicture() { if(camRef) { let data = await camRef.current.takePictureAsync(); console.log(data); setCapturedPhoto(data.uri); return data.uri; } } async function resize(photo) { let imageManipulate = await ImageManipulator.manipulateAsync(photo, [{resize: {height: 350, width: 300}}], {base64: true}); return imageManipulate.base64; } async function predictions(image) { let pred = await clarifai.models.predict({id: "custom_laundry_id", version: "03f4ff3ce1ba4cf68b77e923d0ce8699"}, image); return pred; } async function detectObject() { let photo = await takePicture(); let resized = await resize(photo); let predict = await predictions(resized); setPrediction(predict.outputs[0].data.concepts) console.log(predict); } return ( <View style={{ flex: 1}}> <Camera style={{ flex: 1, alignItems:'center'}} ref={camRef} ratio={ratio}> <View style={{ flex: 1, backgroundColor: 'transparent', margin: 20, justifyContent: 'space-between'}}> {prediction && <FlatList data={prediction.map(predict => ({ key: predict.name, }))} renderItem={({ item }) => ( <Text style={{fontSize: 17, color: '#fff'}}>{item.key + " "}</Text> )} numColumns={4} /> } </View> <BarcodeMask edgeColor={'#62B1F6'} backgroundColor={'transparent'} width={300} height={350} showAnimatedLine={false} /> <View style={{flex: 1, justifyContent: 'space-between', justifyContent:'flex-end', margin: 20}}> <TouchableOpacity style={{ backgroundColor: 'transparent', alignSelf: 'flex-end'}} onPress={detectObject}> <FontAwesome name="camera" style={{color: '#fff', fontSize: 40, alignContent: 'flex-start'}} /> </TouchableOpacity> </View> </Camera> {/* { capturedPhoto && <View style={{flex: 1, justifyContent: 'center', alignItems: 'center', margin: 20}}> <Image style={{width: '100%', height: '100%', borderRadius: 10}} source={{uri: capturedPhoto}} /> </View> } */} </View> ); };