Changing a parent state when a child component updates - react-native

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.

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}
/>
)

Last Image is being cropped, when using ScrollView in react-native

I am trying to build something like instagram posts, that is continuous images that can be scrolled. But the last image is being cropped, that is only the upper half of it is being visible, there are several posts, regarding the same, but those didnt help, (contentContainerStyle={{flexGrow: 1,}}, adding height to a invisible view). Can someone please point out what is going wrong?
EDIT: I have changed scrollview to flatlist and still face the same problem, can you suggest what else to do?
EDIT 2: realised that the <Header /> and <Stories /> above the flatlist are not letting it scroll completely, that is the height that
it is not scrolling is proportional to height of <Header /> and <Stories />
post.js
const Post = ({post}) => {
return (
<View style={{flex:1}}>
<Divider width = {0.5}/>
<PostHeader post={post}/>
<PostImage post={post} />
<PostFooter post={post}/>
</View>
)
}
const PostImage = ({post}) => {
return (
<View style={styles.postContainer}>
<Image style={styles.image} source={{uri: post.post_url}}></Image>
</View>
)
}
const styles = StyleSheet.create({
container: {
},
dp: {
width: 35,
height: 35,
margin:5,
borderRadius: 20,
borderWidth : 1,
borderColor : '#ff8501'
},
postContainer: {
width: '100%',
height: 400,
},
image: {
height: '100%',
resizeMode: 'cover',
}
})
homescreen.js
const HomeScreen = () => {
return (
<SafeAreaView >
<Header />
<Stories />
{/* <ScrollView>
{
POSTS.map((post, index) => {
return (
<Post key={index} post={post} />
)
})
}
</ScrollView> */}
<FlatList data={POSTS} renderItem={({item}) => <Post post={item} />} />
</SafeAreaView>
)
}
If you want to render repetitive view so why you are not using Faltlist instead of Scrollview. For repetitive view react native provide one component which is called Flatlist and pass you array data in render item it will give you better performance as well.
<SafeAreaView style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
const renderItem = ({ item }) => (
<Divider width = {0.5}/>
<PostHeader post={item}/>
<PostImage post={item} />
</View>
);
const styles = StyleSheet.create({
container: {
flex: 1,
},
item: {
backgroundColor: '#f9c2ff',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
},
});
According to React Native docs FlatList is the Component you should use:
ScrollView renders all its react child components at once, but this has a performance downside.
Imagine you have a very long list of items you want to display, maybe several screens worth of content. Creating JS components and native views for everything all at once, much of which may not even be shown, will contribute to slow rendering and increased memory usage.
This is where FlatList comes into play. FlatList renders items lazily, when they are about to appear, and removes items that scroll way off screen to save memory and processing time.
FlatList is also handy if you want to render separators between your items, multiple columns, infinite scroll loading, or any number of other features it supports out of the box.
const Post = () => {
renderItemHandler = ({post, index}) => (
<View key={index} >
<Divider width={0.5}/>
<PostHeader post={post}/>
<PostImage post={post} />
</View>
)
return (
<SafeAreaView style={{flex: 1}}>
<View style={{height: "90%"}}>
<Flatlist
data={POSTS}
renderItem={renderItemHandler}
keyExtractor={item => item.id}
/>
</View>
</SafeAreaView>
)
}

How to close swipe item in 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}
/>
)
}
}

React Native - place two items side by side

I'm trying to place an item on the right side of another, but instead it always stays on the bottom of the first item, like this:
So, right now I'm just trying to place the "TEST" text right next to that side menu.
Here's my code:
<View style = {{flex: 1, width: Dimensions.get('window').width,
height: Dimensions.get('window').height}}>
<Workspace
img={require('./images/quarto.png')}/>
<ScrollView>
<Header>
<HeaderItem img={require('./images/camera.png')}/>
<HeaderItem img={require('./images/camera.png')}/>
<HeaderItem img={require('./images/camera.png')}/>
<HeaderItem img={require('./images/camera.png')}/>
</Header>
</ScrollView>
<ScrollView style = {{flexDirection: 'row'}}>
{this.sideMenuShow()}
<ScrollView style = {{alignSelf: 'flex-end'}}>
<Text style = {{color: 'white'}}>TEST</Text>
</ScrollView>
</ScrollView>
<Footer>
<View style = {styles.logoContainerStyle}>
<Image
style = {styles.logoStyle}
source = {require('./images/magicalStage.png')}
/>
</View>
<View
style = {{width: '70%', flexDirection: 'row', justifyContent:'flex-end', alignItems: 'flex-end'}}>
<TouchableOpacity>
<Text style = {{color: 'white'}}>Workspace </Text>
</TouchableOpacity>
<TouchableOpacity>
<Text style = {{color: 'white', textAlign: 'right'}}>Catalogue</Text>
</TouchableOpacity>
</View>
</Footer>
</View>
If it helps, the side menu code is:
sideMenuShow() {
const furnitureList = <FurnitureList />;
if(!this.state.hideMenu) {
return(
<SideMenu>
<MenuButton
source = {require('./images/left-material.png')}
onPress = {() => this.setState({hideMenu: true})}/>
<Text style = {{color: 'white', fontSize: 16, fontWeight: 'bold'}}>Furniture</Text>
<SideMenuItem
onPress = {() =>
!this.state.hideList ?
this.setState({hideList: true, hideListSofa: false, hideListBoxes: false})
: this.setState({hideList: false})
}
text='Test'
>
</SideMenuItem>
{
this.state.hideList ?
<FurnitureList
source = {require('./images/image.png')}/>
: null
}
</SideMenu>
);
}
else {
return(
<SmallSideMenu>
<MenuButton
source = {require('./images/right-material.png')}
onPress = {() => this.setState({hideMenu: false})}/>
</SmallSideMenu>
);
}
}
I thought I'd get what I want by setting the flexDirection of the ScrollView wraping both items to 'row', but it's not working as I thought it would. Any help would be great!
You placed your <Text style = {{color: 'white'}}>TEST</Text> Component inside a ScrollView.
Try to add contentContainerStyle={{ flexDirection: 'row'}} to the parent ScrollView or just remove the parent ScrollView. Hope it helps.
{ flexDirection: 'row' } as mentioned in above answer works.
You need to apply this to parent.
For example:
let's say your HTML equivalent looks like this:
<div class='parent'>
<div class='child1'></div>
<div class='child2'></div>
</div>
then you've to apply flexDirection: 'row' to 'parent' NOT 'child'
This will make child1 and child2 render horizontally next to each other in same line.

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>