How to close a modal in react native? - reactjs

I am unable to close a modal. I am displaying few images inside it, and onPress of the "X(close)" icon, want to close the modal. I have tried setting the state of modalvisible to false, by default which is set to true. But on press of icon the modal doesn't gets closed. Any solution would be of great help.
export default class imagenav extends Component{
constructor(props){
super(props)
state = {
modalVisible: false,
}
}
openmodal(){
this.setState(modalVisible: true)
}
render() {
return (
<Container>
<Modal onRequestClose={() => {}}>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{source: {uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/1200px-Google_Images_2015_logo.svg.png",
dimensions: {width: 1080, height: 1920}}
},
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false,
})
console.log("getting closed");
}}
/>
</Right>
</Header>
</Modal>
</Container>
);
}
}

You could use an inline if to only render your modal is your state allows it :
{this.state.modalVisible &&
<Modal onRequestClose={() => { }}>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{
source: {
uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/1200px-Google_Images_2015_logo.svg.png",
dimensions: { width: 1080, height: 1920 }
}
},
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false,
})
console.log("getting closed");
}}
/>
</Right>
</Header>
</Modal>
}

You have a state, but you are not using it
<Modal onRequestClose={()=> this.openmodal(false)} visible={this.state.modalVisible}> should be good to go.
oh and your openmodal function can be used for opening and closing the modal
openmodal(value){
this.setState({modalVisible: value})
}
<Icon
name='close'
color='white'
onPress={() => {
this.openmodal(false)
console.log("getting closed");
}}
/>

It is enough to put a state for it in visible as bellow:
<Modal onRequestClose={()=> null} visible={this.state.active} transparent={true}>
/////your Views and things to show in modal
</Modal>
in your state you have to make it as blew:
constructor(props) {
super();
this.state = {
active:false,
}
}
And then you have to toggle it in an onPress for example:
onPress=()={
this.setState({active:true})
}
So totally in your project you will have:
export default class imagenav extends Component{
constructor(props){
super(props)
state = {
modalVisible: false,
}
}
openmodal(){
this.setState({modalVisible: true})
}
render() {
return (
<Container>
<Modal visible={this.state.modalVisible} onRequestClose={() => {}}>
<View style={{flex:1}}>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{source: {uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/7/77/Google_Images_2015_logo.svg/1200px-Google_Images_2015_logo.svg.png",
dimensions: {width: 1080, height: 1920}}
},
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false,
})
console.log("getting closed");
}}
/>
</Right>
</Header>
</View>
</Modal>
</Container>
);
}
}
Update:
According to your last request there is a way. You can pass flag to your next screen and in the componentDidMount() of next screen you can check it. if it is true you can show the modal otherwise ignore it.
I hope I could help. :)

Related

React Native Update Parent Array from Child Component

I am having trouble updating an array that is passed as a prop into my child component. I have searched around but haven't found an answer that can directly solve my problem. My code is as follows:
App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import AddMedication from "./src/AddMedication";
import MedicationList from './src/MedicationList';
const Stack = createNativeStackNavigator();
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
medications: [],
}
this.addMedication = this.addMedication.bind(this);
}
addMedication = (name, dosage, measurement, timesDaily) => {
console.log("Medication added.")
var newItem = {name: name, dosage: dosage, measurement: measurement, timesDaily: timesDaily}
this.setState({
medications: [...this.state.medications, newItem]
})
}
render() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Medication List">
{(props) => <MedicationList {...props} medications={this.state.medications} />}
</Stack.Screen>
<Stack.Screen name="Add New Medication">
{(props) => <AddMedication {...props} addMedication={this.addMedication} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
}
This is the home screen where I am trying to display the array but nothing shows up
MedicationList.js
class MedicationList extends Component {
constructor(props) {
super(props);
this.state = {
tableHead: ['Name', 'Dosage', 'Times Daily', 'Prescriber', 'For Diagnosis'],
}
}
medication = ({ item }) => {
<View style={{ flexDirection: 'row' }}>
<View style={{ width: 50, backgroundColor: 'lightyellow'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold', textAlign: 'center'}}>{item.name}</Text>
</View>
<View style={{ width: 400, backgroundColor: 'lightpink'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold' , textAlign: 'center'}}>{item.dosage}{item.selectedMeasurement}</Text>
</View>
<View style={{ width: 400, backgroundColor: 'lavender'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold' , textAlign: 'center'}}>{item.timesDaiy}</Text>
</View>
</View>
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: '10%'}}>
<Button
title="+ Add New Medication"
onPress={() => {
/* 1. Navigate to the Details route with params */
this.props.navigation.navigate('Add New Medication', {
medications: this.props.medications,
});
}}
/>
<FlatList
data={this.props.medications}
renderItem={this.medication}
/>
</View>
);
}
}
This is where I click the add button to update the medications array
AddMedication.js
class AddMedication extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
dosage: 0,
selectedMeasurement: "mg",
timesDaily: '',
prescriber: '',
forDiagnoses: '',
instructions: '',
validity: false,
};
}
setName = (name) => {
let isValid = this.isFormValid();
this.setState({ name: name, validity: isValid });
}
setDosage = (dosage) => {
let isValid = this.isFormValid();
this.setState({ dosage: dosage, validity: isValid });
}
setMeasurement = (measurement) => {
this.setState({ selectedMeasurement: measurement });
}
setTimesDaily = (timesDaily) => {
let isValid = this.isFormValid();
this.setState({ timesDaily: timesDaily, validity: isValid });
}
setPrescriber = (prescriber) => {
this.setState({ prescriber: prescriber });
}
setDiagnoses = (diagnoses) => {
this.setState({ forDiagnoses: diagnoses });
}
setInstructions = (instructions) => {
this.setState({ instructions: instructions });
}
isFormValid = () => {
return (this.state.name !== '' && (this.state.dosage !== '' && this.state.dosage > 0)
&& (this.state.timesDaily !== '' && this.state.timesDaily > 0));
}
render() {
return (
<View style={styles.container}>
<Text style={{color: 'red', marginBottom: 5, marginLeft: -125}}>* denotes required field</Text>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(name) => this.setName(name)}
placeholder="Medication Name"
value={this.state.name}
/>
</View>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(dosage) => this.setDosage(dosage)}
placeholder="Dosage"
value={this.state.dosage}
/>
</View>
<View style={styles.dosageContainer}>
<Text style={{flex: 1, marginTop: 100, marginLeft: 30}}>
Select Measurement:
</Text>
<Picker
style={styles.picker}
selectedValue={this.state.selectedMeasurement}
onValueChange={(itemValue, itemIndex) =>
this.setMeasurement(itemValue)
}>
<Picker.Item label="mg" value="mg" />
<Picker.Item label="g" value="g" />
<Picker.Item label="ml" value="ml" />
</Picker>
</View>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(timesDaily) => this.setTimesDaily(timesDaily)}
placeholder="Times daily"
value={this.state.timesDaily}
/>
</View>
<TextInput
style={styles.inputText}
onChangeText={(prescriber) => this.setPrescriber(prescriber)}
placeholder="Prescriber"
value={this.state.prescriber}
/>
<TextInput
style={styles.inputText}
onChangeText={(diagnoses) => this.setDiagnoses(diagnoses)}
placeholder="For diagnoses"
value={this.state.forDiagnoses}
/>
<TextInput
style={styles.inputText}
onChangeText={(instructions) => this.setInstructions(instructions)}
placeholder="Instructions"
value={this.state.instructions}
/>
<TouchableOpacity
style={this.isFormValid() ? styles.validButton : styles.invalidButton}
disabled={!Boolean(this.state.name && this.state.dosage && this.state.timesDaily)}
onPress={() => {
this.props.navigation.goBack()
this.props.addMedication(this.state.name, this.state.dosage,
this.state.selectedMeasurement, this.state.timesDaily)
}}
>
<Text style={{color: 'white'}}>Add Medication</Text>
</TouchableOpacity>
</View>
)
}
}
You can pass the state value but I think you cannot pass the addMedication method just like this.
Could you please try passing an arrow function that uses the setState method?
For example:
<Stack.Screen name="Add New Medication">
{(props) => <AddMedication {...props} addMedication={(name, dosage, measurement, timesDaily)=> {this.addMedication(name, dosage, measurement, timesDaily)}} />}
</Stack.Screen>

Adding item to Flatlist by getParam

DIET (MAIN PAGE)
export class Diet extends Component {
constructor(props) {
super(props);
this.state = {
foodList: [],
};
}
render() {
return (
<View style={{ flex: 1, top: hp("12%"), height: hp("100%") }}>
<Button onPress={()=> this.props.navigation.navigate('FoodCreate')}>
<Text>Press to insert Food Name</Text>
</Button>
<FlatList
data={{this.props.route?.params?.foodList}
keyExtractor={(item, index) => item.key.toString()}
renderItem={(data) => (
<ListItem itemDivider title={data.item.food} />
)}
/>
</View>
FOODCREATE
export class FoodCreate extends Component {
constructor(props) {
super(props);
this.state = {
food: null,
foodList: [],
};
}
submitFood = (food) => {
this.setState({
foodList: [
...this.state.foodList,
{
key: Math.random(),
name: food,
},
],
});
this.props.navigation.navigate("Diet", {
foodList: this.state.foodList,
});
};
render() {
return (
<Container>
<Header>
<Left>
<Button transparent>
<Icon
name="arrow-back"
onPress={() => this.props.navigation.goBack()}
style={{ fontSize: 25, color: "red" }}
/>
</Button>
</Left>
<Body>
<Title>Add Food</Title>
</Body>
<Right>
<Button transparent>
<Icon
name="checkmark"
style={{ fontSize: 25, color: "red" }}
onPress={() => {
this.submitFood(this.state.food);<-----------
}}
/>
</Button>
</Right>
</Header>
<View style={{ alignItems: "center", top: hp("3%") }}>
<TextInput
placeholder="Food Name"
placeholderTextColor="white"
style={styles.inptFood}
value={this.state.food}
onChangeText={(food) => this.setState({ food })}
/>
</View>
Hey everyone, so this is how this app should work: when I start Expo it brings me to the Diet screen, from there I press the Button to add a new food to the Flatlist, once I get sent to FoodCreate screen I type in the TextInput the name of the food and when I click the checkmark in the header it should send me back to Diet and display in the Flatlist the name of the food I typed, and so on. When I run the app it gives me the following error: this.props.navigation.getParam is not a function
You have to use
this.props.route.params.foodList
The parameters are passed via the route.params prop, with Navigation v5 the getParams option is not there

How can I display an array of images inside a Modal in react-native?

I am using galleryswiper library to display an array of images inside a modal but when i navigate to the modal component and onPress the modal opens up i don't see any images. Can anyone help me how to pass the image sources inside the modal? Also i am unable to close the modal on Press of the icon.
export default class imagenav extends Component{
constructor(props){
super(props)
state = {
modalVisible: true,
};
}
closeModal() {
this.setState({modalVisible: false});
}
render() {
return (
<Modal visible={this.modalVisible} onRequestClose={() => {} }>
<GallerySwiper
style={{ flex: 1, backgroundColor: "black" }}
images={[
{source: {uri:
dimensions: {width: 1080, height: 1920}}
}
]}
/>
<Header
style={{
backgroundColor: 'black',
borderBottomWidth: 0,
}}
>
<Right>
<Icon
name='close'
color='white'
onPress={() => {
this.setState({
modalVisible: false
})
//this.closeModal()
}}
/>
</Right>
</Header>
</Modal>
);
}
}

General solution for subscribing to listeners in react native

Is there a reusable way of subscribing to listener like keyboard events.
Actually I have a button with position absolute at the very bottom of my screen and when keyboard pops up it comes floating on top and that does not look very good.
So I am hiding that button when keyboard is visible but if you have similar scenario on multiple screens it becomes headache to add subscription on every screen currently I am doing it this way.
class Profile extends Component {
constructor(props) {
super(props);
this._keyboardDidShow = this._keyboardDidShow.bind(this);
this._keyboardDidHide = this._keyboardDidHide.bind(this);
}
componentDidMount() {
// subscribing to keyboard listeners on didMount
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
this._keyboardDidShow
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
this._keyboardDidHide
);
}
_keyboardDidShow() {
this.setState({
keyboardVisible: true,
});
}
_keyboardDidHide() {
this.setState({
keyboardVisible: false,
});
}
componentWillUnmount() {
// unsubscribing listeners on unMount
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
const AnimatedBottomButton = Animated.createAnimatedComponent(BottomButton);
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
{!this.state.keyboardVisible && (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
)}
</ScrollView>
);
}
}
I don't like the above solution since I have to add subscription related code to every Component I want to subscribe for keyboard events I am new to javascript and still learning it.
If any one out there can help me with some general solution of it would be very good.
Custom components come in handy in these situations. You can create a single component with desired behaviors implemented and then you can add that component to the screens you want to use.
Sample
export default class CustomButton extends Component {
state = {
visible: true
}
componentDidMount() {
// subscribing to keyboard listeners on didMount
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => this._toggleVisiblity(false)
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => this._toggleVisiblity(true)
);
}
_toggleVisiblity = (visible) => {
this.setState({ visible })
}
componentWillUnmount() {
// unsubscribing listeners on unMount
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
if (this.state.visible === false) return null
return (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
);
}
}
class Profile extends Component {
render() {
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
<CustomButton />
</ScrollView>
);
}
}
You can go a bit further if you like and create a HOC.
Sample
const withKeyboardEvents = WrappedComponent => {
return class extends Component {
state = {
visible: true,
};
componentDidMount() {
this.keyboardDidShowListener = Keyboard.addListener(
'keyboardDidShow',
() => this._toggleVisiblity(false)
);
this.keyboardDidHideListener = Keyboard.addListener(
'keyboardDidHide',
() => this._toggleVisiblity(true)
);
}
_toggleVisiblity = visible => {
this.setState({ visible });
};
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
render() {
return (
<React.Fragment>
{this.state.visible === true && (
<View
style={{
flex: 1,
justifyContent: 'flex-end',
}}>
<AnimatedBottomButton
title="Done"
onPress={() => Actions.pop()}
style={{
opacity: this.anim5,
transform: [{ scale: this.anim5 }],
marginBottom: Utils.isPhoneX() ? Metrics.doubleBaseMargin : 0,
}}
/>
</View>
)}
<WrappedComponent />
</React.Fragment>
);
}
};
};
class Profile extends Component {
render() {
return (
<ScrollView
style={styles.containerStyle}
bounces={false}
contentContainerStyle={{ flex: 1 }}
keyboardShouldPersistTaps="handled">
{this.renderUserImage()}
{this.renderUserDetail()}
</ScrollView>
);
}
}
export default withKeyboardEvents(Profile)

Image is not displaying after loading from database?

I'm quite new to react native. I have a user with a profile picture that I'm trying to display on screen, when printing to my console snapshot.val().imageURL the correct url is displayed using this:
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => profImage = snapshot.val().imageURL)
.catch(error => console.log(error))
} else {
console.log("No user")
}
I assign that url to profImage and try to set my image uri to that variable:
<Avatar
large
rounded
source={{ uri: profImage }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
However, my image container remains blank. Does this have to do that when the render function is run, the url hasn't been retrieved thus the component display is blank? Do I use this.setState to update the component? If so, what is the proper way to do so?
Here is the relevant part of my component:
class Profile extends React.Component {
static navigationOptions = {
title: "Profile",
headerStyle: {
backgroundColor: '#fff',
borderBottomWidth: 0,
},
headerTitleStyle: {
color: 'black'
},
}
constructor() {
super();
this.state = {
image: null,
uploading: null,
}
}
render() {
const { navigate } = this.props.navigation;
let { image } = this.state;
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => profImage = snapshot.val().imageURL)
.catch(error => console.log(error))
} else {
console.log("No user")
}
return (
<ScrollView>
<View style={styles.profileTopContainer}>
<Avatar
large
rounded
source={{ uri: img }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
<Text
style={styles.usernameText}
>
Jordan Lewallen
</Text>
<TouchableHighlight
onPress={this._pickImage}>
<Text
style={styles.userMinutes}
>
Choose a profile picture
</Text>
</TouchableHighlight>
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
{
(image)
? <Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
: null
}
</View>
}
please try this
<Avatar
large
rounded
source={{ uri: img }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
replace with
<Avatar
large
rounded
source={{ uri: profImage }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
This is because you call the async request inside your render method itself.
When your render method execute you request for imageUrl which is a async call and takes time to resolve and update the value of profImage variable. At that time your render method is finished its execution and set the placeholder.
If you need this to be done you should keep this profImage in your component state. So once the state is updated your render method will be called again and update the UI with new image.
Try this!
class Profile extends React.Component {
static navigationOptions = {
title: "Profile",
headerStyle: {
backgroundColor: '#fff',
borderBottomWidth: 0,
},
headerTitleStyle: {
color: 'black'
},
}
constructor() {
super();
this.state = {
image: null,
uploading: null,
}
}
componentWillRecieveProps(nextProps){
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => this.setState({ image: snapshot.val().imageURL }))
.catch(error => console.log(error))
} else {
this.setState({ image: 'https://www.placeholdit.com' });
console.log("No user")
}
}
render() {
const { navigate } = this.props.navigation;
let { image } = this.state;
return (
<ScrollView>
<View style={styles.profileTopContainer}>
<Avatar
large
rounded
source={{ uri: image }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
<Text
style={styles.usernameText}
>
Jordan Lewallen
</Text>
<TouchableHighlight
onPress={this._pickImage}>
<Text
style={styles.userMinutes}
>
Choose a profile picture
</Text>
</TouchableHighlight>
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
{
(image)
? <Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
: null
}
</View>
}

Resources