I'm trying to delete selected image from an array of images and yet only the first image on the list gets deleted whenever i tap any other image to delete. This is surprising cause i implemented the same method when generating a list in the same screen and it deletes the correct list item each time i tap on it to delete.
Here is all the code in the file excluding cascading styles. Its a lot but i Hope this helps.
const AddPost = () => {
const navigation = useNavigation();
// Thumbnail State
const [thumbnail, setThumbnail] = useState(null);
// Media State
const [media, setMedia] = useState([]);
// Details State
const [title, setTitle] = useState('');
const [category, setCategory] = useState('');
const [caption, setCaption] = useState('');
const [price, setPrice] = useState('');
const [details, setDetails] = useState(false);
// Store State
const [itemName, setItemName] = useState('');
const [itemPrice, setItemPrice] = useState('');
const [photoArray, setPhotoArray] = useState([]);
const [storeItems, setStoreItems] = useState([]);
const width = useWindowDimensions().width;
const numColumns = Math.ceil(width / 120);
const pickThumbnail = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 0.5,
});
if (!result.cancelled) {
setThumbnail(result.uri);
}
};
const pickMedia = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 0.2,
});
if (!result.cancelled) {
setMedia([...media, result.uri]);
}
};
const deleteMedia = (index) => {
let removeMedia = [...media];
removeMedia.splice(index, 1);
setMedia(removeMedia)
console.log(index)
};
const addDetailItem = () => {
if (title == '' || category == '' || caption == '' || price == '') {
Alert.alert('Error', 'Please fill in the item detail.')
} else {
Keyboard.dismiss();
setDetails(true);
detailsRef?.current?.close();
};
};
const detailsRef = useRef(null);
const toggleModal = () => {
detailsRef?.current?.present();
}
const toggleModalClose = () => {
detailsRef?.current?.close();
}
const addItem = {
id: Math.floor(Math.random() * 11),
itemName: itemName,
itemPrice: itemPrice,
photo: photoArray
};
const storeRef = useRef(null);
const toggleStoreModal = () => {
storeRef?.current?.present();
}
const toggleStoreModalClose = () => {
storeRef?.current?.close();
}
const addStoreItem = () => {
if (itemName == '' || itemPrice == '') {
Alert.alert('Error', 'Please fill in the item detail.')
} else {
Keyboard.dismiss();
setStoreItems([...storeItems, addItem]);
setItemName(null);
setItemPrice(null);
setPhotoArray([]);
};
};
const deleteItem = (index) => {
let removeItem = [...storeItems];
removeItem.splice(index, 1);
setStoreItems(removeItem)
};
const pickPhoto = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
quality: 0.2,
});
if (!result.cancelled) {
setPhotoArray([...photoArray, result.uri]);
}
};
const deletePhoto = (index) => {
let removeItem = [...photoArray];
removeItem.splice(index, 1);
setPhotoArray(removeItem)
};
const store = () => {
setStoreItems([...storeItems]);
storeRef?.current?.close();
Keyboard.dismiss();
};
return (
<View style={styles.container}>
{/* Header */}
<Header
title={'Add Post'}
leftComponent={
<View style={styles.headerLeft}>
<Image source={images.logo} style={{ height: 28, width: 28, tintColor: COLORS.primary2}} />
</View>
}
rightComponent={
<TouchableOpacity
style={{
width: 50,
height: 50,
alignItems: 'center',
justifyContent: 'center',
}}
onPress= {() => console.log('Post Sumbitted') }
>
<Image
source={icons.post2}
style={{
width: 35,
height: 35,
tintColor: COLORS.primary2,
}}
/>
</TouchableOpacity>
}
/>
<View style={styles.subContainer}>
<ScrollView>
<Text style={styles.textDetail}>Add a photo or video post with thumbnail, category, caption, price, and store details (Optional).</Text>
{/* Add Thumbnail */}
<AddPostItem label={'Thumbnail'} onPress={pickThumbnail}/>
{thumbnail &&
<View style={styles.thumbnail}>
<Image source={{ uri: thumbnail }} style={styles.thumbnailPhoto} />
</View>
}
{/* Add Media */}
<AddPostItem label={'Media (Video/Photo)'} onPress={pickMedia} />
<View style={{flexDirection: 'row'}}>
{Array.from(Array(numColumns)).map((col, colIndex) => (
<View style={{flex: 1}} key={colIndex}>
{media
.filter((item, index) => index % numColumns === colIndex)
.map((item, index) => (
<View key={index.toString()} style={styles.mediaContainer} >
<Image source={{uri: item}} style={styles.addedMedia}/>
<TouchableOpacity style={styles.imageCancel} key={index} onPress={() => deleteMedia(index)}>
<Image source={icons.cross} style={styles.imageCancelIcon}/>
</TouchableOpacity>
</View>
))}
</View>
))
}
</View>
{/* Add Details */}
<AddPostItem label={'Details (Title, Caption, etc)'} onPress={toggleModal}/>
{details &&
<View style={{marginHorizontal: 20}}>
<View style={{flexDirection: 'row', }}>
<Text style={{color: COLORS.primary2, ...FONTS.body4}}>Title: </Text>
<Text style={{color: COLORS.white, ...FONTS.body4}}>{title}</Text>
</View>
<View style={{flexDirection: 'row', marginTop: 5,}}>
<Text style={{color: COLORS.primary2, ...FONTS.body4}}>Category: </Text>
<Text style={{color: COLORS.white, ...FONTS.body4}}>{category}</Text>
</View>
<View style={{flexDirection: 'row', marginTop: 5,}}>
<Text style={{color: COLORS.primary2, ...FONTS.body4}}>Price: </Text>
<Text style={{color: COLORS.white, ...FONTS.body4}}>N{price}</Text>
</View>
<View style={{marginTop: 5,}}>
<Text style={{color: COLORS.primary2, ...FONTS.body4}}>Caption: </Text>
<Text style={{color: COLORS.white, ...FONTS.body4}}>{caption}</Text>
</View>
</View>
}
{/* Add Store */}
<AddPostItem label={'Store Items (Optional)'} onPress={toggleStoreModal}/>
{storeItems.map((item, index) => {
return (
<View key={index.toString()} style={styles.itemContainer}>
<Text style={styles.itemName}>{item?.itemName}</Text>
<Text style={styles.itemPrice}>N{item?.itemPrice}</Text>
</View>
)
})
}
<View>
</View>
</ScrollView>
</View>
<BottomSheetModal
enablePanDownToClose={true}
ref={detailsRef}
snapPoints={['100%']}
index={0}
backgroundComponent={
({style}) =>
<View style={[style, {
backgroundColor: COLORS.primary,
borderRadius: 30,
}]}/>}
>
{/* Post Details */}
{/* Header */}
<ModalHeader left={toggleModalClose} right={addDetailItem} />
<View style={{flex: 1, backgroundColor: COLORS.primary3, borderTopRightRadius: 35, borderTopLeftRadius: 35}}>
<View style={styles.addDetailText}>
<Text style={styles.textHeader}>Add details</Text>
<Text style={styles.textBody}>Provide the necessary details for your post.</Text>
</View>
<View style={{paddingHorizontal: 10}}>
<FormInput
autoCapitalize='words'
placeholder={'Give it a title'}
onChange={setTitle}
value={title}
/>
<FormInput
autoCapitalize='words'
placeholder={'Category'}
onChange={setCategory}
value={category}
/>
<FormMultiText
autoCapitalize='sentences'
placeholder={'Caption'}
onChange={setCaption}
multiline={true}
numberOfLines={4}
value={caption}
/>
<FormInput
keyboardType='number-pad'
placeholder={'Price (Optional)'}
onChange={setPrice}
value={price}
/>
</View>
<Text style={{color: COLORS.gray, ...FONTS.body5, paddingHorizontal: 10, marginTop: 5, marginLeft: 65}}>Leave this input empty for a "FREE POST".</Text>
</View>
</BottomSheetModal>
<BottomSheetModal
enablePanDownToClose={false}
ref={storeRef}
snapPoints={['100%']}
index={0}
backgroundComponent={
({style}) =>
<View style={[style, {
backgroundColor: COLORS.primary,
borderRadius: 30,
}]}/>}
>
<ModalHeader left={toggleStoreModalClose} right={store} />
<View style={styles.storeItemConatainer}>
<View style={{flex: 1, backgroundColor: COLORS.primary3, borderTopRightRadius: 35, borderTopLeftRadius: 35}}>
<View >
<View style={styles.titleBar}>
<Text style={styles.textHeader}>Add store items (Optional)</Text>
</View>
<Text style={styles.textBody}>Include items available for purchase and their item price. You can add more than one item and multiple photo(s) to each item.</Text>
</View>
{/* Store Items */}
<TouchableOpacity onPress={pickPhoto} style={styles.addPhotoBtn}>
<Text style={{color: COLORS.primary, textAlign: 'center', ...FONTS.body3}}>Add Photo(s)</Text>
</TouchableOpacity>
{/* Photo Container */}
<View style={{flexDirection: 'row'}}>
{Array.from(Array(numColumns)).map((col, colIndex) => (
<View style={{flex: 1}} key={colIndex}>
{photoArray
.filter((item, index) => index % numColumns === colIndex)
.map((item, index) => (
<View key={index.toString()} style={styles.photoContainer}>
<Image source={{uri: item}} style={styles.addedPhoto}/>
<TouchableOpacity style={styles.photoCancel} onPress={() => deletePhoto(index)}>
<Image source={icons.cross} style={styles.photoCancelIcon}/>
</TouchableOpacity>
</View>
))}
</View>
))
}
</View>
<View style={styles.storeInput}>
<TouchableOpacity onPress={addStoreItem}>
<View style={styles.iconContainer}>
<Image source={icons.add} style={styles.icon}/>
</View>
</TouchableOpacity>
<View >
<FormStoreInput
autoCapitalize='words'
placeholder={'Item Name'}
onChange={setItemName}
value={itemName}
/>
<FormStoreInput
keyboardType='number-pad'
placeholder={'Item Price'}
onChange={setItemPrice}
value={itemPrice}
/>
</View>
</View>
<BottomSheetScrollView>
{storeItems.map((item, index) => {
return (
<View key={index.toString()} style={{flexDirection: 'row', marginBottom: 10, marginTop: 5}}>
<AddStoreItem items={item}/>
<TouchableOpacity style={styles.cancel} key={index} onPress={() => deleteItem(index)}>
<Image source={icons.cross} style={styles.cancelIcon}/>
</TouchableOpacity>
</View>
)
})
}
</BottomSheetScrollView>
</View>
</View>
</BottomSheetModal>
</View>
)
}
export default AddPost
This post has been updated.
I guess since we are dealing with images and not an object array that's why index wasnt working. So i changed index with items and decided to us the .filter method instead of .splice. And it worked.
const deleteMedia = (item) => {
setMedia((prev) => prev.filter((el) => el !== item));
console.log(item)
};
You need to declare remove media outside the function delete media because everytime you call delete media. You are reinitalizing the removeMedia useState to the original media values. I did not test these codes, but from the codes I can clearly tell that you are just reinitalizing remove media back to its original value. Try this
let removeMedia=[...media];
const deleteMedia = (index) => {
removeMedia.splice(index, 1);
setMedia(removeMedia)
};
Related
Im working on a react-native project and what I'm trying to do is for the user to have the possibility to select phone numbers in his contact list.
When the user selects one or more contacts, the app won't work, and it shows this error on the console: VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc.
ContactList.js
unction ContactList() {
const [refreshing, setRefreshing] = React.useState(false);
const [itemChecked, setItemChecked] = useState([]);
const [checked, setChecked] = useState(new Map());
const [contacts, setContacts] = useState([]);
const [filter, setFilter] = useState([]);
const [search, setSearch] = useState('');
const [data, setData] = useState(filter)
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) => {
checked.set(item, true);
};
useEffect(() => {
checked &&
setData((previous) => [...previous, {phone: contacts} ])
}, [checked],
)
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' }}>
<CheckBox
style={{ width: 15, height: 15 }}
right={true}
checked={checked.get(index)}
onPress={()=> onChangeValue(index)}
/>
</View>
</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}
ListEmptyComponent={<Text message="No contacts found." />}
/>
</View>
</SafeAreaView>
);
}
export default ContactList;
How can I solve this bug?
I am using flatlist for show user input notes in my app. But Flat List is not render any item that i entered. I used static list for testing and it works correctly. but when i enter a text through textinput it is not showing.
const addToDoHandler = () => {
if(userInput == ''){
return;
}
const newTodo = {
id: Math.floor(Math.random() * 1000),
text: userInput,
completed: false,
};
const newTodos = [...todos, newTodo];
setTodos(newTodos);
//clear user input
setUserInput('');
};
const renderItem = ( {item} ) => {
<View style={{backgroundColor:'#6b4d87', marginVertical: 10, flexDirection:'row', alignItems:'center', justifyContent:'space-between', height:60, width:'95%', borderRadius:10 }}>
<View>
<Text style={{color:'black', fontSize:20, fontWeight:'bold'}}>{item.text}</Text>
</View>
<TouchableOpacity onPress={() => deleteHandler(item.id)}>
<TrashIcon size={35} name="trash" style={{color:'#eff9f0'}} />
</TouchableOpacity>
</View>
};
return (
<SafeAreaView style={styles.root}>
<View style={styles.txtInputView}>
<TextInput
value={userInput}
onChangeText={text => setUserInput(text)}
style={styles.textInput}
placeholder={"Enter Your Note..."}
/>
<TouchableOpacity style={styles.addButton} onPress={addToDoHandler}>
<PlusSign size={35} name="add-sharp" style={styles.plusIcon}/>
</TouchableOpacity>
</View>
<View style={{alignItems:'center'}}>
<FlatList
data={todos}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</View>
</SafeAreaView>
);
You need to return the components from renderItem.
Change it to:
const renderItem = ( {item} ) => (
<View style={{backgroundColor:'#6b4d87', marginVertical: 10, flexDirection:'row', alignItems:'center', justifyContent:'space-between', height:60, width:'95%', borderRadius:10 }}>
<View>
<Text style={{color:'black', fontSize:20, fontWeight:'bold'}}>{item.text}</Text>
</View>
<TouchableOpacity onPress={() => deleteHandler(item.id)}>
<TrashIcon size={35} name="trash" style={{color:'#eff9f0'}} />
</TouchableOpacity>
</View>
);
I have a problem like picture below:
Text
As you guy can see I have info on every item: name and stock. Now I want to update stock on single item by type a number to text Input But when I type 3 into 1 Text Input, it fills 3 in all the remaining Text Input. This is my render Item:
renderItem = ({item}) => {
return (
<View style={styles.card}>
<TouchableOpacity
onPress={() => {
setCreateAt(item.WarehouseProduct.createdAt);
setNote(item.note);
setShowModal(true);
}}>
<Image
style={styles.tinyLogo}
source={require('../assets/images/fish.jpg')}
/>
</TouchableOpacity>
<View
style={{
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
marginLeft: 5,
height: 75,
width: 160,
}}>
<Text numberOfLines={2} style={styles.text}>
{item.name}
</Text>
<Text style={styles.text}>
{item.WarehouseProduct.stock.toString()}
</Text>
</View>
<View style={styles.iconcontainer}>
<Button title="clear" onPress={() => console.log(text)} />
</View>
<View
style={{
alignSelf: 'center',
marginLeft: 20,
borderWidth: 1,
borderRadius: 10,
}}>
<TextInput
style={{height: 40, width: 50}}
keyboardType="numeric"
onChangeText={(income) => setText(income)}
value={text}
/>
</View>
</View>
);
};
Can anyone help me solve this problem? Just give me an idea. Thanks all!
anything you put in renderitem will be rendered as much as your data in flatlist
<Flatlist
data={containtmultipledata}
renderItem={
....
<TextInput
style={{height: 40, width: 50}}
keyboardType="numeric"
onChangeText={(income) => setText(income)}
value={text}
/>
....
}
>
but you asign it to single value
you can change data you put in flatlist directly by index in item
<TextInput
style={{height: 40, width: 50}}
keyboardType="numeric"
onChangeText={(txt) => containMultipledata[index].yourObjectName = txt}
value={item.yourObjectName}
/>
Try this way
const [data, setData] = useState([... flat list data here default]);
const onTextChanged = (index, value) => {
const data = [...data];
data[index].availableStock = value; <-- "availableStock" is example key for showing way out of this -->
setData(data);
}
renderItem = ({item, index}) => {
return (
<View style={styles.card}>
......
<TextInput
...
onChangeText={(income) => onTextChanged(index, income)}
value={item.availableStock || 0}
/>
</View>
);
};
I have a list of cards,
when pressed to any of them, I want to Increase hight for that card,
So I use layoutAnimation to handle this case because when I use Animated API from RN, I got an error when setNativeDrive "height"
Anyway,
In my case, it increases height for every card in the list,
So how can I solve it?
Code snippet
Live Demo
const OpenedAppointments = ({slideWidth}) => {
const [currentIndex, setCurrentIndex] = React.useState(null);
const [cardHeight, setCardHeight] = useState(140);
const collapseCard = (cardIndex) => {
setCurrentIndex(cardIndex);
currentIndex === cardIndex
? (LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut),
Animated.spring(),
setCardHeight(200))
: (LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut),
Animated.spring(),
setCardHeight(140));
};
const renderItems = (item, index) => {
return (
<TouchableOpacity
delayPressIn={0}
onPress={() => collapseCard(index)}
key={item.id}>
<Animated.View
style={[
styles.itemContainer,
{
height: cardHeight,
},
]}>
<View style={styles.headerContainer}>
<View style={styles.imgContainer}>
<Image
resizeMode="cover"
style={styles.img}
source={{uri: item.vendorInfo.vendorImg}}
/>
</View>
<View style={{width: '80%'}}>
<View style={styles.titleContainer}>
<Text numberOfLines={1} style={styles.vendorName}>
{item.vendorInfo.name}
</Text>
<View style={{flexDirection: 'row', alignItems: 'center'}}>
<Text style={styles.rateText}>{item.vendorInfo.rate}</Text>
<AntDesign name="star" size={18} color="#262626" />
</View>
</View>
<View style={styles.socialContainer}>
<View
style={{
width: 32,
height: 32,
backgroundColor: '#000',
borderRadius: 5,
}}
/>
</View>
</View>
</View>
<View style={styles.footer}>
<Text style={styles.serviceName}>
{item.serviceName}
</Text>
<Text style={styles.price}>
{item.price}
</Text>
</View>
// this will appeared when pressed to card "Footer Card"
<View>
<View style={styles.line} />
<View style={styles.editFooter}>
<View style={styles.dateContainer}>
<MemoCalender
style={styles.calenderIcon}
size={26}
color="#000"
/>
<View style={styles.dateBox}>
<Text style={styles.dateText}>{item.date}</Text>
<Text style={styles.dateText}>{item.time}</Text>
</View>
</View>
</View>
</View>
</Animated.View>
</TouchableOpacity>
);
};
return(
<>
{Data.opened.map((item, index) => renderItems(item, index))}
</>
);
}
Change this:
<Animated.View
style={[
styles.itemContainer,
{
height: cardHeight,
},
]
>
{...}
</Animated.View>
by
<Animated.View
style={[
styles.itemContainer,
{
height: currentIndex === index ? cardHeight : 140,
},
]
>
{...}
</Animated.View>
If you want to be more efficient, the default height of your card, define it in a constant (ex: const = DEFAULT_CARD_HEIGHT = 140) and use this constant wherever you use 140 as card height
I am not sure how to add a delete function in a FlatList. I know I can make different components, but I want to know how to do it within this one file. I've trying to figure this out for hours, but do not know how to do.
export default function test() {
const [enteredGoal, setEnteredGoal] = useState("");
const [courseGoals, setCourseGoals] = useState([]);
const goalInput = enteredText => {
setEnteredGoal(enteredText);
};
const addGoal = () => {
setCourseGoals(currentGoals => [
...currentGoals,
{ key: Math.random().toString(), value: enteredGoal }
]);
};
const removeGoal = goalId => {
setCourseGoals(currentGoals => {
return currentGoals.filter((goal) => goal.id !== goalId);
})
}
return (
<View style={styles.container}>
<View>
<TextInput
color="lime"
style={styles.placeholderStyle}
placeholder="Type here"
placeholderTextColor="lime"
onChangeText={goalInput}
value={enteredGoal}
/>
</View>
<FlatList
data={courseGoals}
renderItem={itemData => (
<View style={styles.listItem} >
<Text style={{ color: "lime" }}>{itemData.item.value}</Text>
</View>
)}
/>
<View>
<TouchableOpacity>
<Text style={styles.button} onPress={addGoal}>
Add
</Text>
</TouchableOpacity>
</View>
</View>
);
}
You just need to modify your code a bit to handle the delete button. Since you already have delete functionality, call that function when you click the delete button. That's it.
<FlatList
data={courseGoals}
renderItem={itemData => (
<View style={{ flexDirection: "row", justifyContent: "space-between" }}>
<Text style={{ color: "lime" }}>{itemData.item.value}</Text>
<TouchableOpacity onPress={() => removeGoal(itemData.item.key)}>
<Text>Delete</Text>
</TouchableOpacity>
</View>
)}
/>;
EDIT
change your removeGoal function as below
const removeGoal = goalId => {
setCourseGoals(courseGoals => {
return courseGoals.filter(goal => goal.key !== goalId);
});
};
Hope this helps you. Feel free for doubts.