I have a flatlist in my application and I want to enable a user to mark a particular list item as selected on Long press and provide a delete button to remove multiple items in one go. These are the behaviors I expect.
If no items in the flatlist are selected, pressing an item opens a new screen with item details and back button takes you back to the flatlist.
If no items are selected, long pressing an item marks it as selected. Every item pressed after any particular item is selected is marked as selected instead of opening the details screen.
Items which are already selected and then pressed become unselected.
If any number of items are selected, a delete button is rendered and pressing the back button unselects all items.
I have been able to achieve most of the first three behaviors but I am completly lost with the Back Handler. Here is my component with only the relevant code for brevity. Only the state array which contains the items selected for deletion and the listItem which is used as the RenderItem prop for the flatlist is shown.
const Home = (props) => {
const [deleteItems, setDeleteItems] = useState([]);
const renderItem = ({ item }) => {
let bb_OR_ub = item.useBy ? 'Use By ' : 'Best Before '
let exp_check = CheckExp(item, 1);
let listStyle = {};
if (exp_check === -1)
listStyle = { backgroundColor: '#ff9ea5' }
else if (exp_check === 0)
listStyle = { backgroundColor: '#fff185' }
if (deleteItems.indexOf(item.name) != -1) {
listStyle = { opacity: 0.3 }
}
return (
<ListItem
containerStyle={listStyle}
badge={
exp_check !== 1 ?
exp_check === -1 ? { status: 'error', value: `!` } : {
status: 'warning'
} : null
}
title={item.name}
subtitle={bb_OR_ub + item.date}
bottomDivider
leftAvatar={{ source: require('../shared/cexp.png'), imageProps: { resizeMode: 'contain' } }}
onPress={() => {
if (deleteItems.length == 0)
navigate('ExpiryDetails', { item })
else {
setDeleteItems([...deleteItems, item.name])
}
}}
onLongPress={() => {
if (deleteItems.indexOf(item.name) == -1 || deleteItems.length == 0) {
setDeleteItems([...deleteItems, item.name])
}
else {
setDeleteItems(deleteItems.filter(el => el != item.name))
}
}} />
);
}
The BackHandler provided by react-native allows you to subscribe to back button being pressed. The callback provided to backhandler can provide either true (when the default behavior should not trigger) or false (when the default behavior is allowed to continue).
For your case, we want to have custom behavior on back when there are items selected, at that moment we want to unselect all items.
I adjusted your code to introdcue the BackHandler and unselect any items on back being pressed
const Home = (props) => {
const [deleteItems, setDeleteItems] = useState([]);
// Subscribe to BackHandler once the component is mounted
// or when deletedItems changes
useEffect(() => {
const handler = BackHandler.addEventListener('hardwareBackPress', () => {
// If no deleted items: we return false
if (!deletedItems.length) {
return false;
}
// clear the selected items, and indicate that the back if handled
setDeletedItems([]);
return true;
});
// unsubscribe when component unmounts
return () => {
handler.remove();
};
}, [deletedItems]);
const renderItem = ({ item }) => {
let bb_OR_ub = item.useBy ? 'Use By ' : 'Best Before '
let exp_check = CheckExp(item, 1);
let listStyle = {};
if (exp_check === -1)
listStyle = { backgroundColor: '#ff9ea5' }
else if (exp_check === 0)
listStyle = { backgroundColor: '#fff185' }
if (deleteItems.indexOf(item.name) != -1) {
listStyle = { opacity: 0.3 }
}
return (
<ListItem
containerStyle={listStyle}
badge={
exp_check !== 1 ?
exp_check === -1 ? { status: 'error', value: `!` } : {
status: 'warning'
} : null
}
title={item.name}
subtitle={bb_OR_ub + item.date}
bottomDivider
leftAvatar={{ source: require('../shared/cexp.png'), imageProps: { resizeMode: 'contain' } }}
onPress={() => {
if (deleteItems.length == 0)
navigate('ExpiryDetails', { item })
else {
setDeleteItems([...deleteItems, item.name])
}
}}
onLongPress={() => {
if (deleteItems.indexOf(item.name) == -1 || deleteItems.length == 0) {
setDeleteItems([...deleteItems, item.name])
}
else {
setDeleteItems(deleteItems.filter(el => el != item.name))
}
}} />
);
}
Related
Here is My Code
<FlatList
refreshControl={
<RefreshControl
enabled={true}
refreshing={loader}
onRefresh={() => getLockerHistory(1)}
tintColor={ThemeColors.primary}
/>
}
ListEmptyComponent={noDataMessage()}
onScroll={(e) => {
if (Platform.OS == 'ios') {
return;
}
let paddingToBottom = 20;
paddingToBottom += e.nativeEvent.layoutMeasurement.height;
if (e.nativeEvent.contentOffset.y >= e.nativeEvent.contentSize.height - paddingToBottom) {
getNextRecordsPage();
}
}}
ListFooterComponent={() => {
return (
<ActivityIndicator color={ThemeColors.black} animating={footerLoader} />
);
}}
ListFooterComponentStyle={footerLoader ? { marginVertical: 20 } : {}}
ListFooterComponentStyle={{ paddingVertical: 20 }}
onEndReached={() => {
if (Platform.OS == 'ios') {
getNextRecordsPage()
}
}}
onEndReachedThreshold={Platform.OS == 'ios' ? 0 : null}
keyExtractor={(item, index) => item.lockerCode + '' + index}
data={lockers}
renderItem={(itemData) => {
return renderItem(itemData.item, props.navigation);
}}
/>
When I have more then 5 records which means there is not empty space left on screen then the refresh control won't work. It only works the seperator space between cells.
And my cell is made up of plain views nothing fancy or touchable.
Note: I tried to debug it with empty view but seems like pull/drag to refresh is not being listen by Flat List.
Any help?
Below code is working for me to achieve Refresh Control
import { View, Text, RefreshControl, FlatList } from 'react-native'
<FlatList
testID="DiscussionView"
initialNumToRender={5}
maxToRenderPerBatch={5}
windowSize={11}
data={posts}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={this.onRefresh} />}
keyExtractor={this.keyExtractor}
refreshing={refreshing}
renderItem={this.renderPostItem}
onEndReached={this.onEndReached}
scrollEnabled={scrollEnabled}
onEndReachedThreshold={0.3}
contentContainerStyle
listKey="DiscussionViewList"
onMomentumScrollBegin={() => {
this.onEndReachedCalledDuringMomentum = false
}}
/>
onRefresh = () => {
this.setState(
{ refreshing: true },
// Do what you want,
)
}
keyExtractor = (item) => String(item.id)
renderPostItem = ({ item }) => {
// Render Item here
}
onEndReached = () => {
if (!this.onEndReachedCalledDuringMomentum) {
// Call API again if needed by use case
this.onEndReachedCalledDuringMomentum = true
}
}
I have an application that you can log in to by phone number
After entering the phone number I receive an SMS code
A new screen opens where I can enter this code
When I enter the code, I get information that the code is expired
Sign: First screen
onSignIn() {
const {code, phoneNumber} = this.state;
const newNumber = '+' + code + phoneNumber;
if (newNumber.length > 10) {
firebase
.auth()
.signInWithPhoneNumber(newNumber)
.then(confirmResult => {
this.setState({result: confirmResult});
const navigateAction = NavigationActions.navigate({
routeName: 'SecurityCode',
params: {phoneAuthResponse: confirmResult},
});
this.props.navigation.dispatch(navigateAction);
})
.catch(error => {
if (error.message === 'TOO SHORT') {
alert('Please enter a valid phone number');
} else {
alert(error.message);
}
});
} else {
alert('Please Enter Your Number');
}
}
Confirm: Second screen
onConfirmCode() {
const {securityCode} = this.state;
if (securityCode.length > 5) {
this.props.navigation.state.params.phoneAuthResponse
.confirm(securityCode)
.then(async user => {
const ref = firebase.database().ref(`users/${user.uid}`);
ref.once('value', async snapshot => {
let data = snapshot.val();
if (!data) {
this.props.navigation.navigate('CreateProfile', {
user: {uid: user.uid, phone_number: user.phoneNumber},
});
} else {
this.props.reduxLoginUser(data);
this.props.navigation.navigate('InviteContacts');
}
});
})
.catch(error => console.warn(error.message));
} else {
alert('Please enter the 6 digit code');
}
}
What is done wrong?
Check if the user has been created (You can do this on the Firebase project page)
If it is created then there is another problem to be solved
You must catch that the user is created and go to the screen after logging in
I have an ionic 3 application and I modified the functionality of the hardware back button. It works on pages but it cannot determine whether overlay views like modals and alert dialog boxes are present or not.
Here is my code
this.platform.registerBackButtonAction(() => {
let nav = app._appRoot._getActivePortal() || app.getActiveNav();
let activeView = nav.getActive().instance;
if (activeView != null) {
if (nav.canGoBack()) {
if (activeView instanceof MultiRegistrationOne || activeView instanceof MultiRegistrationTwo || activeView instanceof MultiRegistrationThree) {
// do something
} else {
nav.pop();
}
} else if (activeView.isOverlay) {
activeView.dismiss();
} else {
let alert = this.alertCtrl.create({
title: 'Ionic App',
message: 'Do you want to close the app?',
buttons: [{
text: 'Cancel',
role: 'cancel',
handler: () => {
console.log('Application exit prevented!');
}
},
{
text: 'Close',
handler: () => {
this.platform.exitApp();
}
}]
});
alert.present();
}
}
});
I hope someone can help me with this. Thank you in advance 😊
declare a variable :viewController:ViewController
then in your page or app.components.ts , modify your back button handle to be like the
this.platform.registerBackButtonAction(() => {
try{
this.viewController.dismiss()
}
catch(e){
console.log("error");
}
});
I solved it using MD. Riyas' answer here: Solution
LoginScreen.js
this.props.navigator.push({
screen: "auxxa.LandingScreen",
passProps: { login: true },
overrideBackPress: true,
navigatorStyle: {
navBarHidden: true
}
});
LandingScreen.js
constructor(props) {
super(props);
this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
// this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this));
this.state = {
size: { width, height },
tileData: null,
isLoading: true,
user_id: null,
refetching: false,
access_token: null
};
}
componentWillMount() {
BackHandler.addEventListener(
"hardwareBackPress",
this.handleBackButtonClick
);
}
handleBackButtonClick() {
console.log("check login " + this.props.login);
if (this.backPressed && this.backPressed > 0) {
if (this.props.login) {
console.log("login");
RNExitApp.exitApp();
} else {
console.log("root");
this.props.navigator.popToRoot({ animated: false });
return false;
}
}
this.backPressed = 1;
this.props.navigator.showSnackbar({
text: "Press one more time to exit",
duration: "long"
});
return true;
}
componentDidMount() {
BackHandler.removeEventListener(
"hardwareBackPress",
this.handleBackButtonClick
);
}
I used react-native-navigation from Wix for my app nevigation purpose.Here I have attached login screen and landing screen.after successful login app navigate to landing screen.after that I click back button It will return to login screen.I need to avoid that.How can I do that thing? I tried to exit from the app.But it also not working properly.
Please help me if some one know this.Thanks in advanced.
Use this call in handleBackButtonClick function and why are you removing the listener in componentDidMount ?
this.props.navigator.resetTo({ screen: 'example.ScreenThree'})
.
Uncomment this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent.bind(this)); on your constructor to listen to navigation events
and add the navigationEvent listener method
onNavigatorEvent(event: NavigatorEvent) {
if (event.type === 'NavBarButtonPress') {
if (event.id === 'skill_information') {
// Add here whatever you would like to do (this.handleBackButtonClick() for example)
}
}
Hi, Thanks in advance, am using the Double click Component and it
works well for double click event. But I need to get an action when
user perform a single click. What the work around for this issue.
<DoubleClick onClick={(e) => this.hClick(value,e)}>
<View>
<Text>
{value.item}
</Text>
</View>
</DoubleClick>
I wrote a component.
// #flow
import * as React from 'react';
import { TouchableOpacity } from 'react-native';
import type { PressEvent } from 'react-native/Libraries/Types/CoreEventTypes';
type Props = {
children?: any,
onSingleTap: (event: PressEvent) => void,
onDoubleTap: (event: PressEvent) => void,
};
const MAX_DOUBLE_TOUCH_DISTANCE = 20;
const MAX_DOUBLE_TOUCH_DELAY_TIME = 250;
class SingleDoubleTap extends React.Component<Props> {
_timer: TimeoutID;
_previousPressEvent: ?PressEvent;
onPress = (event: PressEvent) => {
if (this._previousPressEvent) {
this.onReceiveSecondEvent(event);
} else {
this.onReceiveFirstEvent(event);
}
};
onReceiveFirstEvent = (event: PressEvent) => {
this._timer = setTimeout(() => {
this.props.onSingleTap(event);
this._previousPressEvent = null;
}, MAX_DOUBLE_TOUCH_DELAY_TIME);
this._previousPressEvent = event;
};
onReceiveSecondEvent = (event: PressEvent) => {
if (this._isDoubleTap(event)) {
this.props.onDoubleTap(event);
} else {
this.props.onSingleTap(event);
}
this._timer && clearTimeout(this._timer);
this._previousPressEvent = null;
};
_distanceBetweenTouches = (
touch1: PressEvent,
touch2: PressEvent
): number => {
const disX = touch1.nativeEvent.locationX - touch2.nativeEvent.locationX;
const disY = touch1.nativeEvent.locationY - touch2.nativeEvent.locationY;
return Math.sqrt(Math.pow(disX, 2) + Math.pow(disY, 2));
};
_isDoubleTap = (currentEvent: PressEvent) => {
if (!this._previousPressEvent) {
return false;
}
const distance = this._distanceBetweenTouches(
currentEvent,
this._previousPressEvent
);
// $FlowFixMe
const { nativeEvent } = this._previousPressEvent;
const delay = currentEvent.nativeEvent.timestamp - nativeEvent.timestamp;
return (
distance < MAX_DOUBLE_TOUCH_DISTANCE &&
delay < MAX_DOUBLE_TOUCH_DELAY_TIME
);
};
componentWillUnmount = () => {
this._timer && clearTimeout(this._timer);
};
render() {
return (
<TouchableOpacity onPress={this.onPress}>
{this.props.children}
</TouchableOpacity>
);
}
}
export default SingleDoubleTap;
How to use it?
<SingleDoubleTap
onSingleTap={this._onSingleTap}
onDoubleTap={this._onDoubleTap}>
..... // other components
</SingleDoubleTap>
The key thing is you should wait for the DoubleTap event failed to recognize the touch event as OneTap.
Remove the double click and use touchable component. pass the click to function and find whether its single or double click using timer delay