React-Native How to update parent state variable from child component - react-native

I am presenting a component using Modal in react-native. So in the Child component(the component that is presented on top of parent as Modal) I am trying to update the state variable of parent, but it is throwing error like "'changeModalVisibility' is missing in props validation".
Kindly help me to solve this.
Related Code is below:
// Parent Class in render
<SafeAreaView style={styles.container}>
<Modal
animationType="slide"
transparent={false}
visible={this.state.isModalVisible}
onRequestClose={() => { this.changeModalVisibility(false) }}
>
<ChildClass
changeModalVisibility={this.changeModalVisibility}
/>
</Modal>
</SafeAreaView>
// Outside Render function
changeModalVisibility = (bool) => {
this.setState({ isModalVisible: visible });
}
// In Child Class
<TouchableHighlight
underlayColor="none"
onPress={this.props.closeButtonTapped}
style={{ alignItems: 'center', justifyContent: 'center', }}
>
<Text style={{
color: 'white',
marginTop: 10,
marginLeft: 20,
fontWeight: '600',
fontSize: 18,
}}
>Close
</Text>
</TouchableHighlight>
closeButtonTapped() {
this.props.changeModalVisibility //"**'changeModalVisibility' is missing in props validation**"
}

Your Child component in parent component has changeModalVisibility prop.
You should call
this.props.changeModalVisibility(true) or this.props.changeModalVisibility(false)
inside child component. Make sure to use arrow function when you want to execute function from prop :
changeModalVisibility={this.changeModalVisibility}
changeModalVisibility = (visible) => {
this.setState({ isModalVisible: visible });
}
In child component onPress props should be like this:
onPress={() => this.closeButtonTapped()}

Related

Handle on press in a react-native custom component

I'm using react-native. I need to show a menu that consists of a list of items. I simplified the code below and I'm showing only a text but in the real case, the item has got a checkbox an icon and a label so for this reason I created a custom component.
I need to handle the on click on the items.
The problem is that the on press on the TouchableOpacity in the widget is not triggered.
If I use the same hierarchy of components directly the on click works.
So in the example below the onClick on the TouchableOpacity that is directly put in the containing view is triggered but the onClick on the TouchableOpacity that is wrapped in the EventOnMapMenuWidget is not triggered.
How can I handle the onClick on the TouchableOpacity in the EventOnMapMenuWidget?
Please notice that in the log I don't see the
EventOnMapMenuWidget onPress
while I see the
TEST onPress
private drawEventsOnMapForm() {
return <View style={{ margin: marginStyle.medium, flexDirection: 'column' }}>
<EventOnMapMenuWidget
onToggle={() => {
console.log("EventOnMapMenuWidget onToggle")
}}
label={STRINGS.speeding}
/>
<TouchableOpacity style={{ margin: marginStyle.medium, flexDirection: 'row' }} onPress={() => {
console.log("TEST onPress")
}}>
<Text style={{ color: COLORS.black }}>{STRINGS.speeding}</Text>
</TouchableOpacity>
</View>
}
This is the definition of the custom components:
export default class EventOnMapMenuWidget extends Component<EventOnMapMenuWidgetProp> {
render() {
return <TouchableOpacity style={{ margin: marginStyle.medium, flexDirection: 'row' }} onPress={()=> {
console.log("EventOnMapMenuWidget onPress", this.props.label)
this.props.onToggle()
}}>
<Text style={{color: COLORS.black}}>{this.props.label}</Text>
</TouchableOpacity>
}
}

how to stop images from rendering on setState

I have images associated with a counter and based on this increment or decrement in counter, a calculation is done and displayed in a text at the bottom.
The problem is that when I render, the images get rendered again and are loaded again and again and again. which I dont want.
If I dont render, the text will not update with the calculated amount.
For the counter I am using react-native-counter.
I have already tried with shouldcomponentupdate, but I want to stop only image rendering, the rest should work.
Please advise.
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Header
backgroundColor="#25D366"
leftComponent={
<Icon
name="menu"
size={40}
color={"#fff000"}
onPress={() => this.props.navigation.openDrawer()}
/>
}
centerComponent={{
text: "Veg & Fruits",
style: {
color: "#ffffff",
fontSize: 25,
fontWeight: "bold",
},
}}
rightComponent={<Icon name="home" color={"#ff0000"} />}
></Header>
/// this is searchbar component,
<SearchBar
fontColor="#ffffff"
fontWeight="bold"
fontSize={20}
iconColor="#c6c6c6"
shadowColor="#ffffff"
cancelIconColor="#c6c6c6"
backgroundColor="#25D366"
placeholder="Search here"
onChangeText={(text) => {
this.setState({ photos: [] });
this.state.search = text;
this.filterList(this.state.search);
console.log("text changed");
}}
onPressCancel={(text) => {
text = "";
//this.filterList(text);
}}
onPress={(text) => {
console.log("rendering");
console.log("now text is: ", this.state.search);
}}
/>
/// in this view images are displayed using functions
<View>
<ScrollView
style={{
height: Dimensions.get("window").height - 200,
}}
>
<View
key={Date.now()}
style={{
flex: 1,
flexDirection: "column",
flexWrap: "wrap",
}}
>
{this.filterList(this.state.search)}
{this._renderImages()}
</View>
</ScrollView>
<CalcText tt={total_num} />
</View>
</div>
);
}
}
class CalcText extends Component {
constructor(props) {
super(props);
this.state = {
ta: 0,
};
}
shouldComponentUpdate(nextProps) {
console.log(nextProps.tt);
if (this.props.tt !== nextProps.tt) {
console.log("changed");
return true;
} else {
console.log("Not changed");
return false;
}
}
render() {
return (
<View style={{ height: 40, backgroundcolor: "ff0000" }}>
<Text style={{ fontSize: 26, fontWeight: "bold" }}>
Total : {this.props.tt}
</Text>
</View>
);
}
}
You can create a TextBox component and split it from ImageComponent. In this way the images will not be bound to the state of the component rendering text, and you can safely change TextBox state and text preventing ImageComponent to re-render.
Edit:
Okei, now i get it. I think you have no possibility to do it like this.
Let's formalize the problem:
<Parent>
<Images calc={functionToCalc} />
<CalcText totalAmount={this.state.totalAmount}/>
</Parent>
functionToCalc is defined in in <Parent> and update parent state, something like:
const funcToCalc = () => {
// when condition occurs
this.setState({
totalAmount : computedAmount
})
}
The state of <Parent> has:
this.state : {
totalAmount: none
}
Whenever condition (buttonClick?) occurs in <Images/> you run functionToCalc update <Parent> state and rerender <CalcText>. Problem here is that also <Images> will be rerender again as all the parent component will be rerender.
this is one of the way to pass info from siblings in React.
You only have a possibility if you want to prevent <Images> rerendering.
Redux or similar libraries for centralize state
You will move the computed calculation in a Store and <CalcText/> will read that from there. <Images/> component will trigger an action modifying that state but won't listen to that so not being rerender.

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.

React-Native onPress doesn't work, when touch the icon

I'am using react-native-element to create a button group, that embedded an Icon from react-native-vector-icons .
the problem is that when the icon is touched, onPress does not get triggered
constructor(props) {
super(props);
this.state = { selectedIndex: 0 };
this.SetSelected = this.SetSelected.bind(this);
}
SetSelected(index) {
this.setState({ selectedIndex: index });
}
return(
<ButtonGroup
selectedIndex={this.state.selectedIndex}
onPress={this.SetSelected}
selectedButtonStyle={{ backgroundColor: 'blue' }}
buttons={[
{
element: () => (
<Icon.Button
name="slack"
style={{ backgroundColor: 'white' }}
color={'black'}
size={30}
title="Inbox"
>
<Text style={{ color: 'black', fontSize: 15, textAlignVertical: 'center', textAlign: 'center' }}
>
All
</Text>
</Icon.Button>
),
})
Thanks to Kyle Roach,
the reason is
because I am using an Icon.Button which is touchable. So when I try to tap to change the ButtonGroup, the touch event will be caught by Icon.Button instead of the button for the buttonGroup.
I should use Icon instead of Icon.Button.
Try making it a function.
onPress={() => {this.SetSelected()}}
If it doesn't work please provide the this.SetSelected function.

How to add section divider in navigation drawer using react navigation

Suppose I have five items in drawer navigation. I want to add separator after three items. How to add this using react-navigation.
As mentioned vonovak, you could achieve this by using contentComponent which allows complete customization of drawer. For this you will need to create custom component which will override default drawer. Code sample:
Navigation Drawer
const NavigationDrawer = DrawerNavigator({
screen1: { screen: Screen1 },
screen2: { screen: Screen2 },
screen3: { screen: Screen3 },
}, {
contentComponent: SideMenu
})
Custom component which overrides default drawer (DrawerContainer)
class SideMenu extends React.Component {
render() {
return (
<View style={styles.container}>
<ScrollView>
<Text
onPress={() => this.props.navigation.navigate('Screen1')}
style={styles.item}>
Page1
</Text>
// 'separator' line
<View
style={{
borderBottomColor: 'black',
borderBottomWidth: 1
}}/>
<Text
onPress={() => this.props.navigation.navigate('Screen2')}
style={styles.item}>
Page2
</Text>
<Text
onPress={() => this.props.navigation.navigate('Screen3')}
style={styles.item}>
Page3
</Text>
</ScrollView>
</View>
);
}
}
You'll need to use the contentComponent prop to make custom changes. Check out the docs
If you are using DrawerItem component you can use itemStyle prop to add style as follows
const props = {
itemStyle: {
borderBottomWidth: 1,
borderColor: '#E2E4E8',
}
}
<DrawerItems {...props} />
You can also modify the style of container containing all items with
itemsContainerStyle prop.
Check official docs here.
3.x: https://reactnavigation.org/docs/3.x/drawer-navigator#contentoptions-for-draweritems
4.x: https://reactnavigation.org/docs/4.x/drawer-navigator#contentoptions-for-draweritems
5.x: DrawerContent is default drawer Item from v.5
You can pass props to it using drawerContentOptions object in Drawer.Navigator component.
https://reactnavigation.org/docs/5.x/drawer-navigator#drawercontentoptions
You can also pass custom drawer component using drawerContent prop.
https://reactnavigation.org/docs/5.x/drawer-navigator#drawercontent
6.x: Custom component can be added using drawerContent prop
https://reactnavigation.org/docs/drawer-navigator#drawercontent
Style to drawerItem is changed to
https://reactnavigation.org/docs/drawer-navigator#draweritemstyle
Just add this code:
const Seperator = () => <View style={styles.separator} />;
at the top of your code that you want to have the section divider/separator on. If it's a navigation drawer, menu, categories or etc.
Add this styling prop:
separator: {
marginVertical: 8,
borderBottomColor: "#737373",
borderBottomWidth: StyleSheet.hairlineWidth
}
Add section divider/separator between each section, menu, category block of code that you want to separate them like this:
//Block of code of first section/menu/category starts from here
<Icon.Button
name="th-large"
raised={true}
backgroundColor="#ffa500"
size={30}
onPress={() => {
Linking.openURL("https://www.buymeacoffee.com/splendor");
}}
>
<Text style={{ fontSize: 15 }}>
Herat: The "Academy" of Prince Bay Sunghur (1420-1433)
</Text>
</Icon.Button>
**<Seperator />**
//Block of code of first section/menu/category ends here
//Block of code of second section/menu/category starts from here
<Icon.Button
name="th-large"
raised={true}
backgroundColor="#ffa500"
size={30}
onPress={() => {
Linking.openURL("https://www.buymeacoffee.com/splendor");
}}
>
<Text style={{ fontSize: 15 }}>
Venice, Istanbul, and Herat (15th Century)
</Text>
</Icon.Button>
**<Seperator />**
//Block of code of second section/menu/category ends here
//Block of code of third section/menu/category starts from here
<Icon.Button
name="th-large"
raised={true}
backgroundColor="#ffa500"
size={30}
onPress={() => {
Linking.openURL("https://www.buymeacoffee.com/splendor");
}}
>
<Text style={{ fontSize: 15 }}>
The Age of Bihzad of Herat (1465-1535)
</Text>
</Icon.Button>
**<Seperator />**
//Block of code of thirds section/menu/category ends here