TouchableOpacity onPress is not working inside Flatlist but when I replaced onPress with onPressIn/onPressOut it is working fine, but in that case the reaction is too fast and having issue while scrolling. I don''t know what it is happening and haven't found any related issue. Below is my code:
renderItem = ({ item, index }: { item: any, index: number }) => {
const { type } = this.props;
const valueType = {
phone: item,
stage: item.title,
location: item.name
}
return (
<TouchableOpacity
onPressIn={() => this.onSelect(item, index)}
style={styles.modalListContainer}
>
<Icon name={icon[type]} height={20} width={20}/>
<Spacer width={10} />
<View style={styles.modelTextContainer}>
<Text style={styles.modelText}>{valueType[type]}</Text>
</View>
</TouchableOpacity>
)
}
<FlatList
data={item}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
ItemSeparatorComponent={() => <><Spacer height={10} /><View style={styles.modelTextDevider} /><Spacer height={10} /></>}
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.container}
/>
It is rendered inside a Modal using react-native-modals library. Any help would be appreciated.
Thank you.
react-native-modals, have a parent touchable component (PanResponder) which wraps your children's components. On some android devices, when you have a touchable component like a button, the touch event does not propagate down to child component instead capture by react-native-modals parent component.
The ideal solution should be absolute positioning your button but will break your UI and the modal will be useless.
There's an existing issue with this library repository.
https://github.com/jacklam718/react-native-modals/pull/210
but the solution provided is not 100% accurate for Android devices.
If you're using React Navigation, you already installed react-native-gesture-handler.
import TouchableOpacity from react-native-gesture-handler in place of `react-native. It should solve the issue for most devices.
Related
I am using Expo for my app. I have a horizontal Flatlist where I render my data react native paper's Card. I saw Card have onPress function. I used that to navigate the another page. But onPress function does not trigger on Android device. I know React native's touchable-opacity have positioning issue on Android. I tried hitSlop and inline styling zIndex but still does not work. I also wrap my card with react-native's touchable-opacity and play with positioning still did not help me, only it works when i used react-native-gesture-handler's touchable-opacity but then it does not work on IOS. Hope anyone can help me...
import React from 'react';
import { Card } from 'react-native-paper';
import { useNavigation } from '#react-navigation/native';
interface Iprops {
item: string;
}
export default function RenderCard({ item }: Iprops) {
const navigation = useNavigation();
return (
<Card
hitSlop={{ "bottom": 30, "top": 30, "right": 30, "left": 30 }}
onPress={() => {
navigation.navigate(`detail`, { // THIS DOES NOT TRIGGER ON ANDROID
"id": `${item.pk}`
});
}}
style={{ "marginBottom": 20 }}>
<Card.Cover
source={{ "uri": `${item.img_url}` }} />
<Card.Actions>
<Card.Title title={item.name} subtitle="Card Subtitle" />
</Card.Actions>
</Card>
);
}
I've noticed that using onPressIn or onPressOut does work on Android within an absolute positioned flatlist, but onPress does not work. I hope this might be of help to someone out there looking for an answer.
You'll need to use the TouchableOpacity element from react-native-gesture-handler.
Like you told, you should add touchable opacity to the element you are rendering on the card. TouchableOpacity can be tricky. So, first give it a styling of borderWidth:1 and borderColor to see the actual touchable area on the screen. Then you start to bring them together with the icon or the image or whatever you are rendering. TouchableOpacity works but the positioning can be tricky. You have to understand it to use it better. Think touchableOpacity as a view with borders then it'll be easier to grasp. Also, if you dont give touchableOpacity an absolute position in the styling it will be out of the screen somewhere, I was never be able to bring it to somewhere that I can see to position it. So you can add 'position' as well.
I gave up on positioning and render my component's based on Platform.
Platform.OS === `ios` ?
<Card
onPress={() => {
navigation.navigate(`detail`, {
"id": `${item.pk}`
});
}}
style={{ "marginBottom": 20 }}>
<Card.Cover source={{ "uri": `${item.img_url}` }} />
<Card.Actions>
<Card.Title title={item.name} subtitle="Card Subtitle" right={Beer} />
</Card.Actions>
</Card> :
<TouchableOpacity
onPress={() => {
navigation.navigate(`detail`, {
"id": `${item.pk}`
});
}}
>
<Card style={{ "marginBottom": 20 }}>
<Card.Cover source={{ "uri": `${item.img_url}` }} />
<Card.Actions>
<Card.Title title={item.name} subtitle="Card Subtitle" right={Beer} />
</Card.Actions>
</Card>
</TouchableOpacity>;
I am trying to build an auth flow that has a welcome page and then login or signup depending on the user's case.
It is built inside a stack navigator. The first screen has no header but then login and signup do via their screen options.
// AuthNavigator
const AuthStackNavigator = createStackNavigator();
export const AuthNavigator = () => {
return (
<AuthStackNavigator.Navigator
initialRouteName={WELCOME_PAGE.id}
screenOptions={{
headerShown: false,
headerTintColor: colors.primary,
}}
lazy>
<AuthStackNavigator.Screen
name={WELCOME_PAGE.id}
component={WELCOME_PAGE.component}
/>
<AuthStackNavigator.Screen
name={LOGIN_PAGE.id}
component={LOGIN_PAGE.component}
options={LOGIN_PAGE.options}
/>
<AuthStackNavigator.Screen
name={SIGN_UP_PAGE.id}
component={SIGN_UP_PAGE.component}
options={LOGIN_PAGE.options}
/>
</AuthStackNavigator.Navigator>
);
};
This flow, is nested inside a tabNavigator:
// AuthTabNavigator
const AuthTabNavigator =
Platform.OS === 'android'
? createMaterialBottomTabNavigator()
: createBottomTabNavigator();
const RootNavigator = () => {
return (
<AuthTabNavigator.Navigator
activeColor={colors.primary}
inactiveColor="grey"
barStyle={materialBottomNavBackgroundColor}
tabBarOptions={defaultTabNavOptions}>
<AuthTabNavigator.Screen
name={WELCOME_PAGE.id}
component={AuthNavigator}
options={WELCOME_PAGE.options}
/>
</AuthTabNavigator.Navigator>
);
};
export default RootNavigator;
On iOS, things work fine but on Android, It has weird behaviour. When pressing on the input field, the keyboard pushes the bottom bar causing a clunky effect on press but also on dismiss of the field. As if it were recalculating the height every time and repositioning the layout.
In order to make sure it's not coming from my code, I tested again with the snippet from the React Native documentation
I get the following (these are on the Android emulator but I get the same result on my OnePlus android phone)
base version: https://recordit.co/tcEwDbo1oT
no header version: https://recordit.co/O4lZ9G83vg
not nested in a tab navigator: https://recordit.co/uh7mOGlKdk
The only version that works is the one when not nested in the tab navigator so I guess the issue comes from there.
I checked a few solutions but none worked:
Android manifest
Issue coming from the header
Another
workaround because of header
I am using React Native CLI:
"react-native": "0.62.2",
"#react-navigation/bottom-tabs": "^5.2.8",
"#react-navigation/drawer": "^5.7.2",
"#react-navigation/material-bottom-tabs": "^5.1.10",
"#react-navigation/native": "^5.1.7",
"#react-navigation/stack": "^5.2.17",
Let me know if you encountered the same issue and found a way to fix it. Thanks in advance.
EDIT: SOLUTION I WENT WITH
On top of the "custom" solution I posted in the comments, after opening an issue on React Navigation repo and checking closed issues, I found that on Android, you might have 2 options:
Either your invert the nesting, see here
Or you can set the behaviour of the keyboardAvoidingView to position on Android and provide a custom Tabbar
https://github.com/react-navigation/react-navigation/issues/7359#issuecomment-545842090
https://stackoverflow.com/a/51169574/11287266
I ended up going with the latter:
const TabBarComponent = props => {
return (
<View collapsable={false}>
<BottomTabBar {...props} />
</View>
);
};
// AuthTabNavigator
const AuthTabNavigator = createBottomTabNavigator();
const RootNavigator = () => {
return (
<AuthTabNavigator.Navigator
activeColor={colors.primary}
inactiveColor="grey"
barStyle={materialBottomNavBackgroundColor}
tabBarOptions={defaultTabNavOptions}
tabBar={props => <TabBarComponent {...props} />}>
<AuthTabNavigator.Screen
name={WELCOME_PAGE.id}
component={AuthNavigator}
options={WELCOME_PAGE.options}
/>
</AuthTabNavigator.Navigator>
);
};
export default RootNavigator;
<KeyboardAvoidingView
contentContainerStyle={{ flexGrow: 1 }}
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'position'}
keyboardVerticalOffset={Platform.OS === 'android' ? -50 : 25}>
I have my code setup as such:
export default class HomeScreen extends Component {
constructor() {
super();
}
componentDidMount () {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow);
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
}
_keyboardDidShow = () => {
console.log('keyboard did show')
}
_keyboardDidHide = () => {
console.log('keyboard did hide')
}
render() {
return (
<Container styles={styles.container} >
<Content styles={styles.content} contentContainerStyle={marginLeft=this.state.marginLeft}>
<Image
style={styles.bgImg}
source={Images.bgImg}
>
</Image>
<Image
style={styles.logo}
source={Images.logo}
>
</Image>
<Text style={styles.slogan}>This is the title</Text>
<Form style={styles.search_form}>
<Item rounded floatingLabel style={styles.search}>
<Label style={styles.search_label}>Where are you headed?</Label>
<Input style={styles.search_input} />
<Button full rounded style={styles.search_btn}>
<Icon name="search"></Icon>
</Button>
</Item>
</Form>
</Content>
</Container>
);
}
}
I want basically the Content component of native-base to avoid the keyboard. I have my logo at the top, the slogan below it and the form at the bottom of the screen by giving some absolute positioning. At this point, the content component moves way up the screen which I don't want. What I want is the logo and the slogan staying right at the top of the screen but the form which is at the bottom of the page; to move up.
Here's what I've researched so far:
Found out that there is actually a component from react native called KeyboardAvoidingView and I played around with it but keeping the Logo, background image and the rest of the content inside the KeyboardAvoidingView made all the content not show in the screen.
Later I found out that the native base component 'Content' itself extends KeyboardAvoidingView, so there was no need to use it in the first place. But I don't think KeyboardAvoidingView is working with my versions of react native and native base.
So at last, I decided that this was a bug and I would use the Keyboard module of the react native instead to do some custom work, which is where I'm at right now, code-wise.
The Question
The console logging inside _keyboardDidShow and _keyboardDidHide are working, which means now I just need to know how to change the style of a component on keyboardDidShow and keyboardDidHide. Any help is appreciated, of course!
I'm really new to react native so any suggestions to better improve my workflow will be taken seriously.
I have already stumbled across the same problem! I'd recommend you save the keyboard width on state, with, for example:
keyboardDidShow = e => this.setState(p => ({ ...p, height: e.endCoordinates.height })
keyboardDidHide = () => this.setState(p => ({ ...p, height: 0 })
then, having this height, you can make your UI depend on that value. After you make sure that is working, in order for it not to jump between positions, use Animated to have a seamless transition between the positions. Hope this helps!
I'm trying to set the refresh indicator of flat list in react native but don't know how to do it. List View has this prop :
refreshControl={<RefreshControl
colors={["#9Bd35A", "#689F38"]}
refreshing={this.props.refreshing}
onRefresh={this._onRefresh.bind(this)}
/>
}
But Flat List has only these :
refreshing={this.props.loading}
onRefresh={this._onRefresh.bind(this)}
I found the solution! It might be the dummy but FlatList also has a prop called refreshControl like ListView but I just didn't test it! Just like this:
<FlatList
refreshControl={<RefreshControl
colors={["#9Bd35A", "#689F38"]}
refreshing={this.props.refreshing}
onRefresh={this._onRefresh.bind(this)} />}
/>
refreshControl={
<RefreshControl
isRefreshing={isRefreshing}
onRefresh={loadProducts}
colors={[Colors.GreenLight]} // for android
tintColor={Colors.GreenLight} // for ios
/>
}
this covers both ios and android
In official doc for RefreshControl component it is stated as - This component is used inside a ScrollView or ListView to add pull to refresh functionality. When the ScrollView is at scrollY: 0, swiping down triggers an onRefresh event
So for FlatList don't use it directly because they provides the two special props named as refreshing and onRefresh - on which the standard RefreshControl will be added for "Pull to Refresh" functionality. Make sure to also set the refreshing prop.
USAGE -
Step 1:
const wait = (timeout) => { // Defined the timeout function for testing purpose
return new Promise(resolve => setTimeout(resolve, timeout));
}
const [isRefreshing, setIsRefreshing] = useState(false);
const onRefresh = useCallback(() => {
setIsRefreshing(true);
wait(2000).then(() => setIsRefreshing(false));
}, []);
Step 2:
Now use component as
<FlatList
style={styles.flatListStyle}
data={inProgressProjects.current}
keyExtractor={item => item._id}
renderItem={renderItem}
showsVerticalScrollIndicator={false}
refreshing={isRefreshing} // Added pull to refesh state
onRefresh={onRefresh} // Added pull to refresh control
/>
For more information refer here -
https://reactnative.dev/docs/refreshcontrol
https://reactnative.dev/docs/flatlist#onrefresh
Hope this will help you or somebody else.
Thanks!
You can pass in the renderScrollComponent to your FlatList component with the same RefreshControl component you have showed above. I have created a expo snack for this: https://snack.expo.io/rJ7a6BCvW
The FlatList is using VirtualizedList within itself, and for VirtualizedList component, it takes a renderScrollComponent: https://facebook.github.io/react-native/docs/virtualizedlist.html#renderscrollcomponent
I was passing bounces={false} to my Flatlist which was causing problem. This will not allow you to refresh. Remove it if you want to use the above one solution mentioned. Thanks
I am running react native 0.24.1 and I am experiencing an issue with the <TouchableOpacity> component when it is placed inside an <ScrollView>.
Its onPress events fire fine but there is a special case when they do not.
If along with the <TouchableOpacity> component you have a <TextInput>, and the current focus is on the <TextInput> box, then you may click on the <TouchableOpacity> and you will see its onPress event WILL NOT be fired.
At least the first time you do it. Once the focus is NOT on the <TextInput> anymore, you can now press on the <TouchableOpacity> component and its onPress event will fire just fine.
Note that if the <TouchableOpacity> component is placed inside a <View> instead of an <ScrollView> everything works as expected and the above issue does not apply.
Here is some code to demonstrate the problem:
const React = require('react-native');
const {
Component,
Dimensions,
View,
ScrollView,
Text,
TextInput,
TouchableOpacity,
} = React;
// ----------------------------------------------------------------------------
class TouchableOpacityTest extends Component {
constructor(props, context) {
super(props, context);
this.state = {count_onPress:0,count_onPressIn:0,count_onPressOut:0,count_onLongPress:0};
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
onPressEvent(what,e) {
console.log('what:',what);
let newState = {};
newState['count_'+what] = ++this.state['count_'+what];
this.setState(newState);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
render() {
let touchableProps = {
onPress: this.onPressEvent.bind(this,'onPress'),
onPressIn: this.onPressEvent.bind(this,'onPressIn'),
onPressOut: this.onPressEvent.bind(this,'onPressOut'),
onLongPress: this.onPressEvent.bind(this,'onLongPress'),
}
return (
<View style={{flex:1,flexDirection:'column',justifyContent:'flex-start',alignItems:'center',backgroundColor:'blue'}} >
<ScrollView style={{width:Dimensions.get('window').width*0.9,backgroundColor:'red'}}>
<TextInput style={{backgroundColor:'rgb(200,200,200)',marginTop:14}}
placeholder="Focus on me,hide keyboard,and click on text below"
autoCorrect={false}
/>
<TouchableOpacity {...touchableProps} >
<Text style={{fontSize:20,backgroundColor:'pink',marginTop:14}}>
Click on me!{"\n"}
onPress:{this.state.count_onPress}{"\n"}
onPressIn:{this.state.count_onPressIn}{"\n"}
onPressOut:{this.state.count_onPressOut}{"\n"}
onLongPress:{this.state.count_onLongPress}{"\n"}
</Text>
</TouchableOpacity>
</ScrollView>
</View>
);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
// ----------------------------------------------------------------------------
AppRegistry.registerComponent('react_native_app1', () => TouchableOpacityTest);
You may replace the <ScrollView> with a <View> component on the above code and you will see that onPress event fires every time, even when the focus is on the <TextView>
NOTE: I am working on Android. I have no idea if this happens also on iOS.
NOTE 2: According to Aakash Sigdel, this is indeed happening on iOS too.
Set keyboardShouldPersistTaps={true} on your ScrollView.
Duplicate answer here: https://stackoverflow.com/a/34290788/29493
UPDATE: As Hossein writes in his answer, true|false has been deprecated in newer versions in favor of always|never|handled.
Set keyboardShouldPersistTaps='always' to your ScrollView props.
React Native Documentation:
'never' (the default), 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 a children, (or captured by an ancestor).
false, deprecated, use 'never' instead.
true, deprecated, use 'always' instead.
In my case, I was using alignItems:'baseline', when I switched to alignItems:'center', it started working smoothly. Don't know why