Emitting one event cause other event to trigger in react native - react-native

Context:
I am rendering a web view and the navigation buttons are on top bar. I send an event when a navigation back button is pressed. And similarly with the forward button it fires an event. The problem is when I press the back button it causes forward button to fire as well. Thus on the console it says, back button pressed and forward button pressed. This behaviour happens in Android only, in ios it works perfect. I am not sure what am I missing in the Android side.
My component is as follows.
import _EventEmitter from 'EventEmitter'
const appEventEmitter = new _EventEmitter()
export { appEventEmitter }
import React, { Component } from 'react'
import {
StyleSheet,
ScrollView,
} from 'react-native'
import { connect } from 'react-redux'
import { WebView } from 'react-native-webview'
import { Linking } from 'react-native'
import Spinner from 'react-native-loading-spinner-overlay'
import { appEventEmitter } from 'src/common'
import { Icon } from 'react-native-elements'
const goBack = 'goBack'
const goForward = 'goForward'
class HomeComponent extends Component {
static navigationOptions = ({navigation}) => {
return {
headerRight:(
<Icon
iconStyle={styles.chevronColor}
name="chevron-right"
onPress={() => appEventEmitter.emit(goForward) }
size={40}
/>),
headerLeft:(
<Icon
iconStyle={styles.chevronColor}
name="chevron-left"
onPress={() => appEventEmitter.emit(goBack) } // This causes to fire back and forward events
size={40}
/>),
}
}
constructor(props) {
super(props);
this.state = {
webViewRef: "webViewRef",
visible: true,
}
}
componentDidMount () {
this.goBackListenerId = appEventEmitter.addListener(goBack, () => this.goBack())
this.goForwardListenerId = appEventEmitter.addListener(goForward, () => this.goForward())
}
componentWillUnmount () {
appEventEmitter.removeListener(this.goBackListenerId)
appEventEmitter.removeListener(this.goForwardListenerId)
}
goBack = () => {
console.log("BACK PRESSED")
this.refs[this.state.webViewRef].goBack();
}
goForward = () => {
console.log("Forward PRESSED")
this.refs[this.state.webViewRef].goForward();
}
hideSpinner() {
this.setState({ visible: false });
}
showSpinner() {
this.setState({ visible: true });
}
render() {
return (
<ScrollView contentContainerStyle={styles.scrollableContainer}>
<Spinner
visible={this.state.visible}
style={styles.spinnerColor}
/>
<WebView
source={{uri: BASE_URL}}
style={styles.container}
onLoadStart={() => this.showSpinner()}
onLoadEnd={() => this.hideSpinner()}
ref={this.state.webViewRef}
javaScriptEnabled={true}
domStorageEnabled={true}
geolocationEnabled={true}
cacheEnabled={true}
/>
</ScrollView>
)
}
}
const styles = StyleSheet.create({
scrollableContainer: {
flex: 1,
},
spinnerColor: {
color: 'white'
},
navigationHeader: {
backgroundColor: colors.primary,
},
container: {
flex: 1,
},
chevronColor: {
color: 'white'
}
});
const Home = connect()(HomeComponent)
export { Home }

Related

Login Custom TextInput and Pressable not working after logout

This is my Navigation
import React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import { createNativeStackNavigator } from '#react-navigation/native-stack'
import { Login } from '../views/Login'
import { Home } from '../views/modules/Home'
const Stack = createNativeStackNavigator();
const Navigation = () => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Login" component={Login} />
<Stack.Screen name="Home" component={Home} />
</Stack.Navigator>
</NavigationContainer>
)
}
export { Navigation }
This is my Login
import React, { useState, useRef, } from 'react'
import { View, StyleSheet, Alert } from 'react-native'
import FormData from 'form-data'
import { CustomInput } from '../components/CustomInput'
import { CustomButton } from '../components/CustomButton'
import { CustomModalAlert } from '../components/CustomModalAlert'
import { useNavigation } from '#react-navigation/native'
const Login = () => {
const navigation = useNavigation()
const [showModal, setShowModal] = useState(false)
const [code, setCode] = useState("")
const setModalData = (data) => {
setShowModal(data.bool)
if (data.bool) {
navigation.navigate('Home')
}
}
const submitLoginForm = () => {
var data = new FormData();
data.append("code", code);
fetch("http://192.168.3.200/mobile_login/api", {
method : 'POST',
headers : {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data',
},
body : data,
})
.then((res)=>res.json())
.then((res)=>{
if (res.success == 1) {
setShowModal(true)
} else {
Alert.alert(
"Opps!",
res.message,
[{ text: "OK" }]
)
}
})
.catch((err) => {
Alert.alert(
"Network Error!",
"Please check your internet connection",
[{ text: "OK" }]
)
})
}
return (
<View style={styles.container}>
<CustomInput
placeholder='Code'
autoFocus={true}
value={code}
onChangeText={(code) => setCode(code)}
/>
<CustomButton
text={'LOG IN'}
onPress={() => submitLoginForm()}
/>
<CustomModalAlert
setData={setModalData}
showModal={showModal}
title={'Confirmation'}
text={'Go to Home?'}
cancelButtonLabel={'Cancel'}
confirmButtonLabel={'Confirm'}
/>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
})
export { Login }
This is Home
import React, { useState } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import Icon from 'react-native-vector-icons/MaterialIcons'
import { useNavigation } from '#react-navigation/native'
import { CustomModalAlert } from '../../components/CustomModalAlert'
import { CustomFloatingLogout } from '../../components/CustomFloatingLogout'
const Home = () => {
const [showModal, setShowModal] = useState(false)
const navigation = useNavigation()
const logout = () => {
setShowModal(true)
}
const setModalData = (data) => {
setShowModal(data.bool)
if (data.bool) {
navigation.navigate('Login')
}
}
return (
<View style={styles.container}>
<Text><Icon name="person" size={30} /></Text>
<Text>Home</Text>
<CustomFloatingLogout onPress={() => logout()} />
<CustomModalAlert
setData={setModalData}
showModal={showModal}
title={'Logout?'}
text={'Go to Home?'}
cancelButtonLabel={'Cancel'}
confirmButtonLabel={'Yes, Log Out!'}
/>
</View>
)
}
const styles = StyleSheet.create({ container: { flex: 1, }, })
export { Home }
Login View
Home View
When I click the login button in Login View then Ok the popup modal, it goes to the Home View and the homes floating button is working then when I click the Home View floating button to logout it goes to Login View but the TextInput and Button is not working
The question is how can I make TextInput and Button work?
Sorry, I'm new in react-native. Thanks for help.
I have solved and will answer my question 😅 thanks to #Kailash
const setModalData = (data) => {
setShowModal(data.bool)
if (data.bool) {
setShowModal(false)
props.navigation.navigate('Home')
}
}
I just added
setShowModal(false)
under
if (data.bool) {
in Login
The problem is I forgot to close the modal before navigating.
Looks like you have opened Modal but you haven't closed. So its causing this issue.
Reason: If you have used transparent modal and then click on navigate to other screen, in this case if the Modal is still opened, then you will see the new screen but you won't be able to click it, because of transparent modal.
In your case you should be setting your showModal state to false, once click on the label 'Yes, Log Out!'. And do the same for your Login Module as well, if not closed.

TouchableOpacity does not show disabled and activeOpacity props

I am trying to test this simple button:
import React, { FC, ReactNode } from 'react';
import { TouchableOpacity, GestureResponderEvent, ViewStyle } from 'react-native';
type Props = {
style: ViewStyle;
onPress: (event: GestureResponderEvent) => void;
disabled?: boolean;
activeOpacity?: number;
children: ReactNode;
};
export const Button: FC<Props> = ({
style,
onPress,
disabled,
activeOpacity,
children
}) => {
return (
<TouchableOpacity
activeOpacity={ activeOpacity }
onPress={ onPress }
style={ style }
disabled={ disabled }
testID={ 'button' }
>
{ children }
</TouchableOpacity>
);
};
I use this test file, where I simply render my Button with some props:
import React from 'react';
import { Text, StyleSheet } from 'react-native';
import { render } from '#testing-library/react-native';
import { ReactTestInstance } from 'react-test-renderer';
import { Button } from '../../../src/components/Button';
const styles = StyleSheet.create({
button: {
height: 50
}
});
const onPressMock = jest.fn();
describe('FilterForm', () => {
it('should render Button with default arguments', () => {
const { queryByText, debug } = render(
<Button style={ styles.button } onPress={ onPressMock } disabled activeOpacity={ 0.3 }>
<Text>{ 'Dummy Test Text' }</Text>
</Button>
);
debug();
// Not important - just in case you are curious //
let buttonText = queryByText('Dummy Test Text');
expect(buttonText).not.toBeNull();
buttonText = buttonText as ReactTestInstance;
expect(buttonText.parent?.parent?.props.testID).toEqual('button');
expect(buttonText.parent?.parent?.props.activeOpacity).toEqual(0.3);
expect(buttonText.parent?.parent?.props.disabled).toEqual(true);
});
});
The problem is that I get this tree returned, which does not have disabled or activeOpacity in it:
<View
accessible={true}
focusable={true}
onClick={[Function onClick]}
onResponderGrant={[Function onResponderGrant]}
onResponderMove={[Function onResponderMove]}
onResponderRelease={[Function onResponderRelease]}
onResponderTerminate={[Function onResponderTerminate]}
onResponderTerminationRequest={[Function onResponderTerminationRequest]}
onStartShouldSetResponder={[Function onStartShouldSetResponder]}
style={
Object {
"height": 50,
"opacity": 1,
}
}
testID="button"
>
<Text>
Dummy Test Text
</Text>
</View>
Because of that my assertions in the test file above fail. How can I test the props of TouchableOpacity then?
Thanks in advance for your time!
I can call disabled prop by using fireEvent(button, 'press'). Disabled button will not call the handler, so I can assert it with expect(handlerMock).not.toBeCalled().
As to activeOpacity, I guess storybook should be used for visual testing.

How to render a modal on top of a screen

I am implementing firebase cloud messaging in my react native app. I have a regular class (not a react component) with static methods and variables that is instantiated when app fires. This class have three static methods/listeners (foreground, background and launch app) to manage push notifications from firebase.
I am trying to handle actions when in foreground.
How do I render a modal on top of a screen (not switching screens but run an overlay card in the center with two buttons), when user receives a push message from firebase, regardless of which screen he is on?
Tried solution provided on this article at hackernoon, wrapping modal component inside the AppContainer did not do anything. https://hackernoon.com/managing-react-modals-with-singleton-component-design-5efdd317295b
I have also tried the idea on this tutorial with modal component, but have not succeed in any cases.
https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
Firebase cloud messaging class:
export default class FCM {
static appClosedListener = async () => {
const firebaseContent = await firebase.notifications().getInitialNotification();
if (firebaseContent) {
console.log(firebaseContent);
const serverParcel = JSON.parse(firebaseContent._data.payload).an;
const notificationState = "closedapp";
FCM.pushNotificationHandler(serverParcel, notificationState);
}
};
static backgroundListener = () => {
FCM.background = firebase.notifications().onNotificationOpened(firebaseContent => {
const serverParcel = JSON.parse(firebaseContent._data.payload).an;
const notificationState = "background";
FCM.pushNotificationHandler(serverParcel, notificationState);
});
};
static foregroundListener = () => {
FCM.foreground = firebase.notifications().onNotification(firebaseContent => {
const serverParcel = JSON.parse(firebaseContent._data.payload).an;
const notificationState = "foreground";
FCM.pushNotificationHandler(serverParcel, notificationState);
});
};
static pushNotificationHandler = (serverParcel, notificationState) => {
const typeOfAction = serverParcel.typeEnum;
switch(typeOfAction){
case "message":
break;
case "webnews":
const url = serverParcel.link;
NavigationService.navigate("FCMModal", {}); //Here is where I want the modal to popup.
//NavigationService.navigate("InAppBrowser", {url});
break;
}
}
App.js
const ModalStack = createStackNavigator(
{
FCMModal: FCMModal
},
{
mode: 'modal',
transparentCard: true,
cardStyle: {
// makes transparentCard work for android
opacity: 1.0
}
}
)
let rootStack = createStackNavigator(
{
main: {
screen: BottomTabNavigator //There are four navigatorstacks with multiple screens beneath here
},
InAppBrowser: {
screen: InAppBrowserStack,
defaultNavigationOptions: {
tabBarVisible: true
}
},
FCMModal: {
screen: ModalStack
}
},
{
initialRouteName: "main",
headerMode: "none"
}
);
const AppContainer = createAppContainer(rootStack);
export default class App extends Component {
render() {
return (
<AppContainer
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef);
}}
/>
);
}
}
Modal class
import React, { Component } from 'react';
import { Modal, Text, TouchableHighlight, View } from 'react-native';
export default class FCMModal extends Component {
state = {
modalVisible: true,
};
setModalVisible(visible) {
this.setState({modalVisible: visible});
}
render() {
return (
<View style={{width: 600, height: 400, borderWidth: 2, borderColor: 'red', opacity: 0.5}}>
<Modal
animationType="slide"
transparent={true}
visible={this.state.modalVisible}
onRequestClose={() => {console.log("CLOSING MODAL!")}}>
<View style={{marginTop: 22}}>
<View>
<Text>Hello World!</Text>
<TouchableHighlight
onPress={() => {
this.setModalVisible(false);
}}>
<Text>Hide Modal</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
</View>
);
}
}

React Native: ListView.DataSource is not updating

I am trying to update ListView.DataSource after fetching data from server but its not happening. I have two components, one is imported in other.
From base component I am trying to update ListView.DataSource in other component. Here is my code.
index.android.js
import React, { Component } from "react";
import { ListView, View, Button, AppRegistry } from "react-native";
import OtherComponent from "./Components/OtherComponent";
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
movies: [{ title: "ABCD" }, { title: "EFGH" }]
};
}
getTopics = () => {
fetch("https://facebook.github.io/react-native/movies.json")
.then(response => response.json())
.then(responseText => {
console.log(responseText.movies);
this.setState({
movies: responseText.movies
});
})
.catch(error => {
console.warn(error);
});
};
render() {
return (
<View>
<Button
onPress={this.getTopics}
title="Get Topics"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
<OtherComponent movies={this.state.movies} />
</View>
);
}
}
AppRegistry.registerComponent("MyApp", () => MyApp);
OtheComponent.js
import React, { Component } from "react";
import { View, ListView, Text, StyleSheet, Button } from "react-native";
export default class FormativeRevisionList extends Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.state = {
dataSource: ds.cloneWithRows(props.movies)
};
}
render() {
return (
<View>
<ListView
style={styles.listContainer}
dataSource={this.state.dataSource}
renderRow={rowData => (
<View>
<Text style={styles.listItem}>{rowData.title}</Text>
</View>
)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
listContainer: {
paddingTop: 22
},
listItem: {
fontSize: 30,
fontWeight: "bold",
textAlign: "center"
}
});
In your code, you only set your dataSource in contructor of FormativeRevisionList, this mean FormativeRevisionList will only render the given movies when your first render it.
To render new list after you press Get Topics button , you need to set the dataSource again when it receive new props, this can be achieve by setting it in FormativeRevisionList componentWillReceiveProps
componentWillReceiveProps(nextProps) {
if (nextProps.movies !== this.props.movies) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(nextProps.movies)
})
}
}
You can read more about componentWillReceiveProps from here

App get close when i pressed back button of android while using react native

I am new in react native. I have two pages in my app. When i press the back button, i want to open the previous page but when i press the back button, app get close. What can be done to solve this issue ?
My code is :
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Navigator,
TextInput,
TouchableHighlight
} from 'react-native';
import ToolbarAndroid from 'ToolbarAndroid';
import ActionButton from 'react-native-action-button';
import backAndroid from 'react-native-back-android';
import {hardwareBackPress} from 'react-native-back-android';
class AwesomeProject extends Component {
renderScene(route, navigator) {
if(route.name == 'HomePage') {
return <HomePage navigator={navigator} {...route.passProps} />
}
if(route.name == 'FormBuilderPage') {
return <FormBuilderPage navigator={navigator} {...route.passProps} />
}
}
render() {
return (
<Navigator
style={{ flex:1 }}
initialRoute={{ name: 'HomePage' }}
renderScene={ this.renderScene } />
)
}
}
class BackButtonEvent extends React.Component{
handleHardwareBackPress(){
if(this.sate.isOpen()){
this.handleClose();
return true;
}
}
}
var HomePage = React.createClass({
_navigate(name) {
this.props.navigator.push({
name: 'FormBuilderPage',
passProps: {
name: name
}
})
},
render() {
return (
<View style={styles.container}>
<ToolbarAndroid style = {styles.toolbar}>
<Text style = {styles.titleText}> Data Collector </Text>
</ToolbarAndroid>
<ActionButton
source = {require('./icon_container/ic_plus_circle_add_new_form.png')}
onPress = {this._navigate}
>
</ActionButton>
</View>
)
}
})
var FormBuilderPage = React.createClass({
render() {
return (
<View style={styles.container}>
<ToolbarAndroid style = {styles.toolbar}>
<TextInput placeholder = "Text here"/>
</ToolbarAndroid>
</View>
)
}
})
var styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
toolbar: {
height: 56,
backgroundColor: '#3F51B5'
},
titleText: {
color: '#fff',
}
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
You need to use BackAndroid API of React Native. This is the snippet from my example project.
BackAndroid.addEventListener('hardwareBackPress', () => {
var flag = false;
if(_route.name==="newbooking"){
Alert.alert(
"Confirmation",
"Are you sure you want to cancel?",
[
{text: 'No', onPress: () => console.log('OK Pressed!')},
{text: 'Yes', onPress: () => {_navigator.pop();}}
]
);
return true;
}
else{
flag = true;
}
if (_navigator.getCurrentRoutes().length === 1 ) {
return false;
}
if(flag){
_navigator.pop();
return true;
}
});
You can see how I have implemented that here!