React Native Input Losing Focus after a keystroke - reactjs

So I am having a problem after just typing 1 key into a textfield the textfield is re-rendering and losing focus.
Here are some snippets of code, if you need more do not hesitate to ask:
Here is the DiaryInput Component:
const DiaryInput = ({label, value, onChangeText, placeholder, secureTextEntry, autoCorrect, autoCapitalize}) => {
const {inputStyle, labelStyle, containerStyle} = styles;
return (
<View style={containerStyle}>
<Text style={labelStyle}>
{label}
</Text>
<TextInput
secureTextEntry={secureTextEntry}
placeholder={placeholder}
autoCorrect={autoCorrect}
autoCapitalize={autoCapitalize}
style={[inputStyle, {height: Platform.OS === 'android' ? 50 : 25}]}
value={value}
onChangeText={onChangeText}
keyboardType={'numeric'}
/*editable={false}*//>
</View>
)};
Here I am using the Component (within render()):
import React, {Component} from 'react';
import {ScrollView} from 'react-native';
import {DiaryInput} from "../DiaryInput";
import {DiarySlider} from "../DiarySlider";
import DiaryHeader from "../DiaryHeader";
import {TextArea} from "../common/TextArea";
import {connect} from 'react-redux';
import {
carbsChanged,
fatsChanged,
proteinChanged,
dailyWeightChanged,
hoursSleepChanged,
feelLevelChanged,
wodChanged,
notesChanged,
journalChanged,
dateChangedLeft,
dateChangedRight,
getDiaryInfo,
saveDiaryInfo
} from "../../actions/DiaryActions";
class DiaryPage extends Component {
componentWillMount() {
// this.props.getDiaryInfo();
// this.state.currentDateString = dateString;
this.props.currentDate = new Date();
}
onCarbsChanged(text) {
this.props.carbsChanged(text);
}
onFatsChanged(text) {
this.props.fatsChanged(text);
}
onProteinChanged(text) {
this.props.proteinChanged(text);
}
onDailyWeightChanged(text) {
this.props.dailyWeightChanged(text);
}
onHoursSleepChanged(text) {
this.props.hoursSleepChanged(text);
}
onFeelLevelChanged(value) {
this.props.feelLevelChanged(value);
}
onWODChanged(text) {
this.props.wodChanged(text);
}
onNotesChanged(text) {
this.props.notesChanged(text);
}
onJournalChanged(text) {
this.props.journalChanged(text);
}
onRightChanged(date) {
this.props.dateChangedRight(date);
}
onLeftChanged(date) {
this.props.dateChangedLeft(date);
}
render() {
return (
<ScrollView>
<DiaryHeader
text={this.props.currentDate.toLocaleString("en-US")}
onLeft={this.onLeftChanged(this.props.currentDate)}
onRight={this.onRightChanged(this.props.currentDate)}/>
<DiaryInput
label={"Carbs"}
value={this.props.carbs}
onChangeText={this.onCarbsChanged.bind(this)}/>
<DiaryInput
label={"Fat"}
value={this.props.fats}
onChangeText={this.onFatsChanged.bind(this)}/>
<DiaryInput
label={"Protein"}
value={this.props.proteins}
onChangeText={this.onProteinChanged.bind(this)}/>
<DiaryInput
label={"Daily Weight"}
value={this.props.dailyWeight}
onChangeText={this.onDailyWeightChanged.bind(this)}/>
<DiaryInput
label={"Hours Sleep"}
value={this.props.hoursSleep}
onChangeText={this.onHoursSleepChanged.bind(this)}/>
<DiarySlider
label={"Feel Level"}
value={this.props.feelLevel}
onChangeText={this.onFeelLevelChanged.bind(this)}/>
<TextArea
label={"WOD"}
value={this.props.wod}
onChangeText={this.onWODChanged.bind(this)}/>
<TextArea
label={"Notes"}
value={this.props.notes}
onChangeText={this.onNotesChanged.bind(this)}/>
<TextArea
label={"Journal"}
value={this.props.journal}
onChangeText={this.onJournalChanged.bind(this)}/>
</ScrollView>
)
}
}
function mapStateToProps({diary}) {
const {carbs, fats, proteins, dailyWeight, hoursSleep, feelLevel, wod, notes, journal, currentDate} = diary;
return {carbs, fats, proteins, dailyWeight, hoursSleep, feelLevel, wod, notes, journal, currentDate};
}
export default connect(mapStateToProps, {
carbsChanged,
fatsChanged,
proteinChanged,
dailyWeightChanged,
hoursSleepChanged,
feelLevelChanged,
wodChanged,
notesChanged,
journalChanged,
dateChangedLeft,
dateChangedRight,
getDiaryInfo,
saveDiaryInfo
})(DiaryPage);
Here I have the function for when text Changes:
onHoursSleepChanged(text) {
this.props.hoursSleepChanged(text);
}
The Action:
export const hoursSleepChanged = (text) => {
return {
type: HOURS_SLEEP_CHANGED,
payload: text
}};
Reducer:
case HOURS_SLEEP_CHANGED:
return {...state, hoursSleep: action.payload};
And I think that's all that is needed...
The action and reducer is working properly... Just the input is losing focus after a single keystroke...
Please let me know if you have an idea on this. I have been struggling to find the cause.
Thank you!

Related

What is a good way to set local storage?

I have made a high contrast mode but I have gotten stuck with trying to save the new value to local storage, any guidance I will be appreciated.
Here is the themes wrapper where I am setting up the theme
import React, { useState, useEffect } from 'react';
import { ThemeContext, themes } from '../context/themeContext';
export function isOdd(num) {
console.log("isOdd",num % 2);
}
export default function ThemeContextWrapper(props) {
const [theme, setTheme] = useState(themes.dark);
function changeTheme(theme) {
setTheme(theme);
}
useEffect(() => {
switch (theme) {
case themes.light:
document.body.classList.add('white-content');
// Store
// Retrieve
break;
case themes.dark:
default:
document.body.classList.remove('white-content');
localStorage.setItem(theme, themes.dark);
localStorage.getItem(theme);
break;
}
}, [theme]);
return (
<ThemeContext.Provider value={{ theme: theme, changeTheme: changeTheme }}>
{props.children}
</ThemeContext.Provider>
);
}
and here is the on click listener that triggers the high contrast mode
import React, { useState, useEffect } from "react";
import 'devextreme/dist/css/dx.common.css';
import 'devextreme/dist/css/dx.material.blue.light.css';
// Custom icons by Ionicons
import 'ionicons/dist/css/ionicons.css';
import DarkModeicon from '../images/icons/dark-mode-icon.png'
import SpeedDialAction from 'devextreme-react/speed-dial-action';
import { isOdd } from '../themewrapper/themeWrapperContext';
import Sun from '../images/icons/sun.png'
import config from 'devextreme/core/config';
import notify from 'devextreme/ui/notify';
import '../App.css';
class FloatingActionButton extends React.Component {
constructor(props) {
super(props);
config({
floatingActionButtonConfig: {
icon: 'runner',
closeIcon: 'icon ion-md-close',
position: {
my: 'right bottom',
at: 'right bottom',
offset: '-16 -16'
}
}
});
}
// function enabled night mode
toggleTheme() {
isOdd(3)
console.log("toggleTheme click func")
document.body.classList.add('dark-content');
localStorage.setItem(theme, themes.dark);
localStorage.getItem(theme, themes.dark);
}
toggleDay() {
isOdd(3)
console.log("toggle day click func")
document.body.classList.remove('dark-content');
document.body.classList.add('white-content');
}
render() {
return (
<div id="app-container">
<SpeedDialAction
hint="Turn on Day Mode"
icon={Sun}
onClick={() => this.toggleDay()}
/>
<SpeedDialAction
hint="Increase Font"
icon="growfont"
onClick={() =>
alert("Increase Font Clicked!!")
}
/>
<SpeedDialAction
hint="turn on night mode"
icon={DarkModeicon}
onClick={() => this.toggleTheme()}
/>
</div>
);
}
}
export default FloatingActionButton;
I tried to set up the local storage in both the on click listener and theme wrapper no success.
As using the Context.Provider' to provide the value to the child, you should also use Context.Consumer' in the child which needs to pick the value from provider.
So in FloatingActionButton component, you might use this instead:
class FloatingActionButton extends React.Component {
// ...some other code
toggleTheme(theme, changeTheme) {
let newTheme;
if (theme === themes.light) {
newTheme = themes.dark;
} else {
newTheme = themes.light;
}
changeTheme(newTheme); // set the theme immediately
localStorage.setItem('theme', newTheme); // then save it into localStorage
}
render() {
return (
<ThemeContext.Consumer>
{(theme, changeTheme) => (
// ...some other elements
<button onClick={() => toggleTheme(theme, changeTheme)}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
)
}
}
Then you can detect the theme in the ThemeContextWrapper component:
export default function ThemeContextWrapper(props) {
const [theme, setTheme] = useState(); // keep the default value as null because it will get the value in localStorage when useEffect detect the first render.
function changeTheme(theme) {
setTheme(theme);
}
useEffect(() => {
switch (theme) {
case themes.light:
document.body.classList.add('white-content');
break;
case themes.dark:
document.body.classList.remove('white-content');
default:
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
setTheme(savedTheme)
} else { // if there is no theme data in localStorage, set theme to dark mode
setTheme(themes.dark)
}
break;
}
}, [theme]);
return (
<ThemeContext.Provider value={{ theme: theme, changeTheme: changeTheme }}>
{props.children}
</ThemeContext.Provider>
);
}

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 with Redux: Child component does not rerender after props have changed (even though they are not shallow equal)

I'm building an app with React Native using Redux for the state management. I will post my code for all the involved components and the reducer down below, but since that is going to be much, let me describe the problem in a few sentences first.
I have an immutable reducer for my objects called 'waitercalls'. I have a screen (HomeScreen) that renders two components. Each component is a <FlatList /> of objects. The objects (waitercalls) are given to them via props by it's parent (HomeScreen). HomeScreen is connected to Redux via React-Redux' connect() and gets the objects ('waitercalls') via a selector created with Re-Select.
The left list's items can be pressed and upon press dispatch an event to the reducer. Here comes the problem. When the left list's item are pressed, the left list correctly updates (calls render()). The right list though does not re-render, even though it gets the same props.
Why does the left list rerender, but the right list not? The reducer is immutable, the selector is too and even the length of the list changes from one to zero which should eliminate the possibility of a shallow equal.
And now for the code:
waitercallsReducer:
import { createSelector } from "reselect";
const initialState = {};
const waitercallsReducer = (state = initialState, action) => {
if (action.payload && action.payload.entities && action.payload.entities.waitercalls) {
return {
...state,
...action.payload.entities.waitercalls
};
} else {
return state;
}
};
export default waitercallsReducer;
export const getAllWaitercallsNormalizedSelector = state => state.waitercalls;
export const getAllWaitercallsSelector = createSelector(
getAllWaitercallsNormalizedSelector,
waitercalls => Object.values(waitercalls)
);
export const getAllActiveWaitercallsSelector = createSelector(
getAllWaitercallsSelector,
waitercalls => waitercalls.filter(waitercall => !waitercall.done)
);
Action creators:
import { setValues } from "../core/core";
// feature name
export const WAITERCALLS = "[Waitercalls]";
// action creators
export const setValues = (values, type) => ({
type: `SET ${type}`,
payload: values,
meta: { feature: type }
});
export const setWaitercalls = waitercalls => setValues(waitercalls, WAITERCALLS);
HomeScreen:
import React, { Component } from "react";
import { View, TouchableOpacity } from "react-native";
import { SafeAreaView } from "react-navigation";
import { connect } from "react-redux";
import { Icon } from "react-native-elements";
import PropTypes from "prop-types";
// ... I've omitted all the imports so that it's shorter
export class HomeScreen extends Component {
// ... I've omitted navigationOptions and propTypes
render() {
const {
checkins,
customChoiceItems,
menuItemPrices,
menuItems,
orders,
pickedRestaurant,
tables,
waitercalls
} = this.props;
console.log("Rendering HomeScreen");
return (
<SafeAreaView style={styles.container}>
<View style={styles.activeOrders}>
<OrdersList
checkins={checkins}
customChoiceItems={customChoiceItems}
menuItemPrices={menuItemPrices}
menuItems={menuItems}
orders={orders}
restaurantSlug={pickedRestaurant.slug}
tables={tables}
waitercalls={waitercalls}
/>
</View>
<View style={styles.tableOvewView}>
<TableOverview
checkins={checkins}
orders={orders}
tables={tables}
waitercalls={waitercalls}
/>
</View>
</SafeAreaView>
);
}
}
const mapStateToProps = state => ({
checkins: getAllCheckinsSelector(state),
customChoiceItems: getAllCustomChoiceItemsNormalizedSelector(state),
menuItemPrices: getAllMenuItemPricesNormalizedSelector(state),
menuItems: getAllMenuItemsNormalizedSelector(state),
orders: getActiveOrdersSelector(state),
pickedRestaurant: getPickedRestaurantSelector(state),
tables: getAllTablesSelector(state),
waitercalls: getAllActiveWaitercallsSelector(state)
});
export default connect(mapStateToProps)(HomeScreen);
OrdersList (as you can see OrdersList also allows presses for orders, which displays the same erroneous behaviour of not having the TableOverView rerender), which is the left list with the clickable <ListItem />s.
import React, { PureComponent } from "react";
import { FlatList, Image, Text } from "react-native";
import { ListItem } from "react-native-elements";
import { connect } from "react-redux";
import PropTypes from "prop-types";
// ... omitted imports
export class OrdersList extends PureComponent {
// omitted propTypes
keyExtractor = item => item.uuid;
registerItem = item => {
// Remember the order status, in case the request fails.
const { restaurantSlug, setOrders } = this.props;
const itemStatus = item.orderStatus;
const data = {
restaurant_slug: restaurantSlug,
order_status: "registered",
order_uuid: item.uuid
};
setOrders({
entities: { orders: { [item.uuid]: { ...item, orderStatus: data.order_status } } }
});
postOrderStatusCreate(data)
.then(() => {})
.catch(err => {
// If the request fails, revert the order status change and display an alert!
alert(err);
setOrders({ entities: { orders: { [item.uuid]: { ...item, orderStatus: itemStatus } } } });
});
};
answerWaitercall = item => {
const { restaurantSlug, setWaitercalls } = this.props;
const data = {
done: true,
restaurant_slug: restaurantSlug
};
setWaitercalls({ entities: { waitercalls: { [item.uuid]: { ...item, done: true } } } });
putUpdateWaitercall(item.uuid, data)
.then(() => {})
.catch(err => {
alert(err);
setWaitercalls({ entities: { waitercalls: { [item.uuid]: { ...item, done: false } } } });
});
};
renderItem = ({ item }) => {
const { checkins, customChoiceItems, menuItemPrices, menuItems, tables } = this.props;
return item.menuItem ? (
<ListItem
title={`${item.amount}x ${menuItems[item.menuItem].name}`}
leftElement={
<Text style={styles.amount}>
{tables.find(table => table.checkins.includes(item.checkin)).tableNumber}
</Text>
}
rightTitle={`${
menuItemPrices[item.menuItemPrice].label
? menuItemPrices[item.menuItemPrice].label
: menuItemPrices[item.menuItemPrice].size
? menuItemPrices[item.menuItemPrice].size.size +
menuItemPrices[item.menuItemPrice].size.unit
: ""
}`}
subtitle={`${
item.customChoiceItems.length > 0
? item.customChoiceItems.reduce((acc, customChoiceItem, index, arr) => {
acc += customChoiceItems[customChoiceItem].name;
acc += index < arr.length - 1 || item.wish ? "\n" : "";
return acc;
}, "")
: null
}${item.wish ? "\n" + item.wish : ""}`}
onPress={() => this.registerItem(item)}
containerStyle={styles.alignTop}
bottomDivider={true}
/>
) : (
<ListItem
title={
item.waitercallType === "bill"
? SCREEN_TEXT_HOME_BILL_CALLED
: SCREEN_TEXT_HOME_SERVICE_ASKED
}
leftElement={
<Text style={styles.amount}>
{
tables.find(table =>
table.checkins.includes(
checkins.find(checkin => checkin.consumer === item.consumer).uuid
)
).tableNumber
}
</Text>
}
rightIcon={{
type: "ionicon",
name: item.waitercallType === "bill" ? "logo-euro" : "ios-help-circle-outline"
}}
onPress={() => this.answerWaitercall(item)}
bottomDivider={true}
/>
);
};
render() {
const { orders, waitercalls } = this.props;
return (
<FlatList
keyExtractor={this.keyExtractor}
data={[...orders, ...waitercalls]}
renderItem={this.renderItem}
// ... omitted ListHeader and ListEmpty properties
/>
);
}
}
export default connect(
null,
{ setOrders, setWaitercalls }
)(OrdersList);
TableOverview, which is the right <FlatList />:
import React, { Component } from "react";
import { FlatList } from "react-native";
import PropTypes from "prop-types";
// ... omitted imports
export class TableOverview extends Component {
// ... omitted propTypes
keyExtractor = item => item.uuid;
renderItem = ({ item }) => {
const { checkins, orders, waitercalls } = this.props;
if (item.invisible) return <Table table={item} />;
console.log("Rendering TableOverview");
return (
<Table
table={item}
hasActiveOrders={orders.some(order => item.userOrders.includes(order.uuid))}
billWanted={item.checkins.some(checkin =>
waitercalls.some(
waitercall =>
waitercall.waitercallType === "bill" &&
waitercall.consumer ===
checkins.find(checkinObj => checkinObj.uuid === checkin).consumer
)
)}
serviceWanted={item.checkins.some(checkin =>
waitercalls.some(
waitercall =>
waitercall.waitercallType === "waiter" &&
waitercall.consumer ===
checkins.find(checkinObj => checkinObj.uuid === checkin).consumer
)
)}
/>
);
};
formatData = (data, numColumns) => {
const numberOfFullRows = Math.floor(data.length / numColumns);
let numberOfElementsLastRow = data.length - numberOfFullRows * numColumns;
while (numberOfElementsLastRow !== numColumns && numberOfElementsLastRow !== 0) {
data.push({ uuid: `blank-${numberOfElementsLastRow}`, invisible: true });
numberOfElementsLastRow++;
}
return data;
};
render() {
const { tables } = this.props;
return (
<FlatList
style={styles.container}
keyExtractor={this.keyExtractor}
data={this.formatData(tables, NUM_COLUMNS)}
renderItem={this.renderItem}
numColumns={NUM_COLUMNS}
/>
);
}
}
export default TableOverview;
I found the solution!
The List was not rerendering, because the <FlatList /> only looked at the tables and not the waitercalls.
I had to add the following property to the <FlatList />:
extraData={[...checkins, ...orders, ...waitercalls]}

React Native global back handling

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>)
}
}

Resources