Dismiss modal when navigating to another screen - react-native

I have an App with Home Screen, in this screen I'm rendering a Modal which opens on button press, inside the Modal I have a button that is supposed to navigate me to another screen, it's navigating correctly but when I navigate to another screen the modal doesn't disappear, how can i hide it?
Adding the code to demonstrate
Home:
import React, { Component } from 'react';
import Modal from './Modal';
class Home extends Component {
state = {
isModalVisible: false
};
toggleModal = () =>
this.setState({ isModalVisible: !this.state.isModalVisible });
render() {
const { navigate } = this.props.navigation;
<Modal
visible={this.state.isModalVisible}
navigation={this.props.navigation}
/>
);
}
}
export default Home;
Modal:
import React, { Component } from "react";
import Modal from "react-native-modal";
class Modal extends Component {
render() {
const { navigate } = this.props.navigation;
return (
<Modal
isVisible={this.props.visible}>
<Button onPress={() => {navigate('Main')}}>
>Button</Text>
</Button>
</Modal>
);
}
}
export default Modal;

Ideally you should wait for the setState to finish inside the callback and then navigate to the screen, since the methods are async and may disrupt the state if navigate is called before setState has finished completing.
Also parent should control the state of the child.
Home
onNavigate = () => {
this.setState({isModalVisible: false}, () => this.props.navigation.navigate('Main')
}
<Modal
visible={this.state.isModalVisible}
onNavigate={this.onNavigate}
/>
Modal
<Modal
isVisible={this.props.visible}>
<Button onPress={this.props.onNavigate}>
<Text>Button</Text>
</Button>
</Modal>

You should provide a reference to the variable that defines the visibility state of the modal component. You'll need to define a function hides the modal and pass the function reference to the modal component and execute it on press of the button along side with the navigation action.
Something on the lines of -
Your home screen should have a function like -
onModalClose = () => {this.setState({isModalVisible: false})}
then pass this as reference to the modal component like -
<Modal
visible={this.state.isModalVisible}
navigation={this.props.navigation}
onModalClose={this.onModalClose}
/>
and call it on the onPress() method of the <Button/> component like-
<Button onPress={() => {this.props.onModalClose(); navigate('Main')}}>
EDIT
Just noticed, since you already have a function that toggles the visibility of your modal, you need not define a new function. You can pass that function reference to the modal component itself.
<Modal
visible={this.state.isModalVisible}
navigation={this.props.navigation}
onModalClose={this.toggleModal}
/>

I took Pritish Vaidya answer and made it usable for any screen.
Home
import React, { Component } from 'react';
import Modal from './Modal';
class Home extends Component {
state = {
isModalVisible: false
};
toggleModal(screenName) {
this.setState({isModalVisible: !this.state.isModalVisible });
if (screenName && screenName != '') {
this.props.navigation.navigate(screenName);
}
}
render() {
<Modal
visible={this.state.isModalVisible}
onDismiss={(screenName) => { this.toggleModal(screenName); }}
/>
);
}
}
export default Home;
Modal:
class Modal extends Component {
dismissScreen(screenName) {
const dismissAction = this.props.onDismiss;
dismissAction(screenName);
}
render() {
return(
<View style={{ flex: 1, padding: 20 }}>
<Button
title="Dismiss Modal"
onPress={() => {this.dismissScreen();}}
/>
<Button
title="Navigate to Other Screen"
onPress={() => {this.dismissScreen('ScreenName');}}
/>
</View>
);
}
}

Related

check if current is the main page react-native-webview

I have a WebView project of an PHP admin panel, that I put a custom Back Button on top of its WebView. I want this button to be visible only if the current screen can go back, which is any screen beside main screen, and in this case our main screen is a admin panel login page. So I came up with the code I shared below.
It works fine, unless you login with wrong credentials. The Back Button immediately shows up when the login button pressed inside the WebView because it gets to another url from admin.panel/login to admin.panel/login/login, but it shouldn't render unless we successfully login to the panel. How can I achieve that?
BackButton.js
import { StyleSheet, TouchableOpacity } from "react-native";
import { Ionicons } from "#expo/vector-icons";
const BackButton = (props) => {
return (
<TouchableOpacity style={styles.button} onPress={props.onPress}>
<Ionicons name="arrow-back" size={24} color="black" />
</TouchableOpacity>
);
};
App.js
import React, { useEffect, useRef } from "react";
import { StyleSheet, Platform, BackHandler, View } from "react-native";
import { WebView } from "react-native-webview";
import MyStatusBar from "./components/MyStatusBar";
import MyBackButton from "./components/BackButton";
export default function App() {
const webViewRef = useRef();
// to handle goBack function for both button and hardwareBackPress
const onBackPress = () => {
if (webViewRef.current) {
webViewRef.current.goBack();
return true;
}
return false;
};
// to check if current can go back
const [canGoBack, setCanGoBack] = React.useState(false);
// WebView does not support swipe back for some android devices
// thus we have to handle the goBack function with hardwareBackPress
useEffect(() => {
...
}
}, []);
return (
<View style={styles.container}>
<MyStatusBar />
<WebView
allowsBackForwardNavigationGestures
allowsInlineMediaPlayback
ref={webViewRef}
source={{ uri: "https://admin.panel/login" }}
style={styles.webView}
onNavigationStateChange={(navState) => setCanGoBack(navState.canGoBack)}
/>
{canGoBack? (
<View style={styles.button}>
<MyBackButton onPress={onBackPress} />
</View>
) : null}
</View>
);
}

How to access navigation outside main component?

I've been working with the default tabs project created with create-react-native-app.
So I created my own screen where this.props.navigation is accessible in the main (export default class) component. It works fine to do navigate('Search') from the button titled 'nav default'.
However, after many tries, I couldn't navigate from either the button in the headerRight or in my custom component MyComponent.
How do I change my alerts to instead do navigate('Search') like the main component?
import React from 'react';
import { Button, Dimensions, ScrollView, StyleSheet, Text, View } from 'react-native';
class MyComponent extends React.Component {
// i wish i could navigate from here
render() {
//const { navigate } = this.props.navigation; // this causes TypeError undefined is not an object (evaluating this.props.navigation.navigate)
return (
<View>
<Button title="nav from component" onPress={() => alert('This should navigate, not alert')} color="red" />
</View>
);
}
}
export default class MyScreen extends React.Component {
// i wish i could navigate from here too
static navigationOptions = {
title: 'Test',
headerRight: (
//<Button onPress={() =>navigate('Search')} /> // how do I achieve this?
<Button title="nav from header" onPress={() => alert('This should navigate, not alert')} />
),
};
render() {
const { navigate } = this.props.navigation;
return (
<ScrollView style={styles.container}>
<Button onPress={() =>navigate('Search')} title="nav default" />
<MyComponent />
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 15,
backgroundColor: '#fff',
},
});
There are two different problems. First, navigation is only passed as a prop to the components that are navigation screens. So, to access it from other components, such as your MyComponent, you have to pass it through props <MyComponent navigation={navigation} /> and then you can use this.props.navigation.navigate inside it. You can also use the withNavigation higher order component, but in that case the first approach is better.
The other problem is, if you want to access the navigation prop in navigationOptions, you should define it as a function, and not as an object. The implementation would be something like this:
static navigationOptions = ({ navigation }) => ({
title: 'Test',
headerRight: (
<Button onPress={() =>navigation.navigate('Search')} />
),
});

react-navigation - navigating from child component

I have a leaderboard which calls a component and passes it data to it like so:
_renderItem =({item}) => (
<childComponent
key={item._id}
id={item._id}
name={item.name}
/>
);
And inside the childComponent I try do this:
<TouchableOpacity onPress={() => this.props.navigation.navigate("Profile", { id: this.props.id})} >
<View>
<Right>
{arrowIcon}
</Right>
</View>
</TouchableOpacity>
Where I am hoping that it will then go to the profile page and grab the correct data based on the id passed to it. The issue is that when I click the arrow to go to the profile page I get the error Cannot read property 'navigate of undefined. I have put both the leaderboard and childComponent in my HomeDrawerrRoutes.js and MainStackRouter.js. Any help would be great, thanks.
There is an easy Solution for this,
use withNavigation . it's a higher order component which passes the navigation prop into a wrapped Component.
example child component
import React from 'react';
import { Button } from 'react-native';
import { withNavigation } from 'react-navigation';
class ChildComponent extends React.Component {
render() {
<View
onPress = {()=> this.props.navigation.navigate('NewComponent')}>
... logic
</View>
}
}
// withNavigation returns a component that wraps ChildComponent and passes in the
// navigation prop
export default withNavigation(ChildComponent);
for more details : https://reactnavigation.org/docs/en/connecting-navigation-prop.html
This is a 3 page example that shows how to pass the navigate function to a child component and how to customize props send to screens from within the StackNavigator
// subcomponent ... receives navigate from parent
const Child = (props) => {
return (
<TouchableOpacity
onPress={() => props.navigate(props.destination) }>
<Text>{props.text}>>></Text>
</TouchableOpacity>
);
}
// receives navigation from StackNavigator
const PageOne = (props) => {
return (
<View>
<Text>Page One</Text>
<Child
navigate={props.navigation.navigate}
destination="pagetwo" text="To page 2"/>
</View>
)
}
// receives custom props AND navigate inside StackNavigator
const PageTwo = (props) => (
<View>
<Text>{props.text}</Text>
<Child
navigate={props.navigation.navigate}
destination="pagethree" text="To page 3"/>
</View>
);
// receives ONLY custom props (no nav sent) inside StackNAvigator
const PageThree = (props) => <View><Text>{props.text}</Text></View>
export default App = StackNavigator({
pageone: {
screen: PageOne, navigationOptions: { title: "One" } },
pagetwo: {
screen: (navigation) => <PageTwo {...navigation} text="Page Deux" />,
navigationOptions: { title: "Two" }
},
pagethree: {
screen: () => <PageThree text="Page III" />,
navigationOptions: { title: "Three" }
},
});
The useNavigation hook was introduced in v5:
import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '#react-navigation/native';
export function ChildComponent() => {
const navigation = useNavigation();
return (
<Button
title="Back"
onPress={() => {
navigation.goBack();
}}
/>
);
}
Docs: https://reactnavigation.org/docs/use-navigation
For some reason if you don't want to use withNavigation, the following solution works too. You just have to pass navigation as a prop to your child component.
For example:
export default class ParentComponent extends React.Component {
render() {
return (
<View>
<ChildComponent navigation={this.props.navigation} />
</View>
);
}
}
And in child component:
const ChildComponent = (props) => {
return (
<View>
<TouchableOpacity
onPress={() => props.navigation.navigate('Wherever you want to navigate')}
/>
</View>
);
};
export default ChildComponent;

Native Base Footer Nav Tabs

I am new to React Native and have created a footer with three button tabs. I am now wondering how to render different screens by clicking the buttons. My code:
export default class Uptown extends Component {
render() {
return (
<Container>
<Header>
<Title>Uptown</Title>
</Header>
<Content>
<App />
</Content>
<Footer>
<FooterTab>
<Button>
Contact
</Button>
<Button>
Here
</Button>
<Button>
Me
</Button>
</FooterTab>
</Footer>
</Container>
);
}
}
How would I go about changing screens when the buttons are pressed?
You can add conditional rendering instead of static <App/> tag. You can use code as following to render conditionally based on selected footer. (I used state variable to store selected page index. When index changed, render function automatically called by engine)
import First from './Home' // your first screen
import Next from './Next' // your second screen
class Updown extends Component {
constructor(props) {
super(props)
this.state = {index: 0} // default screen index
}
switchScreen(index) {
this.setState({index: index})
}
render() {
let AppComponent = null;
if (this.state.index == 0) {
AppComponent = First
} else {
AppComponent = Second
}
return (
<Container>
<Header><Title> Header... </Title></Header>
<Content> <AppComponent/> </Content>
<Footer>
<Button onPress={() => this.switchScreen(0) }> First </Button>
<Button onPress={() => this.switchScreen(1) }> Second </Button>
</Footer>
</Container>
)
}
}
I think as of native-base v2.3.1 (August 2017) the recommended way is to follow documentation on React Navigation / TabNavigator. Of course this is only if you actually intend to use TabNavigator (which I do) with the benefits of React Navigation (I'm not knowledgeable enough to say what those are).
But if you want the footer tabs to be part of the main navigation structure of your app, instead of an ad-hoc mechanism for one page, this seems to be the way to go.
import React, { Component } from "react";
import LucyChat from "./LucyChat.js";
import JadeChat from "./JadeChat.js";
import NineChat from "./NineChat.js";
import { TabNavigator } from "react-navigation";
import { Button, Text, Icon, Footer, FooterTab } from "native-base";
export default (MainScreenNavigator = TabNavigator(
{
LucyChat: { screen: LucyChat },
JadeChat: { screen: JadeChat },
NineChat: { screen: NineChat }
},
{
tabBarPosition: "bottom",
tabBarComponent: props => {
return (
<Footer>
<FooterTab>
<Button
vertical
active={props.navigationState.index === 0}
onPress={() => props.navigation.navigate("LucyChat")}>
<Icon name="bowtie" />
<Text>Lucy</Text>
</Button>
<Button
vertical
active={props.navigationState.index === 1}
onPress={() => props.navigation.navigate("JadeChat")}>
<Icon name="briefcase" />
<Text>Nine</Text>
</Button>
<Button
vertical
active={props.navigationState.index === 2}
onPress={() => props.navigation.navigate("NineChat")}>
<Icon name="headset" />
<Text>Jade</Text>
</Button>
</FooterTab>
</Footer>
);
}
}
));
You need to create a current index and bind the function to switch from a tab to another.
To do it use this :
import First from './First'
import Second from './Second'
class Updown extends Component {
constructor(props) {
super(props)
this.setState({currentIndex: 0}) // default screen index
}
switchScreen(index) {
this.setState({currentIndex: index})
}
render() {
let AppComponent = null;
//Here you can add as many tabs you need
if (this.state.currentIndex == 0) {
AppComponent = First
} else {
AppComponent = Second
}
return (
<Container>
<Header><Title> Header... </Title></Header>
<Content>
{AppComponent} // Load the component like this
</Content>
<Footer>
//HERE don't forget the bind function and pass the appropriate number
<Button onPress={() => this.switchScreen.bind(this,0) }> First </Button>
<Button onPress={() => this.switchScreen.bind(this,1) }> Second </Button>
</Footer>
</Container>
)
}
}
Hope it helps :)
I think that we can check some part of the #Hossein Mobasher code
import First from './Home' // your first screen
import Next from './Next' // your second screen
class Updown extends Component {
constructor(props) {
super(props)
this.state = {index: 0} // default screen index
}
switchScreen(index) {
this.setState({index: index})
}
render() {
let AppComponent = null;
if (this.state.index == 0) {
AppComponent = First
} else {
AppComponent = Second
}
return (
<Container>
<Header><Title> Header... </Title></Header>
<Content>
<AppComponent/> // you can write like this
</Content>
<Footer>
<Button onPress={() => this.switchScreen(0) }> First </Button>
<Button onPress={() => this.switchScreen(1) }> Second </Button>
</Footer>
</Container>
)
}
}

undefined is not an object(evaluating this.props.navigator.push)

I have two page i.e sign in page and a payment page.
I am trying to navigate to payment page on tap on SignIn button, but i am getting error undefined is not an object(evaluating this.props.navigator.push)
The code is as below:
import React, {
StyleSheet,
Text,
View,
TextInput,
Component,
Alert,
Navigator
} from 'react-native';
var Button = require('react-native-button');
import Payments from '../payments'
class Signin extends Component{
onSubmitPressed(){
this.props.navigator.push({
title: 'secure Page',
component: <Payments/>
});
};
render() {
return(
<View style={styles.container}>
<View style={styles.Inputview}>
<TextInput id='user' style={styles.input}
placeholder={'Username'}
/>
<TextInput id='Password' secureTextEntry={true}
placeholder={'Password'}
onChangeText={password => this.setState({password})}
/>
</View>
<View >
<Button style={styles.Register}
onPress={(this.onSubmitPressed)}>
Sign In
</Button>
</View>
</View>
)
}
}
export default Signin
If any one let me know how to solve this issue??
You need to set up your Navigator and initial route as the entry point as opposed to a regular component. Try something like this:
(Also set up a working example here)
https://rnplay.org/apps/iKx2_g
class App extends Component {
renderScene (route, navigator) {
return <route.component navigator={navigator} />
}
render() {
return (
<Navigator
style={styles.container}
renderScene={this.renderScene.bind(this)}
initialRoute={{component: SignIn}}
/>
);
}
}
class SignIn extends Component {
_navigate () {
this.props.navigator.push({
component: Payments
})
}
render () {
return (
<View>
<Text>Hello from SignIn</Text>
<Button onPress={this._navigate.bind(this)} />
</View>
)
}
}
class Payments extends Component {
render () {
return (
<Text>Hello from Payments</Text>
)
}
}
First you need to bind the this to the function onSubmitPressed. And make sure that you have passed navigator object to this component on the renderScene function of the navigator.
// binding this to onSubmitPressed
<Button style={styles.Register}
onPress={this.onSubmitPressed.bind(this)}
>
Sign In
</Button>
// binding this to on SubmitPressed using arrow function
<Button style={styles.Register}
onPress={() => this.onSubmitPressed()}
>
Sign In
</Button>