React native gesture passes through front view to the view behind - javascript

I am trying to make a react native app in which I have two views. One is above the behind (absolute) view.
I am using onStartShouldSetResponder to detect click outside the front view and I want to hide the login state.
But the gesture passes through front View to the view behind.
It means the login_component(below) click is also passed to login_container(below) and hides the view.
Here is my code,
<View onStartShouldSetResponder={() => true}
onResponderRelease={() => this.setState({ showLogin: false })}
style={styles.login_container}
>
<View onStartShouldSetResponder={() => false}
style={styles.login_component}
>
<Text>Join a room</Text>
</View>
</View>
Here is my styling code,
const styles = StyleSheet.create({
login_component: {
justifyContent: "center",
alignItems: "center",
height: 200,
width: 300,
backgroundColor: "#F5FCFF",
margin: "auto",
borderRadius: 15,
},
login_container: {
position: "absolute",
height: "100%",
width: "100%",
backgroundColor: "rgba(0,0,0,0.7)",
justifyContent: "center",
alignItems: "center",
},
});
Any idea how to fix this?
Here is a preview of what I am doing - https://i.imgur.com/KAhFAXX.png
Any alternate solution is also welcome.
Thank you

You can try setting a high "zIndex" CSS property for the JSX element that you want to be able to click. A higher zIndex will make sure that element is positioned above the one with the lower zIndex.
In your case, you want your "login_component" to be on top of "login_container". So your JSX can be modified as follows (not tested):
<View style={{...styles.login_container, zIndex: 99}}> // Lower zIndex (Below)
<View style={{...styles.login_component, zIndex: 999}}> // Higher zIndex (Above)
<Text>Join a room</Text>
</View>
</View>
Usually you wouldn't have to worry about setting zIndex yourself, but I'm guessing the 'position: "absolute"' property has affected the default behaviour.

Finally I got the solution it was simple just to use stopPropagation on the login_component.
Final code,
<View onStartShouldSetResponder={() => true}
onResponderRelease={() => this.setState({ showLogin: false })}
style={styles.login_container}
>
<View onStartShouldSetResponder={(e) => e.stopPropagation()}
style={styles.login_component}
>
<Text>Join a room</Text>
</View>
</View>

Related

React Native: Why my text breaks like this and why is it not respecting the container margin?

I want to display multiple elements next to each other in React Native (with a flex wrap if there are too many elements or if the elements are too long). Each element consists of an icon and a text. If the text is too long, the text should break into the next line.
I tried different things with flexGrow and flexShrink but nothing had the desired effect. Here is my code (expo snack):
export default function App() {
function iconWithText(text) {
return (
<View style={styles.iconWithTextWrapper}>
<View style={{paddingRight: 10, backgroundColor: 'grey'}}>
<Text>Icon</Text>
</View>
<View style={{backgroundColor: 'yellow'}}>
<Text>{text}</Text>
</View>
</View>
)
}
return (
<View style={styles.container}>
{iconWithText('short text')}
{iconWithText('short text')}
{iconWithText('this is a long, long, long text that does break very ugly')}
</View>
);
}
const styles = StyleSheet.create({
container: {
marginTop: 50,
marginHorizontal: 50,
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'flex-start',
},
iconWithTextWrapper: {
flexDirection: 'row',
margin: 10,
}
});
Here is the corresponding result - as you can see the margin of the whole container is not respected and the text does not break very well:
Why is the right margin of the container not respected, why does the text break like this and how can I fix this?
Try adding a flex:1 to your text style.
edit:
I played around in your snack and came up with this:
https://snack.expo.io/#moistbobo/iconwithtext
Please see if this fits your use case.

React native KeyboardAvoidingView not moving correctly

I have a TextInput that when pressed gets covered by the keyboard. So I wrapped it in a KeyboardAvoidingView. But regardless of the behavior that I set for this view, the TextInput won't move above the keyboard. Using position as the behavior moves the TextInput but only half way above the keyboard, while the other two don't seem to work at all.
I also tried wrapping my entire component with a KeyboardAvoidingView, but doing so breaks the entire layout.
Can anyone help me? I never managed to get KeyboardAvoidingView to work for me and now I really need it. Thanks in advance!
Here is my component. Also worth mentioning is that this component is top level(well, almost top level since it's wrapped in a Router)
const { height, width } = Dimensions.get('screen')
const style = StyleSheet.create({
main: {
height,
width,
flexDirection: 'column',
},
iconSelecter: {
width,
height: 196,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: Colors.primary,
marginTop: 32
},
icon: {
height: 164,
width: 164,
},
saveButton: {
width: 96,
height: 96,
borderRadius: 100,
backgroundColor: Colors.secondary,
alignItems: "center",
justifyContent: "center",
alignSelf: 'center',
position: 'absolute',
bottom: 96 + 32
},
saveIcon: {
height: 54,
width: 54,
},
textInputWrapper: {
borderBottomColor: Colors.textInputBorder,
width: 288,
borderBottomWidth: 1,
alignSelf: 'center',
marginTop: 96,
height: 48,
},
textInput: {
fontWeight: "300",
fontSize: 14,
margin: 0
},
hintWrapper: {
alignSelf: 'center',
marginTop: 4
},
hint: {
fontSize: 12,
fontFamily: "Roboto-Thin",
fontStyle: 'normal',
}
})
const CreateActivity = ({ goBack }: NavigationProps) => {
//////////////////////////////
//State and logic
///////////////
return (
// TODO: Add touchable opacity to dismiss keyboard
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<View style={{ flex: 1 }}>
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</View>
)
}
export default CreateActivity;
I suggest that you to try wrap all the content of the screen in <KeyboardAvoidingView /> (or make it one of the outermost elements), otherwise it only will slide up its children (the View and the TextInput) leaving the rest of the content in its original position, making the layout look overlaped and weird. If you do that, the value "position" should work fine.
Something like this:
<View style={style.main}>
<Appbar title="New activity" canGoBack goBack={goBack} />
<KeyboardAvoidingView behavior="position" >
<View style={{ flex: 1 }}> // --> Remove flex: 1 if you experience some issue with the positioning
<View style={style.iconSelecter}>
<GestureRecognizer onSwipeLeft={nextIcon} onSwipeRight={lastIcon}>
<Image style={style.icon} source={icons[currentIconIndex]?.file}></Image>
</GestureRecognizer>
</View>
<View style={style.hintWrapper}>
<Text style={style.hint}>Swipe to cycle through the icons</Text>
</View>
<KeyboardAvoidingView>
<View style={style.textInputWrapper}>
<TextInput style={style.textInput} placeholder={"Give this activity a name"} value={name} onChangeText={setName}></TextInput>
</View>
</KeyboardAvoidingView>
<TouchableNativeFeedback onPress={createActivity} background={TouchableNativeFeedback.Ripple("#fff", true)}>
<View style={style.saveButton}>
<Image style={style.saveIcon} source={require("../../assets/icons/light/save.png")}></Image>
</View>
</TouchableNativeFeedback>
</View>
</KeyboardAvoidingView>
</View>
Also see the comment in the code above. Check if you really need to use of flex: 1 in all the outer wrapper elements, and take a look to the height you are setting in the style.main based on dimentions. I don't think that it is necesary and I think it could lead to some measure issues if you fix the height of the parent container.
EDIT:
I was just digging in react-native docs and I realize that there is a zIndex that you could use to avoid ablsolute positioning. It is a relative style prop so it needs to be set between sibling views, like this:
export default class MyComponent extends React.Component {
render() {
return (
<View>
<View style={[styles.appbarShape, styles.appbarZIndex]} ><Text>Header</Text></View>
<KeyboardAvoidingView behavior="position" style={styles.contentZIndex}>
{other children}
<TextInput placeholder="enter text"/>
</KeyboardAvoidingView>
</View>
);
}
}
const styles = StyleSheet.create({
appbarShape: {
height: 80,
width: Dimensions.get('window').width,
justifyContent: 'center',
alignSelf: "stretch",
backgroundColor: "#FFF"
},
appbarZIndex: {
zIndex: 3,
},
contentZIndex: {
zIndex: 0
}
});
Since the view that represents the appbar has a greater zIndex it shows up over the ones with a lower zIndex
Check this out working in this snack https://snack.expo.io/5VXAcw4Y0
Docs: https://reactnative.dev/docs/layout-props
Hope it helps!
Use react-native-keyboard-aware-scroll-view
<KeyboardAwareScrollView extraHeight={135} enabledOnAndroid={true}
extraScrollHeight={70} style={styles.mainContainer}
automaticallyAdjustContentInsets={true}
enableOnAndroid={true}
keyboardShouldPersistTaps='handled'
scrollEnabled={true} >
//your form
</KeyboardAwareScrollView>
const styles = StyleSheet.create({
mainContainer: { flex: 1, marginHorizontal: 15, marginVertical: 15 },
});

How can I change the colour of a button in react native without reseting state?

I'm currently building a screen in react native that has a bunch of horizontal flatLists stacked vertically. Think uber eats, Spotify and Netflix.
The horizontal flatLists contain cards.
I have added like and share buttons to these cards. I want my users to be able to save or like the item in the card while scrolling. The way you can on instagram.
I click one the buttons to send a request to the backend which works.
The problem I am facing is that I want the buttons to change colour when I press them.
What I would normally do is place a new variable in state, that gets changed when the request to the backend is done.
The problem with this approach is that, it causes state to reload. This cause my component screen to reload. This is not an ideal user experience. I don't want the whole screen to flash when someone presses a button.
I also don't want my users to lose their place on the screen.
Is there a better approach that allows me to:
1) press the button
2) update the backend
3) Have my button change colour.
constructor(props) {
super(props)
this.state = {
card: [],
saved: false,
shared: false
}
}
<FlatList
data={card}
keyExtractor={(index, item) => Math.random().toString()}
horizontal
renderItem={({ item }) => {
return (
<View>
<TouchableOpacity
onPress={() => this.description(item)}
>
<Card image={{uri: item.img}} containerStyle={{width: width * 0.8, height: height * 0.4}} >
<View style={{height: 60, justifyContent: 'center'}}>
<Text style={[textStyle, {fontSize: 16}]}>{item.name}</Text>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent:'space-between', width: width * 0.7 }}>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
<View>
<Icon
name='bookmark'
type='Feather'
onPress={() => this.save(item._id)}
color={userHasPressedSave.includes(item._id) ? '#FF0000' : null}
/>
</View>
<View>
<Icon
name='ios-heart'
type='ionicon'
onPress={() => this.shared(item._id)}
/>
</View>
</View>
</View>
</Card>
</TouchableOpacity>
</View>
)
}
}
/>
This should be obvious ... If you don't want to modify 'global'/parent state then use 'local'/child state - just convert buttons into components.
Pass handlers to be able to call backend requests.
Use their local state to change color, content (like/unlike label), enable/disable, counter... whatever you need ... just 'think in react' ... tree of components.
You can also store changes in parent (within handlers) as class/object properties. Updating them (as opposed to updating the state) won't force rerendering but you will be able to use them when a rerender occurs.

React Native - Making background image avoid keyboard

I have a "fullscreen" background image that is used for a page:
container = {
flex: 1,
width: null,
height: null
}
<View ...>
...
<Image ... styles={container}>
...
<TextInput ... />
...
</Image>
</View>
However, as you may notice, tapping on the text input will open up the keyboard and the height of view changes. Since the image is set to cover, it also adjusts as the dimension of the view changes. I want the height of the parent view and the <Image> to not be affected by the keyboard, and only the content of the <Image> should be pushed up by the keyboard.
I'm aware there is a <KeyboardAvoidingView> available but I am not sure how to use it or does it even handle this situation.
Any ideas would be great. Thanks.
I do like this in React Native and it works :
backgroundImage: {
position: 'absolute',
left: 0,
top: 0,
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
},
I added
android:windowSoftInputMode="adjustPan"
to my AndroidManifest.xml and it worked out perfectly - the view doesn't get shrinked and the text inputs got pushed up.
Here's the solution I found to the same problem that I've faced. As you said, you can use react-native-keyboard-avoiding-view which is a really good way of avoiding keyboard and this solution implements that.
So what we have here is an image with style imageStyle wrapping everything.
render() {
return(
<Image source={{uri: 'blabla'}} style={imageStyle}>
<View style={styles.container}>
<KeyboardAwareScrollView>
<TouchableOpacity onPress={this.abc.bind(this)}>
<View style={styles.abc}>
<Text>Test</Text>
</View>
</TouchableOpacity>
...
</KeyboardAwareScrollView>
...
</View>
</Image>
)
}
and imageStyle:
const imageStyle = {
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
resizeMode: 'stretch',
}
Bonus: If you are going to support screen rotations, you can do:
const { width, height } = Dimensions.get('window')
const imageStyle = {
width: width < height ? width : height,
height: width < height ? height : width,
resizeMode: 'stretch',
}
Change
android:windowSoftInputMode="adjustResize"
To
android:windowSoftInputMode="adjustPan"
In android/app/src/main/AndroidManifest.xml, block activity
I wanted to accomplish the same thing, but without changing windowSoftInputMode.
I was able to do it by setting just the height of the image to Dimensions.get('window').height.
My background image stays put when the keyboard opens, but the components sitting on top of it move out of the way.
Because I was using React Navigation also, I was having issues using the window height effectively. I have a notification stuck to the bottom, and it was off the screen. My eventual solution was to close the ImageBackground element prior to the children:
<View style={styles.container}>
<ImageBackground
source={BACKGROUND}
imageStyle={{ resizeMode: 'repeat' }}
style={styles.imageBackground}
/>
<SafeAreaView style={{ flex: 1, justifyContent: 'space-between' }}>
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? "padding" : "height"} style={{flex: 1, justifyContent: 'space-between'}}>
{children}
</KeyboardAvoidingView>
<Notification/>
</SafeAreaView>
</View>
With styles looking like
export const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
export const styles = StyleSheet.create(
{
notification: { position: 'absolute', bottom: 0, left: 0, right: 0, alignItems: 'stretch'},
imageBackground: { position: 'absolute', left: 0, top: 0, height: screenHeight, width: screenWidth },
container: {
flex: 1,
alignItems: 'stretch',
justifyContent: 'space-around',
},
});

2 Different Background Colours For ScrollView bounce

I have a ScrollView that has a top section with one background colour and a bottom section with another different colour.
When a user scrolls past the content and the view bounces (elastic over-extend), how could I make it so the background is consistent with either the top or the bottom, depending on the scroll direction?
I wouldn't play with the contentInset and contentOffset of the ScrollView as if your content changes, it might change the position of your scrollview.
You can do something very simple by just adding a View at the very top of your ScrollView:
// const spacerHeight = 1000;
<ScrollView>
{Platform.OS === 'ios' && (
<View
style={{
backgroundColor: 'red',
height: spacerHeight,
position: 'absolute',
top: -spacerHeight,
left: 0,
right: 0,
}}
/>
)}
</ScrollView>
On iOS, you can render a spacer View on top of the ScrollView, and use contentInset to render it "off-screen", contentOffset to set the initial scroll position to offset the inset:
render() {
const isIos = Platform.OS === 'ios'
const SPACER_SIZE = 1000; //arbitrary size
const TOP_COLOR = 'white';
const BOTTOM_COLOR = 'papayawhip';
return (
<ScrollView
style={{backgroundColor: isIos ? BOTTOM_COLOR : TOP_COLOR }}
contentContainerStyle={{backgroundColor: TOP_COLOR}}
contentInset={{top: -SPACER_SIZE}}
contentOffset={{y: SPACER_SIZE}}>
{isIos && <View style={{height: SPACER_SIZE}} />}
//...your content here
</ScrollView>
);
}
Because contentInset and contentOffset are iOS only, this example is conditioned to degrade gracefully on Android.
The accepted solution did not work well for me because I need to put flexGrow: 1 on the contentContainerStyle. Using insets/offsets didn't make the content grow the way I want, otherwise it worked not so bad.
I have another solution to suggest: putting a bicolor background layer under a transparent ScrollView, and add colors to your scrollview content. This way, on ios bounce, the bicolor layer under the scrollview will reveal itself.
Here's what I mean by bicolor layer (here the scrollview is empty and transparent)
Now if I put back the ScrollView children (which if a body with blank background, and a footer with yellow background), I get this:
As long as you don't bounce more than 50% of the scrollview height, you will see the appropriate background color.
Here's a component you can use to wrap your scrollview.
const AppScrollViewIOSBounceColorsWrapper = ({
topBounceColor,
bottomBounceColor,
children,
...props
}) => {
return (
<View {...props} style={[{ position: 'relative' }, props.style]}>
{children}
<View
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: -1, // appear under the scrollview
}}
>
<View
style={{ flex: 1, backgroundColor: topBounceColor }}
/>
<View
style={{ flex: 1, backgroundColor: bottomBounceColor }}
/>
</View>
</View>
);
};
And here's how you use it:
<AppScrollViewIOSBounceColorsWrapper
style={{flex: 1}}
topBounceColor="white"
bottomBounceColor="yellowLancey"
>
<ScrollView style={{flex: 1}}>
<WhiteBackgroundBody/>
<YellowBackgroundFooter />
</AppScrollView>
</AppScrollViewIOSBounceColorsWrapper>
Make sure to NOT set a background color to the scrollview, otherwise the bicolor layer will never reveal itself (backgroundColor on contentContainerStyle is fine)
This is, I think the most stupid simple way i found to do it:
<ScrollView style={{backgroundColor: '#000000'}}>
[...]
<View style={{position: "absolute", bottom: -600, left: 0, right: 0, backgroundColor: '#FFFFFF', height: 600}}/>
</ScrollView>
You may adjust the height/bottom absolute value to your likings depending on how far you think the user could scroll.
I personally implemented that into a <ScrollBottom color={"white"}/> component for ease of use in all my ScrollViews
For me, the simplest solution is modification based on Sebastien Lorber answer which doesn't include wrapping, just calling it before (or after) ScrollView component:
Create component:
interface IScrollViewBackgroundLayer {
topBounceColor: string;
bottomBounceColor: string;
}
export const ScrollViewBackgroundLayer = ({
topBounceColor,
bottomBounceColor,
}: IScrollViewBackgroundLayer): ReactElement => (
<View
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
zIndex: -1, // appear under the scrollview
}}>
<View style={{ flex: 1, backgroundColor: topBounceColor }} />
<View style={{ flex: 1, backgroundColor: bottomBounceColor }} />
</View>
);
and use it like this:
return (
<SafeAreaView style={styles.container}>
<ScrollViewBackgroundLayer topBounceColor={topBounceColor} bottomBounceColor={bottomBounceColor} />
<ScrollView>
...
</ScrollView>
</SafeAreaView>

Categories