React Native global back handling - reactjs

I have 3 components:
ComponentA
ComponentB
BackPressHandlingComponent
BackPressHandlingComponent deals with back press.
When back pressed from ComponentA; I must exit the app.
When back pressed from ComponentB; I must go to ComponentA.
Here is my BackPressHandlingComponent code -
import { BackHandler } from 'react-native';
export class BackPressHandlingComponent extends Component {
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackPress);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackPress);
}
}
My question is -
How do I tell BackPressHandlingComponent from Component A that I must exit app and from Component B that I need to go back to Component A

As per your use case, I would have addedBackpress event listeners on ComponentA and ComponentB, such that when you are on ComponentA when the callback is called you can exit the app and when in ComponentB its callback is called you can navigate to ComponentA.
Simple demo for above solution:
App.js
/**
*
* #format
* #flow
*/
import React, { Component } from 'react';
import { Text, View, TouchableOpacity, StyleSheet } from 'react-native';
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'
import BackHandlerHOC from './BackHandlerHOC'
type Props = {};
export default class App extends Component<Props> {
state = {
render: 'A'
}
toggleComponent = () => {
let component = 'A'
if (this.state.render === 'A') {
component = 'B'
}
this.setState({ render: component })
}
render() {
const { render } = this.state
const wrappercomponent = render === 'A' ? (
<BackHandlerHOC
name="ComponentA"
Component={ComponentA}
/>
) : (
<BackHandlerHOC
name="ComponentB"
Component={ComponentB}
/>
)
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => this.toggleComponent()}
>
<Text> Change </Text>
</TouchableOpacity>
{wrappercomponent}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
padding: 20
}
})
ComponentA
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class ComponentA extends Component {
render() {
return (
<View>
<Text>A</Text>
</View>
);
}
}
export default ComponentA;
ComponentB
import React, { Component } from 'react';
import { View, Text } from 'react-native';
class ComponentB extends Component {
render() {
return (
<View>
<Text>B</Text>
</View>
);
}
}
export default ComponentB;
BackHandlerHOC
import React, { Component } from 'react';
import { BackHandler, ToastAndroid, View, Text } from 'react-native';
class BackHandlerHOC extends Component {
componentDidMount = () => {
BackHandler.addEventListener('hardwareBackPress', this.backPressHandler);
};
componentWillUnmount = () => {
BackHandler.removeEventListener('hardwareBackPress', this.backPressHandler);
};
backPressHandler = () => {
const { name } = this.props;
if (name === 'ComponentA') {
BackHandler.exitApp()
} else {
// this.props.navigator.resetTo({
// screen: 'ComponentA'
// })
ToastAndroid.show('will go back to A', 0);
}
return true;
};
render() {
const { Component } = this.props;
return (
<View>
<Text>Hello from backpress</Text>
<Component />
</View>
);
}
}
export default BackHandlerHOC;
You can also find the working example on expo here
Hope this helps

Just to add another approach,
I made use of the react-navigation lifecycle events,and the hardwareBackPress event, mind you the version of react-navigation here is 3.x.x.
The lifecycle event onWillFocus is called when the screen comes in view and the life-cycle event onWillBlur is called when the user is moving on to another screen, here somehow the React lifecycle events are in the hands of react-navigation, hence cannot use them here see https://reactnavigation.org/docs/3.x/navigation-lifecycle.
Following is the code:
import { BackHandler,Alert } from "react-native";
import { NavigationEvents } from 'react-navigation';
class SomeComponent {
//...my componentDidMount etc and other methods.....
backButtonAction(){
Alert.alert(
"Confirm Exit",
"Do you want to exit the app?",
[
{
text: "No",
onPress: () => {},
style: "cancel"
},
{ text: "Yes", onPress: () => BackHandler.exitApp() }
],
{ cancelable: false }
);
return true; // coz the event handler needs to return boolean.
};
setBackButtonAction(){
BackHandler.addEventListener(
"hardwareBackPress",
this.backButtonAction
);
}
removeBackButtonAction(){
BackHandler.removeEventListener(
"hardwareBackPress",
this.backButtonAction
);
}
render() {
return (
<Container>
<NavigationEvents
onWillFocus={payload => this.setBackButtonAction()}
onWillBlur={payload => this.removeBackButtonAction()}
/> //..... my view code
</Container>)
}
}

Related

passing textinput value from one screen into another using mapDispatchToProps

I want to pass value of a textinput from one screen to another using mapDispatchToProps. I am roughly a newbie in the redux world and I am a bit confused. kindly make corrections to my code below. I have tried using the example implemented on the documentation, however, I do not fully understand mapDispatchToProps.
PS I tried to keep the code as simple as possible for better understanding
Screen1
import React, { Component, Fragment } from 'react';
import {
View,
Text,
StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
class Screen1 extends Component {
static navigationOptions = {
header: null,
}
constructor(props) {
super(props);
this.state = {
total: 1,
};
this.onChangeText = this.onChangeText.bind(this);
}
onChangeText(number) {
const total = parseInt(number);
if (number.length === 0) {
this.setState({ total: '' });
} else {
this.setState({ total });
}
}
render() {
return (
<SafeAreaView style={styles.AndroidSafeArea}>
<View style={styles.wrapper}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollableList}
>
<InputField
children={"Receiver's phone no."}
iconType={'ios-call'}
placeholder={"number"}
keyboardType={'phone-pad'}
maxLength={11}
/>
<InputField
children={"Receiver's gifts"}
iconType={'ios-basket'}
placeholder={'Gifts'}
keyboardType={'phone-pad'}
maxLength={2}
onChangeText={this.onChangeText}
value={this.state.total.toString()}
/>
</ScrollView>
</View>
</SafeAreaView>
);
}
}
function mapDispatchToProps(dispatch) {
return {
total: () => {
dispatch(this.onChangeText());
}
}
}
export default connect(mapDispatchToProps) (Screen1);
Screen2
import React, { Component, Fragment } from 'react';
import {
View,
Text,
StyleSheet,
} from 'react-native';
import { connect } from 'react-redux';
class Screen2 extends Component {
static navigationOptions = {
header: null,
}
constructor(props) {
super(props);
this.state = {
};
}
render() {
return (
<SafeAreaView style={styles.AndroidSafeArea}>
<View style={styles.wrapper}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollableList}
>
<Text>{this.props.onChangeText}</Text>
</ScrollView>
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return {
total: state.onChangeText
}
}
}
export default connect(mapStateToProps) (Screen2);
Reducer.js
import { TOTAL_GIFTS } from '../actions/types';
const initialState = {
total: ''
};
const Reducer = (state = initialState, action) => {
switch (action.type) {
case TOTAL_GIFTS:
return {
...state,
total: action.total
};
default:
return state;
}
};
export default Reducer;
Im leaving the none redux related parts of your code out.
Screen1
class Screen1 extends React.component{
handleChange(number){
this.props.announceChange(number)
}
}
//mapping dispatch to props
function mapDispatchToProps(dispatch){
announceChange(number)
}
//action creator
function announceChange(number){
return {type:'SOME_CONSTANT',payload:number}
}
export default connect(null,mapStateToProps)(Screen1)
Reducer:
export default function reducer(state={},action){
switch(action.type){
case 'SOME_CONSTANT':
return {...state,number:action.payload}
default :
return state;
}
}
screen2:
class Screen2 extends React.component{
render(){
const {number} = this.props
return(
<span>{number}</span>
)
}
}
function mapStateToProps(state){
return {
number : state.reducername
}
}
export default connect(mapStateToProps)(Screen2);
the above code is a minimal sample of the way you can use redux. if you dont have any ideas how to setup your store,reducer and other redux stuff reading this wont take more than 10 mints.
mapDispatchToProps: are functions/actions to update store(redux)
mapStateToProps : to get data from store(redux)
on first screen you will disptach action to update email using mapDispatchToProps
on second you will get email from mapStateToProps
I have created a sample code for you (CHECK IN ANDROI/IOS)
Please check https://snack.expo.io/#mehran.khan/reduxtest
APP PREVIEW

React native, delay api call?

I have a method called: onChangeText
It means every time I type, it will search the remote api.
How do I delay the remote api call? i.e. let user types certain things, then connect the api, rather than connect every key stroke.
onChangeText(title) {
console.log('-- chg text --');
console.log(title);
this.props.searchApi(title);
}
The component:
import React, { Component } from 'react';
import { SearchBar, Divider } from 'react-native-elements';
import { View, ScrollView, Text, StyleSheet, Image} from 'react-native';
import { connect } from 'react-redux';
// action creator
import { searchApi } from './reducer';
class SearchContainer extends Component {
constructor(props) {
super(props);
}
onChangeText(title) {
console.log('-- chg text --');
console.log(title);
this.props.searchApi(title);
}
onClearText(e) {
console.log('-- clear text --');
console.log(e);
}
render() {
const { } = this.props;
const containerStyle = {
display: 'flex',
justifyContent: 'center',
alignItems: 'center'
}
const searchStyle = {
width: 300,
height: 45
};
return (
<View
style={containerStyle}
>
<Image
source={require('../../asset/img/logo.png')}
style={{
height: 150,
width: 150
}}
/>
<SearchBar
cancelButtonTitle="Cancel"
placeholder='Search'
containerStyle={searchStyle}
onChangeText={this.onChangeText.bind(this)}
onClearText={this.onClearText.bind(this)}
/>
</View>
);
}
}
const mapStateToProps = state => {
return {
};
};
const mapDispatchToProps = dispatch => {
return {
searchApi: () => dispatch(searchApi())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchContainer);
Use lodash debounce. It is used for this exact use case
Sample React example. Should be able to port to native the same way
import React, {Component} from 'react'
import { debounce } from 'lodash'
class TableSearch extends Component {
//********************************************/
constructor(props){
super(props)
this.state = {
value: props.value
}
this.changeSearch = debounce(this.props.changeSearch, 250)
}
//********************************************/
handleChange = (e) => {
const val = e.target.value
this.setState({ value: val }, () => {
this.changeSearch(val)
})
}
//********************************************/
render() {
return (
<input
onChange = {this.handleChange}
value = {this.props.value}
/>
)
}
//********************************************/
}

React Native wont render next array items at setState

Hello I am trying to make a step wizard component but I have the following issue. I have the following file:
import React from 'react';
import { View } from 'react-native';
import WizardStep from './WizardStep'
export default class Wizard extends React.Component {
constructor(props){
super(props);
this.state = {
questions: this.props.questions,
answers: this.props.answers,
totalSteps: this.props.questions.length,
currentStep: 0,
results: []
}
}
updateStep = answer => {
newResults = this.state.results
newResults[this.state.currentStep - 1] = answer
this.setState({
results: newResults,
currentStep: this.state.currentStep + 1
}, () => {
if (this.state.currentStep == this.state.totalSteps) {
this.props.finish();
}
})
}
renderStep = () => {
if (this.state.currentStep < this.state.totalSteps) {
return (
<View>
<WizardStep
question={this.state.questions[this.state.currentStep]}
answers={this.state.answers[this.state.currentStep]}
step={this.state.currentStep}
totalSteps={this.state.totalSteps}
updateStep={this.updateStep}
/>
</View>
);
} else {
return null;
}
}
render(){
return(
<View>
{this.renderStep()}
</View>
)
}
}
questions is an array of strings and answers is an array of arrays of strings.
Anyway the first screen shows up just fine. But when I call the updateStep function the currentStep updates but it doesn't show the 2nd item from questions/answers array. Any ideas? Thank you in advance!
Adding the other components for the wizard:
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Button } from "react-native-elements";
import { Constants } from 'expo';
import WizardStepButton from './WizardStepButton';
export default class WizardStep extends React.Component {
constructor(props){
super(props);
this.state ={
question: this.props.question,
answers: this.props.answers,
totalSteps: this.props.totalSteps,
step: this.props.step,
}
}
renderAnswers = () => {
var answers = []
for (var i = 0; i < this.state.answers.length; i++) {
answers.push(
<WizardStepButton
answer={this.state.answers[i]}
updateStep={this.props.updateStep}
key={i}
/>
);
}
return answers;
}
render(){
return(
<View>
<Text style={styles.title}>Step {this.state.step + 1}/{this.state.totalSteps}</Text>
<Text style={styles.title}>{this.state.question}</Text>
{this.renderAnswers()}
</View>
)
}
}
const styles = StyleSheet.create({
title: {
marginTop: 30,
marginBottom: 30,
fontSize: 25,
color: 'rgba(96,100,109, 1)',
lineHeight: 24,
textAlign: 'center',
},
});
and the button component:
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { Button } from "react-native-elements";
import { Constants } from 'expo';
export default class WizardStepButton extends React.Component {
constructor(props){
super(props);
this.state ={
}
}
render(){
return(
<View>
<Button
style={{margin: 10}}
large
raised
title={this.props.answer}
onPress={() => this.props.updateStep(this.props.answer)}
/>
</View>
)
}
}
You should only increment state values by using a state updater function. - https://stackoverflow.com/a/45196079/874027
You're not spreading this.state.results before editing and putting them back into state.
Also the currentStep checks indexing looks off.
updateStep = answer => {
this.setState((state) => {
const { results, currentStep } = state
const newResults = [...results]
newResults[currentStep] = answer
return {
results: newResults,
currentStep: currentStep + 1,
}
}, () => {
const { currentStep, totalSteps } = this.state
if (currentStep + 1 === totalSteps) {
this.props.finish();
}
})
}
EDIT: in WizardStep component you're syncing props with state in constructor so when you try to pass the new props after you update your state, they'll never get reflected in the Wizard since its constructor has already fired off. You can either fix this by using props in your WizardStep component, or by passing it a key, so the new instance gets created every time the key changes, e.g.
<WizardStep
question={this.state.questions[this.state.currentStep]}
answers={this.state.answers[this.state.currentStep]}
step={this.state.currentStep}
totalSteps={this.state.totalSteps}
updateStep={this.updateStep}
key={this.state.currentStep}
/>
I've tested this locally and the steps do get changed with this approach.

How to clear interval when app in background state react-native?

I have a component which fetch a request in every 30 seconds interval, it's working fine as expected. But I want when app goes in background state the request will stop and when app in foreground the request will start and vise-versa. Is there is any solution to do this.
Here is my component file.
Notifications.js
/**
* Notification Button
*/
import React from "react";
import { View, TouchableWithoutFeedback, Text } from "react-native";
import { connect } from "react-redux";
import Icon from 'react-native-vector-icons/MaterialIcons';
// styles
import styles from "./Styles";
// global styles
import globalStyles from "BkProvider/src/styles/global";
import { colors } from "BkProvider/src/styles/base";
// root navigator
import { rootNavigator } from "BkProvider/src/screens/DashboardGroup";
// strings
import strings from "BkProvider/src/strings";
// actions
import { getSystemAlerts, seeAllSystemAlertLogs, stopNotificationTick } from "BkProvider/src/actions";
// Navigation constants
import NavigationConstants from "BkProvider/src/constants/NavigationConstants";
const { posRelative } = globalStyles;
class NotificationButton extends React.Component {
componentDidMount() {
this.props.getSystemAlerts();
}
componentWillUnmount() {
stopNotificationTick();
}
/**
* Function to open system alerts popup
*/
openSystemAlerts() {
this.props.seeAllSystemAlertLogs(this.props.unseenAlertLogIdsArray);
rootNavigator.push({
screen: NavigationConstants.SYSTEM_ALERTS_LISTING,
title: strings.allNotifications,
backButtonTitle: ''
});
}
render() {
const { unseenAlertLogIdsArray } = this.props;
return (
<TouchableWithoutFeedback onPress={this.openSystemAlerts.bind(this)}>
<View style={styles.button}>
<View style={[posRelative]}>
<Icon
name="notifications-none"
size={27}
color={colors.white}
/>
{(unseenAlertLogIdsArray && unseenAlertLogIdsArray.length > 0) &&
<Text style={styles.badge}>{unseenAlertLogIdsArray.length}</Text>
}
</View>
</View>
</TouchableWithoutFeedback>
);
}
}
const mapStateToProps = ({ systemAlerts }) => {
const { unseenAlertLogIdsArray } = systemAlerts;
return { unseenAlertLogIdsArray }
}
export default connect(mapStateToProps, {
getSystemAlerts,
seeAllSystemAlertLogs
})(NotificationButton);
Actions.js
/**
* System Alerts Actions
*/
import Toast from "react-native-simple-toast";
import { NetInfo } from "react-native";
// action types
import {
GET_SYSTEM_ALERTS,
SEE_ALL_SYSTEM_ALERT_LOGS
} from "BkProvider/src/actions/actionTypes";
// helpers methods
import { getUserId } from "./AppInitializer";
// webservice config
import WebServiceConfig, { APITypes } from "../webservice/WebServiceConfig";
import WebService from "../webservice/WebService";
import APINames from "../webservice/APINames";
let timer = null;
let globalDispatch;
let webServiceObject
/**
* Function To Get System Alerts
*/
export const getSystemAlerts = () => (dispatch) => {
clearInterval(timer);
globalDispatch = dispatch;
let apiConfig = new WebServiceConfig(APINames.LoadSystemAlertLogs),
httpParams = {
'page': "",
'limit': "",
'role': 'provider',
'uid': getUserId()
}
webServiceObject = new WebService(onResultCallback)
.addPostParameterObject(httpParams)
.addServiceConfiguration(apiConfig)
timer = setInterval(() => notificationsTick(), 15000);
notificationsTick();
}
/**
* API Response Callback Handler
*/
const onResultCallback = (webServiceResultObj) => {
if (webServiceResultObj.isSuccess && webServiceResultObj.response != null) {
if (webServiceResultObj.response.api_status === 1) {
if (globalDispatch) {
globalDispatch({ type: GET_SYSTEM_ALERTS, payload: webServiceResultObj.response.data });
}
}
} else {
if (webServiceResultObj.shouldShowErrorMessage)
Toast.show(webServiceResultObj.errorMessage)
}
}
/**
* System Alerts Notification Ticks
*/
const notificationsTick = () => {
NetInfo.isConnected.fetch().then(isConnected => {
if (isConnected) {
if (webServiceObject)
webServiceObject.execute();
}
})
}
/**
* Function To Clear The Interval Of System Alerts Api
*/
export const stopNotificationTick = () => {
clearInterval(timer);
}
/**
* Function To See All System Alerts Logs
*/
export const seeAllSystemAlertLogs = (unseenAlertLogIdsArray) => (dispatch) => {
if (unseenAlertLogIdsArray) {
let apiConfig = new WebServiceConfig(APINames.SeeAllSystemAlertLogs)
.setAPIType(APITypes.POST);
// params
let params = {
role: "provider",
uid: getUserId(),
unseen_log_ids: unseenAlertLogIdsArray
}
dispatch({ type: SEE_ALL_SYSTEM_ALERT_LOGS }); // dispatch an action to see all system alert logs
new WebService()
.addPostParameterObject(JSON.stringify(params))
.addServiceConfiguration(apiConfig)
.execute()
}
}
Just check the AppState when notificationsTick gets called.
if(AppState.currentState === "active"){
notificationsTick();
}
Note that JS timers might not get called when your app is in background - From my own experience, on iOS, click the home button, touch power button to enter sleep mode, application Javascript gets frozen in 10 seconds. And they might get called when your app gets back in foreground... You should test that
A more robust implementation would keep a reference on the timer, and cancel it when app goes into background.
You should use App state for this case here is the sample code that i used
import React, {Component} from 'react'
import {AppState, Text} from 'react-native'
class AppStateExample extends Component {
state = {
appState: AppState.currentState
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
console.log('App has come to the foreground!')
}
this.setState({appState: nextAppState});
}
render() {
return (
<Text>Current state is: {this.state.appState}</Text>
);
}
}
There is a React native API for that. https://facebook.github.io/react-native/docs/appstate
This would let you check if the app is in background or foreground, and then you can either cancel the request, or continue.
Basic Usage example from their Docs:
import React, {Component} from 'react'
import {AppState, Text} from 'react-native'
class AppStateExample extends Component {
state = {
appState: AppState.currentState
}
componentDidMount() {
AppState.addEventListener('change', this._handleAppStateChange);
}
componentWillUnmount() {
AppState.removeEventListener('change', this._handleAppStateChange);
}
_handleAppStateChange = (nextAppState) => {
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
console.log('App has come to the foreground!')
}
this.setState({appState: nextAppState});
}
render() {
return (
<Text>Current state is: {this.state.appState}</Text>
);
}
}
This console logs the current state of the app, so you would need to add this AppState to your state and do whatever you need.

How to pass params to other screen in React Native?

My React Js skills are very basic,What I want to get is when I click on a category, I show a list of posts of the category selected in a new screen in this case is PostsScreen.
The problem is that i get the itemId null.
I don't know what i'm doing wrong.
Categories Screen
import React, {Component} from 'react';
import { NavigationActions, DrawerNavigator, StackNavigator } from 'react-navigation';
import{Dimensions, Button, View, SafeAreaView, FlatList, ActivityIndicator, TouchableOpacity } from 'react-native';
export default class WGoals extends Component {
static navigationOptions = {
title: 'Categories'
};
navigateToScreen = (route, params) => () => {
const navigateAction = NavigationActions.navigate({
routeName: route,
params: params
});
this.props.navigation.dispatch(navigateAction);
}
constructor(props)
{
super(props);
this.state = {
isLoading: true,
}
}
render() {
return (
<Container style={styles.background_general}>
<TouchableOpacity onPress={this.navigateToScreen('PostsScreen', itemId = '1')} >
<Text>Category 1</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.navigateToScreen('PostsScreen', itemId = '2')} >
<Text>Category 2</Text>
</TouchableOpacity>
</Container>
);
}
}
Posts Screen
import React, {Component} from 'react';
import { NavigationActions, DrawerNavigator, StackNavigator } from 'react-navigation';
import{Dimensions, View, SafeAreaView, FlatList, ActivityIndicator } from 'react-native';
export default class Posts extends Component {
static navigationOptions = {
title: 'Posts'
};
render() {
const { params } = this.props.navigation.state;
const itemId = params ? params.itemId : null;
return (
<Container style={styles.background_general}>
<Text>Details Screen</Text>
<Text>itemId: {JSON.stringify(itemId)}</Text>
</Container>
);
}
}
Change arguments of your function
this.navigateToScreen('PostsScreen', itemId = '1')
to
this.navigateToScreen('PostsScreen', {itemId: '1'})

Resources