How to close swipe item in react-native? - react-native

I have this code (get from native-base example) that is working fine. But after click on either sides (right or left), the 'swipe' still open. I know that there is a method called closeRow(), but I don't know how to apply in this case.
Left button is for 'split' item into 2 or more, while right button is to delete current item. What I need is to close all opened rows in this list. Why all? Because in the case of 'delete' function, the current item is deleted in the right way, but the next-one get right button opened (since it's the same 'index' of list, even if the item itself is different).
This is my current code:
<Container style={styles.container}>
<Header>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name="arrow-back" />
</Button>
</Left>
<Body>
<Title>{translate("title", { ...i18n_opt, name })}</Title>
</Body>
</Header>
<Content>
<Modal
isVisible={this.state.visibleModal === true}
animationIn={"slideInLeft"}
animationOut={"slideOutRight"}
>
{this._renderModalContent()}
</Modal>
<View style={styles.line}>
<View style={{ flex: 2 }}>
<Text style={[styles.alignCen, styles.headerTitle]}>
{translate("lap", { ...i18n_opt })}
</Text>
</View>
<View style={{ flex: 10 }}>
<Text style={[styles.alignCen, styles.headerTitle]}>
{translate("time", { ...i18n_opt })}
</Text>
</View>
<View style={{ flex: 3 }}>
<Text
style={[
{ paddingRight: 10 },
styles.alignRig,
styles.headerTitle
]}
>
{translate("diff", { ...i18n_opt })}
</Text>
</View>
</View>
<List
enableEmptySections
dataSource={laps2}
ref={c => {
this.component = c;
}}
renderRow={data => <PersonalRankItem dados={data} />}
renderLeftHiddenRow={data => (
<Button
full
onPress={() => {
this.setState({
...this.state,
visibleModal: true,
cur_tx_id: tx_id,
cur_lap: data.lap
});
}}
style={{
backgroundColor: "#CCC",
flex: 1,
alignItems: "center",
justifyContent: "center",
marginBottom: 6
}}
>
<MaterialCommunityIcons
name="arrow-split-vertical"
size={20}
color="#5e69d9"
/>
</Button>
)}
renderRightHiddenRow={(data, secId, rowId, rowMap) => (
<Button
full
danger
onPress={_ => {
//alert("Delete");
//console.log("Data.lap:",data.lap);
dispatchDeleteLap(tx_id, data.lap, true);
}}
style={{
flex: 1,
alignItems: "center",
justifyContent: "center",
marginBottom: 6
}}
>
<Icon active name="trash" size={20} />
</Button>
)}
leftOpenValue={70}
rightOpenValue={-70}
/>
</Content>
</Container>

Goal
You need to close all the rows, each time one of row side button clicked. The next problem is when item deleted, the next row is opened even the content is different.
How?
All you need is first, collect the ref of each rows and then when the button clicked, trigger the closeRow method of all ref's row. And the important part, make your row key persistent and unique to avoid problem like in your case.
Quick Code Sample
class Screen extends Component {
constructor(props) {
super(props);
this._rowRefs = [];
}
// this is used to collect row ref
collectRowRefs = (ref) => {
this._rowRefs.push(ref);
};
// your render row function
renderRow = (data) => (
// make this row key consistent and unique like Id, do not use index counter as key
<PersonalRankItem key={data.id} dados={data} ref={this.collectRowRefs}/>
);
// When your hidden side button is clicked
onButtonClicked = (data) => {
// do the button normal action
// ....
// close each row
this._rowRefs.forEach((ref) => {
ref.closeRow();
});
};
// this is your hidden side button
renderLeftHiddenRow = () => (
<Button onClick={this.onButtonClicked} />
);
render() {
// Your List in here
return (
<List
renderRow={this.renderRow}
renderLeftHiddenRow={this.renderLeftHiddenRow}
/>
)
}
}

Related

How to show an icon on last index of flat list

I'm trying to wrap my flat list and trying to show an Icon after last index of Flatlist. I had tried but it works fine on a single row. when we data goes to next row it would not work.
Here is my flat list code. Modal and Input both are my custom component.
const renderItem = ({item}) => {
return (
<TouchableOpacity style={[styles.tagPostContainer, styles.viewTagContainer]}>
<AppText
type={'input'}
label={`#${item}`}
color={colors.placeholder}
containerStyle={[styles.tagPostInactiveTxt, styles.BgAddedTag]}
/>
</TouchableOpacity>
);
};
return(
<>
<View style={{flexDirection:'row',justifyContent:'flex-start'}}>
<FlatList
data={tags}
renderItem={renderItem}
keyExtractor={keyExtractor}
contentContainerStyle={styles.TagFlatlist}
/>
<TouchableOpacity
style={styles.plusIconContainer}
onPress={() => setVisible(true)}>
<CreateBuildIconFocus height={13} width={13} />
</TouchableOpacity>
</View>
<Modal
visible={visible}
buttonLabel={'Done'}
containerWidth={width / 1.7}
onCancel={onCancel}
onSubmit={onClick}
containerHeight={Platform.OS === 'android' ? 200 : 200}>
<Input
placeholder={'Add Tags'}
autoFocus={true}
minWidth={120}
maxWidth={150}
value={tag}
onChangeText={text => setTag(text)}
/>
</Modal>
</>
)
FlatList styles:
TagFlatlist: {
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'center',
},
Here is my design screen.
I find a solution for this. You can implement ListFooterComponent that used for showing something on end of flatlist. Here you can see more details of ListFooterComponent.
Here are the code of Flatlist.
return(
<FlatList
data={tags}
renderItem={renderItem}
keyExtractor={keyExtractor}
ListFooterComponent={
<TouchableOpacity
style={styles.plusIconContainer}
onPress={() => setVisible(true)}>
<CreateBuildIconFocus height={13} width={13} />
</TouchableOpacity>
}
contentContainerStyle={styles.TagFlatlist}
/>
)

Changing a parent state when a child component updates

I have a main component, which has two child components, a flat list and a button group component (from react-native-elements).
I want to update the flat list data when a user taps on one of the button group options, however, I can't really figure this out, I tried to use callbacks, but couldn't really understand how they work, and it didn't work for me.
This is my main component:
return (
<SafeAreaView style={styles.homeContainer}>
<View style={{flex: 1}}>
<View style={styles.headerContainer}>
<View
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}>
<Text style={styles.title}>Groups</Text>
<Avatar
rounded
source={{
uri: profilePhotoURL,
}}
/>
</View>
<Text style={styles.subtitle}>Find people to learn with</Text>
</View>
<OptionChooser /> {/** <---- this is the button group component*/}
<FlatList
data={meetings}
renderItem={({item}) => (
<TouchableOpacity
style={styles.cardButton}
onPress={() =>
navigation.navigate('MeetingDetails', {meeting: item})
}>
<MeetingCard meetingModel={item} style={{flex: 1}} />
</TouchableOpacity>
)}
keyExtractor={(item) => item.id!}
showsVerticalScrollIndicator={false}
/>
</View>
<FloatingButton onPress={() => navigation.navigate('AddMeeting')} />
</SafeAreaView>
);
And this is my button group (OptionChooser) component:
const OptionChooser = () => {
const [selectedIndex, setSelectedIndex] = useState<number>(0);
const buttons = ['All', 'Today', 'This week'];
const updateIndex = (index) => {
setSelectedIndex(index);
console.log(index);
};
return (
<View style={styles.buttonGroupContainer}>
<ButtonGroup
onPress={updateIndex}
selectedIndex={selectedIndex}
buttons={buttons}
containerStyle={{height: 44, borderRadius: 4}}
selectedButtonStyle={{backgroundColor: '#8BCFB0'}}
/>
</View>
);
};
My goal is whenever updateIndex gets called in OptionChooser, to update the flat list in the parent component.
As you have said, callbacks would be the easiest option to use in the situation.
Lets start with your parent component.
Assume that you have two state variables meetings,selectedIndex
Its always a good idea to make the child component dumb and manage the state in parent rather than managing states in both.
Your parent would have the setSelectedIndex which would update the parent selectedIndex state.
so you pass the state and function to the child like below
<OptionChooser selectedIndex={selectedIndex} setSelectedIndex={setSelectedIndex}/>
And you child component will have to be like this
const OptionChooser = ({selectedIndex,setSelectedIndex}) => {
const buttons = ['All', 'Today', 'This week'];
return (
<View style={styles.buttonGroupContainer}>
<ButtonGroup
onPress={setSelectedIndex}
selectedIndex={selectedIndex}
buttons={buttons}
containerStyle={{height: 44, borderRadius: 4}}
selectedButtonStyle={{backgroundColor: '#8BCFB0'}}
/>
</View>
);
};
And in your render you can simply filter the meetings using this state like below
<FlatList data={meetings.filter(x=>x.type==selectedIndex)} ...
//actual condition may vary according to your need.
So whenever your child changes the changes would be reflected in parent.

Modal doesnt open when clicking on TouchableOpacity - React Native

I am trying to implement the modal component in this app and for some reasons, I cant make it work. I have done it in another app and even though everything looks as it should in this one, it still doesn't work, it just doesn't toggle!
Here is my code (i call toogleModal() here ):
<TouchableOpacity
activeOpacity={1}
style={styles.slideInnerContainer}
//onPress={() => { alert(`You've clicked '${rest_name}'`); }}
onPress={() => this.toggleModal(rest_name)}
>
<View style={styles.shadow} />
<View style={[styles.imageContainer, even ? styles.imageContainerEven : {}]}>
{this.image}
<View style={[styles.radiusMask, even ? styles.radiusMaskEven : {}]} />
</View>
<View style={[styles.textContainer, even ? styles.textContainerEven : {}]}>
<View style={{ flexDirection: 'row' }}>
{uppercaseTitle}
{ratings}
</View>
<Text
style={[styles.subtitle, even ? styles.subtitleEven : {}]}
numberOfLines={2}
>
{rest_location}
</Text>
</View>
</TouchableOpacity>
Now here is the toggleModal() which should set the state and then call the onPressItem() :
toggleModal = (item) => {
this.setState({ isModalVisible: !this.state.isModalVisible });
this.onPressItem(item);
};
and onPressItem() :
onPressItem = (item) => {
return (
<ThemeProvider theme={theme}>
<Modal animationIn="rubberBand" animationOut={"bounceOut"}
isVisible={this.state.isModalVisible}
onBackdropPress={() => this.setState({ isModalVisible: false })}
>
<View style={{ flex: 1 }}>
{item}
</View>
<View style={{ flex: 1 }}>
<Button title="Hide modal" onPress={this.toggleModal} />
</View>
</Modal>
</ThemeProvider>
)
};
Now, remember this code is taken from another app where modal is working perfectly!
Most probably your issue with click option is connected with incorrect import TouchableOpacity from correct module. Check if you are importing from react-native:
import { TouchableOpacity } from 'react-native';
change this line
onPress={() => this.toggleModal(rest_name)}
to this:
onPress={() => {this.toggleModal(rest_name)}}
you only need to put the function call in brackets

React Native warning: each child in a list should have a unique 'key' prop

I am creating a render function to display information from within an array. Everything is functioning completely fine, however when I run the project I get an warning saying that each child in the list requires a unique key. I researched all the reasons why this may be happening however I don't see why I am getting this warning. Please help me.
Here is the entire code, from setting the data and rendering the function, thank you:
this.state = {
feed: [{
username: ["Its_Jess: ", "Animal_Luver: ", "Kyl3_Rayn3r: ", "Smith_Family: "],
caption: ["The New Photoshoot was so much fun!", "AWWWW Look at them sleep!!", "Beautiful swim", "This photo is from our vacation <3"],
coments: ["View All 49 Comments", "View All 19 Comments", "View All 69 Comments", "View All 4 Comments"],
photo:[Pic1,Pic2,Pic4,Pic6],
avatar:[Pic1,Ava1,Ava2,Ava3]
}]
}
renderFeed = () => {
return this.state.feed.map((card, index) => {
return card.username.map((username, i) => {
if(card.caption[i])
return (
<View>
<TouchableHighlight
onPress={()=>this.toggleModal({photo:card.photo[i], avatar:card.avatar[i] ,caption:card.caption[i],username:card.username[i],coments:card.coments[i]})}
underlayColor="white">
<Card
key={`${i}_${index}`}
image={card.photo[i]}
containerStyle={{borderRadius:10, marginRight:1, marginLeft:1,}}>
<View
style={{ flex: 1, flexDirection: 'row', justifyContent: 'space-between' }}
>
<View style={{ flexDirection: 'row'}}>
<Avatar
key={card.avatar}
size="small"
rounded
source={card.avatar[i]}
/>
</View>
<View style={{flexDirection:'row'}}>
{this.state.isLiked[i] ?(
<Avatar
rounded
icon={{
name: 'heart-multiple-outline',
type: 'material-community',
}}
overlayContainerStyle={{
backgroundColor: '#ff4284',
marginLeft: 5,
}}
reverse
size="small"
onPress={()=> this.toggleLike(i)}/>
) : (
<Avatar
rounded
icon={{ name:'heart-multiple-outline', type:'material-community', color: '#ff4284'}}
overlayContainerStyle={{marginLeft:5}}
reverse
size='small'
onPress={()=> this.toggleLike(i)}/>
)}
<Avatar
reverse
rounded
icon={{ name:'comment-processing-outline', type:'material-community' }}
overlayContainerStyle={{backgroundColor: '#dbdbdb',marginLeft:5}}
size='small'/>
</View>
</View>
{ this.state.fontLoaded == true ? (
<View style={{flexDirection:'row'}}>
<Text style={{fontFamily: 'MontserratB', color:'#bf00b9', marginTop:10}} key={username}>{username}</Text>
<Text style={{fontFamily:'Montserrat', marginTop:10}} key={card.caption}>{card.caption[i]}</Text>
</View>
) : (<Text> Loading...</Text>)
}
<Text style={{marginTop:4, color:'#878787'}} key={card.coments}>{card.coments[i]}</Text>
</Card>
</TouchableHighlight>
</View>
);
return <React.Fragment />;
});
})
}
render(){
return (
<View>
{this.renderFeed()}
</View>
)}
The issue was in where I was declaring my key. I moved key={`${i}_${index}`} from the <Card> element up to the <View> element and now the warning is gone
It's always a good practice to assign each of the elements in the array with a unique key because Keys are necessary to improve performance of your React app. Please refer the below article to get the exact knowledge of why it's necessary.
Keys

FlatListFooter can't be displayed

I want to add footer to my flatList :
i try this code :
renderFooter = () => {
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<Button> This is footer </Button>
</View>
);
}
<FlatList
data={menuData}
renderItem={({item}) => <DrawerItem navigation={this.props.navigation} screenName={item.screenName} icon={item.icon} name={item.name} key={item.key} />}
ListFooterComponent ={this.renderFooter}
/>
But no footer appears when running.
Any help please
You used the component
ListFooterComponent
in right way. you need to check your render method for footer. I faced the same issue and i follow this example, and it helps me. I hope it will help you.
Simple way/hack. In the menuData array, you just add a flag (i called) to the child object to indicate that it's a last item. For eg:
If you can modify your menuData structure, added lastItem prop true to indicate that it's the last item :
const menuData = [
{name:'menu1', screenName:'screen1', icon:'../assets/icon1.png'},
{name:'menu2', screenName:'screen2', icon:'../assets/icon2.png', lastItem:true}
];
and then
renderFlatItems = ({item}) => {
const itemView = null;
if (!items.lastItem) {
itemView = <DrawerItem navigation={this.props.navigation} screenName={item.screenName} icon={item.icon} name={item.name} key={item.key} />
} else {
itemView = <View style={{padding:100}}><Button> This is footer </Button> </View>
}
return {itemView};
}
then use it in the Flatlist like so
<FlatList
data={menuData}
renderItem={this.renderFlatItems}
/>
If you want a footer that remains at the bottom of the screen "Above" the list, then you can just add a View after the FlatList.
<View>
<FlatList style={{flex: 1}} />
<View
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: 50,
}}
/>
</View>