I'm using this module and the issue is that the Dialogue element that pops up, which is a Modal, has two TouchableOpacity buttons. After typing, when the keyboard is up, clicking the 'submit' TouchableOpacity will first clear/hide the keyboard, and only the second tap on the 'submit' TouchableOpacity will trigger the onPress event. What can I do as a workaround here? I've tried changing it to a Button from react-native and from react-native-elements but it behaves the same way.
Edit:
The component:
return (
<Modal
animationType="fade"
transparent={true}
visible={this.props.isDialogVisible}
onRequestClose={() => {
this.props.closeDialog();
this.setState({ inputModal: "" });
}}
>
<View style={[styles.container, { ...modalStyleProps }]}>
<TouchableOpacity
style={styles.container}
activeOpacity={1}
onPress={() => {
this.props.closeDialog();
this.setState({ inputModal: "", openning: true });
}}
>
<View style={[styles.modal_container, { ...dialogStyleProps }]}>
<View style={styles.modal_body}>
<Text style={styles.title_modal}>{title}</Text>
<Text
style={[
this.props.message ? styles.message_modal : { height: 0 }
]}
>
{this.props.message}
</Text>
<TextInput
style={styles.input_container}
autoCorrect={
textProps && textProps.autoCorrect == false ? false : true
}
autoCapitalize={
textProps && textProps.autoCapitalize
? textProps.autoCapitalize
: "none"
}
clearButtonMode={
textProps && textProps.clearButtonMode
? textProps.clearButtonMode
: "never"
}
clearTextOnFocus={
textProps && textProps.clearTextOnFocus == true
? textProps.clearTextOnFocus
: false
}
keyboardType={
textProps && textProps.keyboardType
? textProps.keyboardType
: "default"
}
autoFocus={true}
onKeyPress={() => this.setState({ openning: false })}
underlineColorAndroid="transparent"
placeholder={hintInput}
onChangeText={inputModal => {
if (this.props.setBackupCommentText) {
this.props.setBackupCommentText(inputModal);
}
this.setState({ inputModal });
}}
value={value || this.props.backupCommentText}
multiline={true}
/>
</View>
<View style={styles.btn_container}>
<TouchableOpacity
style={styles.touch_modal}
onPress={() => {
this.props.closeDialog();
this.setState({ inputModal: "", openning: true });
}}
>
<Text style={styles.btn_modal_left}>{cancelText}</Text>
</TouchableOpacity>
<View style={styles.divider_btn}></View>
<TouchableOpacity
style={styles.touch_modal}
onPress={() => {
if (
this.props.initValueTextInput &&
this.props.initValueTextInput.trim().length === 0
) {
Toast.show("Comment cannot be empty");
} else {
this.props.submitInput(value);
this.setState({ inputModal: "", openning: true });
if (this.props.setBackupCommentText) {
this.props.setBackupCommentText("");
}
}
}}
>
<Text style={styles.btn_modal_right}>{submitText}</Text>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
</View>
</Modal>
);
This is likely because a parent scrollview is intercepting the tap gesture. To solve this problem, find the nearest ScrollView or FlatList parent and add the keyboardShouldPersistTaps='handled' attribute. This will prevent the keyboard from auto dismissing on the tap (which consumes the tap). You may need to add Keyboard.dismiss() to get it to work as expected.
<ScrollView keyboardShouldPersistTaps='handled'>
...
</ScrollView>
For FlatList or ScrollView, you can use following props
keyboardShouldPersistTaps={true}
Add keyboardShouldPersistTaps to parent or nearest FlatList or ScrollView,
Determines when the keyboard should stay visible after a tap.
'never' tapping outside of the focused text input when the keyboard is up dismisses the keyboard. When this happens, children won't
receive the tap.
'always', the keyboard will not dismiss
automatically, and the scroll view will not catch taps, but children
of the scroll view can catch taps.
'handled', the keyboard will not
dismiss automatically when the tap was handled by children of the
scroll view (or captured by an ancestor).
false, deprecated, use 'never' instead
true, deprecated, use 'always' instead
reference: https://reactnative.dev/docs/scrollview#keyboardshouldpersisttaps
Just wrap your code inside from react-native-keyboard-aware-scroll-view library
<Modal>
<KeyboardAwareScrollView>
...
</KeyboardAwareScrollView>
</Modal>
and that's it.
Related
I am implementing a custom button that uses a <Pressable/> as it's base component.
//...
<Pressable
style={({ pressed }) => [
styles.button,
style,
// if button is disabled it gets grayed out
// if it's pressed, and user did not specify underlay color
// it uses default overlay color
// if it's not pressed, use either user-specified
// background color, or default
{
backgroundColor: isDisabled
? "#808080"
: pressed && underlayColor !== false
? underlayColor || styles.button.underlayColor
: style?.backgroundColor || styles.button.backgroundColor,
},
]}
//...
I feel like this is unreadable.
Furthermore, I have another custom button component that uses this component (custom-custom component, so to speak). If I pass an array as a style to it, the overlay feature won't work since it is expecting an object (style?.backgroundColor), not an array.
Here is the source code for both components:
Custom buttom:
const Button = ({
title,
containerStyle,
style,
onPress,
isDisabled,
textStyle,
icon,
iconContainerStyle,
underlayColor,
android_ripple,
}) => {
return (
<View style={[styles.container, containerStyle]}>
<Pressable
style={({ pressed }) => [
styles.button,
style,
{
backgroundColor: isDisabled
? "#808080"
: pressed && underlayColor !== false
? underlayColor || styles.button.underlayColor
: style?.backgroundColor || styles.button.backgroundColor,
},
]}
android_ripple={
android_ripple === true && { color: "rgba(0, 0, 0, 0.2)" }
}
disabled={isDisabled}
onPress={onPress}
>
<>
{/* show icon if specified */}
{icon ? (
<View style={[styles.icon, iconContainerStyle]}>{icon}</View>
) : null}
{/* show title if specified */}
{title && (
<Text
style={{
...styles.text,
...textStyle,
}}
>
{title}
</Text>
)}
</>
</Pressable>
</View>
);
};
export default Button;
Icon Button (custom-custom button):
export default function IconButton({
containerStyle,
style,
onPress,
isDisabled,
iconName,
iconType,
iconColor,
iconSize,
}) {
return (
<Button
isDisabled={isDisabled}
containerStyle={{ ...styles.container, ...containerStyle }}
/*[styles.button, style] will break the overlay color feature.. since in the custom <Button /> component, it is expecting an object, not an array*/
style={{ ...styles.button, ...style }}
onPress={onPress}
icon={
<Icon
name={iconName}
type={iconType}
color={iconColor}
size={iconSize || 35}
/>
}
underlayColor={false}
android_ripple={true}
/>
);
}
I want to be able to pass arrays and still be able to access the styles.backgroundColor. What would be the best of way of doing this?
Thanks in advance!
I have made a Home page in which there are three buttons in the header (like a tab navigator) I want something like on clicking each button a screen appears beneath the header, as shown in the image below:
Here's what I have tried:
constructor(props) {
super(props);
this.state = {
initialstate: 0, //Setting initial state for screens
};
}
render(){
return(
<View style={styles.container}>
<TouchableOpacity onPress={() => this.setState({ initialstate: 0})}>
<Image source={require('../../assets/add.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 1})}>
<Image source={require('../../assets/request.png')}
resizeMode="contain"/>
</TouchableOpacity>
<TouchableOpacity onPress={() => this.setState({ cardstate: 2})}>
<Image source={require('../../assets/send.png')}
resizeMode="contain"/>
</TouchableOpacity>
{this.state.initialstate == 0 ? ( <RequestComp/> ) : ( <TopUpComp/> ) }
//Over Here when I use the Third Screen like " : <SendComp/> " it gives me JSX error says "EXPECTED }"
</View>
Ciao, what you need seems more like a popup that appears from the bottom of the screen. I use react-native-popup-ui Toast component to do that.
Something like:
import { Root, Toast } from 'popup-ui'
...
<Root>
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<TouchableOpacity
onPress={() =>
Toast.show({
title: 'User created',
text: 'Your user was successfully created, use the app now.',
color: '#2ecc71'
})
}
>
<Text>Call Toast</Text>
</TouchableOpacity>
</View>
</Root>
And the result is:
Note: I have android and the Taost is overlapped by android navigation bar but on iOS you should see all the Toast component
you are using a ternary operator ( initial ? true : false ) which can work only for two components in your case.
Based on the code below, I would expect to see 'onDismiss!' when I swipe down on the modal view.
When I swipe down on the modal view it does not invoke the provided function. I can't find any other React Native users experiencing this problem. I am using React Native version 0.60.6.
Am I using Modal the wrong way or is this a bug?
<Modal
animationType="slide"
presentationStyle="pageSheet"
visible={showSetup}
hardwareAccelerated
onDismiss={() => alert('onDismiss!')}
onRequestClose={() => alert('onRequestClose!')}
>
<SetupView closeView={() => this.willClose()} />
</Modal>
This issue on the React Native issue tracker accurately describes this issue: https://github.com/facebook/react-native/issues/26892
I hope it's fixed soon!
Hey there you should try something like that
This is the code for the component rendered in my modal:
<TouchableWithoutFeedback
onPressOut={() => {
this.refs.view_ref.measure((fx, fy, width, height, px, py) => {
if (py === Dimensions.get('window').height) {
this.props.hide(false);
}
});
}}
>
<View style={styles.view} ref={'view_ref'}>
<View style={styles.nav}>
<Text style={styles.nav_title}>New Currency</Text>
<TouchableOpacity
onPress={() => {
this.props.hide(false);
}}
>
<Icon
style={styles.nav_close}
name={'close'}
size={30}
color={mode === 'dark' ? 'white' : 'black'}
/>
</TouchableOpacity>
</View>
</View>
</TouchableWithoutFeedback>
When wrapping the view you want to render in a TouchableWithoutFeedback, you get the "onPressOut" functionality. Then with this portion of code:
onPressOut={() => {
this.refs.view_ref.measure((fx, fy, width, height, px, py) => {
if (py === Dimensions.get('window').height) {
this.props.hide(false);
}
});
}}
you basically tell the view to listen on touch events happening at the top of the modal, thus it will be also executed when you swipe to dismiss
I was inspired by this answer:
https://github.com/facebook/react-native/issues/26892#issuecomment-605516625
Enjoy 😊
This is a bug with React Native, but I have found a workaround that works for me. Basically I have a TouchableWithoutFeedback fill the entire Modal. Then I use the onPressOut prop, which receives an event from which you can measure the locationY. What I have found is that if locationY < 0, the swipe was successful in dismissing the Modal, and in that case you can call setModalVisible(false). Then, next time you want to re-show the Modal, it will work. Hope it helps!
<Modal animationType="slide" presentationStyle="pageSheet" visible={ modal }>
<TouchableWithoutFeedback onPressOut={(e) => {
if (e.nativeEvent.locationY < 0) {
handleModal(false)
}}
>
<View style={ styles.modalOuter }>
<View style={ styles.modalInner }>
<Text>Hi from Modal</Text>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
// ...
const styles = StyleSheet.create({
modalInner: {
backgroundColor: "white",
position: "absolute",
bottom: 0,
width: "100%",
height: 400
},
modalOuter: {
backgroundColor: "white",
height: "100%"
}
});
I'm new to React Native and I have a simple app which opens a Webview when a button is pressed. If the navigation state changes, I want to close the Webview. I'm able to know when to close it but unable to find how.
The doc does not mention any function to do it. What's the solution for this?
version : react-native: 0.47.2
you can add it in Modal
_onNavigationStateChange (webViewState) {
this.hide()
}
show () {
this.setState({ modalVisible: true })
}
hide () {
this.setState({ modalVisible: false })
}
render () {
const { clientId, redirectUrl, scopes } = this.props
return (
<Modal
animationType={'slide'}
visible={this.state.modalVisible}
onRequestClose={this.hide.bind(this)}
transparent
>
<View style={styles.modalWarp}>
<View style={styles.contentWarp}>
<WebView
style={[{ flex: 1 }, this.props.styles]}
source={{ uri: `http://google.com` }}
scalesPageToFit
startInLoadingState
onNavigationStateChange={this._onNavigationStateChange.bind(this)}
onError={this._onNavigationStateChange.bind(this)}
/>
<TouchableOpacity onPress={this.hide.bind(this)} style={styles.btnStyle}>
<Text style={styles.closeStyle}>close</Text>
</TouchableOpacity>
</View>
</View>
</Modal >
)
}
for anyone who wants a custom close button or such, you could also do like this.
<SafeAreaView style={{ flex: 1 }}>
<TouchableOpacity onPress={closeWebView}>
<Text>x</Text>
</TouchableOpacity>
<WebView style={{ flex: 1 }} source={{ uri }} />
</SafeAreaView>
I currently have two buttons (No, Yes)( component imported from native-base package) that when pressed should update the state with either 0 or 1 respectively and also toggle between true or false to notify if this field has been filled (by default, neither of the two will be pressed, hence set to false).
I have a handleOnClick() function bound to the "No" button with a debugger to test if I actually do hit it, but once inside this function, I'm not sure how to grab any info for associated components (i.e. the "No" text within the Text component) so I can perform logic to check if "No" or "Yes" was pressed.
If this was done in pure React, I know I can access certain data attributes that I add to DOM elements or traverse the DOM, but I'm not sure how to do this in React Native or if I'm able to add custom props to a built in component that I can then access.
class Toggle extends Component {
constructor() {
super()
this.state = {
selectedOption: '',
isFilled: false
}
this.checkField = this.checkField.bind(this)
this.handleOnClick = this.handleOnClick.bind(this)
}
checkField() {
console.log(this)
// debugger
}
handleOnClick(ev) {
debugger
console.log("I was pressed")
}
render() {
const options = this.props.inputInfo.options //=> [0,1]
const optionLabels = this.props.inputInfo.options_labels_en //=>["No","Yes"]
return (
<View style={{flexDirection: 'row'}}>
<View style={styles.row} >
<Button light full onPress={this.handleOnClick}><Text>No</Text></Button>
</View>
<View style={styles.row} >
<Button success full><Text>Yes</Text></Button>
</View>
</View>
)
}
}
If you want to pass information into function, you can pass it when it is called. In your case, you can call your function from arrow function, like so:
<View style={{flexDirection: 'row'}}>
<View style={styles.row} >
<Button light full onPress={() => this.handleOnClick('No')}>
<Text>No</Text>
</Button>
</View>
<View style={styles.row} >
<Button success full><Text>Yes</Text></Button>
</View>
</View>
And in your function
handleOnClick(text) {
debugger
console.log(`${text} pressed`)
}
Have you tried:
render() {
const options = this.props.inputInfo.options //=> [0,1]
const optionLabels = this.props.inputInfo.options_labels_en //=>["No","Yes"]
return (
<View style={{flexDirection: 'row'}}>
<View style={styles.row} >
<Button light full onPress={() => this.handleOnClick('No')}><Text>No</Text></Button>
</View>
<View style={styles.row} >
<Button success full onPress={() => this.handleOnClick('Yes')}><Text>Yes</Text></Button>
</View>
</View>
)
}
and
handleOnClick(word) {
this.setState({ selectedOption: word, isFilled: true })
}