React native KeyboardAvoidingView not moving correctly - javascript

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

Related

React Native, content going under navigation header, SafeAreaView not working

As you can see in the image below, I have to give some top margin in order to "not" hide my half of the content under the navigation header, isn't header supposed to be a "safe area" for the below content, to be on the safe side I provided the SafeAreaView but still my content goes under the Header and unfortunately I have to give some hardcoded margin top value to avoid hiding.
The above image is when I comment marginTop.
Above image is when I add marginTop: 70
Code:
NotificationScreen.tsx:
import {
View,
SafeAreaView,
Text,
StatusBar,
} from 'react-native';
import Animated, {FadeIn, FadeOut} from 'react-native-reanimated';
import OrderItem from '../../components/OrderItem';
const NotificationScreen = () => {
const [orders, setOrders] = useState([]);
useEffect(() => {
// calling API...
}, []);
return (
<SafeAreaView style={styles.container}>
<StatusBar backgroundColor="transparent" translucent />
<Text style={{color: 'lightgray', fontSize: 18}}>My Orders: </Text>
<Animated.FlatList
data={orders}
entering={FadeIn}
leaving={FadeOut}
contentContainerStyle={{
alignItems: 'center',
}}
keyExtractor={(_: any, index) => 'key' + index}
renderItem={({item}) => <OrderItem key={item.id} data={item} />}
/>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
marginTop: 70, // if I remove this, the content goes under the header as shown in image.
flex: 1,
padding: 10,
backgroundColor: COLORS.primary,
},
});
export default NotificationScreen;
One more query, why my OrderItem not taking the full width of FlatList (see image, the gray box is not takin the full width...), I have provided width: 100% to my OrderItem container:
OrderItem.tsx:
const OrderItem = ({data}) => {
return (
<View style={styles.container}>
<View style={styles.textBlock}>
<Text style={styles.label}>Strategy: </Text>
<Text style={styles.info}>{data.strategyTitle}</Text>
</View>
// ... same views as shown in image
</View>
);
};
const styles = StyleSheet.create({
container: {
width: '100%',
paddingVertical: 10,
paddingHorizontal: 10,
alignItems: 'center',
justifyContent: 'space-between',
backgroundColor: COLORS.lightGray,
borderRadius: 10,
},
textBlock: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
},
label: {
color: 'grey',
fontSize: 16,
},
info: {
color: 'white',
fontSize: 16,
},
});
export default OrderItem;
The SafeAreaView does not work currently for Android devices. You need to have something like this to avoid this issue:
import { Platform,StatusBar } from "react-native";
const styles = StyleSheet.create({
container: {
paddingTop: Platform.OS == "android" ? StatusBar.currentHeight : 0,
},
});
<SafeAreaView style={styles.container}>
</SafeAreaView>
And for the OrderItem not taking all available width, remove from Animated.FlatList this:
contentContainerStyle={{alignItems: 'center'}}

TextInput disappears under keyboard in React Native

I am making a react native app and for some reason when I tap on the text input to type something it disappears under the keyboard. I want it to come to the top of the keyboard like the way Instagram messenger and other messenger apps do it. I tried using keyboardAvoidView and setting the behavior to padding and height but none of it worked.
import {
View,
Text,
StyleSheet,
SafeAreaView,
TextInput,
TouchableOpacity,
} from "react-native";
import CommunityPost from "../components/CommunityPost";
import { Context as CommunityContext } from "../context/CommunityContext";
const Key = ({ navigation }) => {
const { createCommunityPost, errorMessage } = useContext(CommunityContext);
const [body, setBody] = useState("");
return (
<View style={styles.container}>
<SafeAreaView />
<CommunityPost />
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
multiline={true}
placeholder="Type..."
placeholderTextColor="#CACACA"
value={body}
onChangeText={setBody}
/>
<TouchableOpacity
style={{ justifyContent: "center", flex: 1, flexDirection: "row" }}
onPress={() => createCommunityPost({ body })}
>
<Text style={styles.sendText}>Send</Text>
</TouchableOpacity>
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "flex-start",
backgroundColor: "#2B3654",
justifyContent: "flex-end",
},
inputContainer: {
justifyContent: "flex-end",
flexDirection: "row",
},
sendText: {
color: "white",
fontSize: 25,
},
input: {
fontSize: 25,
color: "white",
borderColor: "#2882D8",
borderWidth: 1,
borderRadius: 15,
width: "80%",
marginBottom: 30,
marginLeft: 10,
padding: 10,
},
});
export default Key;
You must KeyboardAvoidingView component provided by react native.
<KeyboardAvoidingView
behavior={Platform.OS == "ios" ? "padding" : "height"}
style={styles.container}
>
<SafeAreaView />
<CommunityPost />
<View style={styles.inputContainer}>
<TextInput
style={styles.input}
multiline={true}
placeholder="Type..."
placeholderTextColor="#CACACA"
value={body}
onChangeText={setBody}
/>
<TouchableOpacity
style={{ justifyContent: "center", flex: 1, flexDirection: "row" }}
onPress={() => createCommunityPost({ body })}
>
<Text style={styles.sendText}>Send</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
For these situations we can use one of these methods:
1.wrapping Component with <ScrollView></ScrollView>
2.wrapping Component with <KeyboardAvoidingView></KeyboardAvoidingView>
Sometimes our wrong given styles can make these happens too such as : Having a fixed value for our styles, check your margins and use one of the given methods
I hope it helps
Try using keyboardVerticalOffset and test it with different values.
For more info check this out
For those who want to keep the code clean, just use the FlatList component and add a View component involving the component with the states: {flex: 1, height: Dimensions.get ("window"). Height}.
Below left a component ready for anyone who wants to use, just wrap your component in this way:
<FormLayout>
{... your component}
</FormLayout>
Here is the solver component:
import React from 'react'
import { View, StyleSheet, FlatList, Dimensions } from 'react-native'
import Background from './Background'
const FormLayout = ({ children }) => {
const renderContent = <View style={styles.container}>
{children}
</View>
return <FlatList
ListHeaderComponent={renderContent}
showsVerticalScrollIndicator={false}
/>
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: Dimensions.get('window').height * 0.95
}
})
export default FormLayout

How to place floating action button in bottom right in React Native

I am trying to place a floating action button in the lower right corner of my app but it is placing it in the top left way off screen.
Returned view:
<View>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity onPress={this.onPress} activeOpacity={.5} >
<Image
source={require('./assets/images/hamburger.png')}
style={{ width: 30, height: 25, marginLeft: 15}}
/>
</TouchableOpacity>
</View>
<FloatingAction style={styles.bottom}/>
</View>
Styles:
const styles = StyleSheet.create({
bottom: {
flex: 1,
position: 'absolute',
bottom: 10,
right:10
},
});
My current view displays a header and a bottom tab view. I am able to place multiple FAB's in each tab screen but that produces an undesirable behavior. Thank you for any help.
Edit:
What I have:
What I want:
Your issue was on adding { flex: 1, position: 'absolute',} to the button style together. The parent component that covers all the phone screen would use flex: 1, your button component is the one that receives the style for the position.
Always creating a new component makes stuff easier to read and understand. So let's say you have a button component (<FloatingButton/>), you would do something like this:
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import FloatingButton from './FloatingButton';
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Text>
I'm just a Text
</Text>
<FloatingButton
style={styles.floatinBtn}
onPress={() => alert(`I'm being clicked!`)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
floatinBtn: {
position: 'absolute',
bottom: 10,
right: 10,
}
});
You will get this result:
This is the button component:
import React from 'react';
import { View, TouchableOpacity } from 'react-native';
export default props => (
<TouchableOpacity onPress={props.onPress} style={props.style}>
<View
style={{
backgroundColor: 'blue',
width: 45,
height: 45,
borderRadius: 45,
}}
/>
</TouchableOpacity>
);
Check the snack demo: https://snack.expo.io/#abranhe/floating-btn
// this should occupy the whole screen
<View style={{flex:1}}>
<View style={{flexDirection: 'row'}}>
<TouchableOpacity onPress={this.onPress} activeOpacity={.5} >
<Image
source={require('./assets/images/hamburger.png')}
style={{ width: 30, height: 25, marginLeft: 15}}
/>
</TouchableOpacity>
</View>
<FloatingAction style={styles.bottom}/>
</View>
const styles = StyleSheet.create({
bottom: {
position: 'absolute',
bottom: 10,
right:10
},
});
Just use that CSS in your code:
.floating-btn {
position:fixed;
bottom:10;
right: 10;
}
That's it

React Native - Parent View to wrap the child view

I have a view having text inside it. The component looks like this:
MyComponent = () => {
return (
<View style={styles.viewStyle}>
<Text style={styles.textStyle}>My Text</Text>
</View>
);
}
const styles = {
viewStyle: {
backgroundColor: '#006699',
height: 40,
justifyContent: 'center',
alignItems: 'center',
},
textStyle: {
color: '#000000',
fontSize: 16,
}
}
When this component gets used, I want my View to have a custom width such that it just wraps my text. Here's the visual clue:
It's about the original design of Layout with Flexbox.
You can make it work by either add flexDirection: 'row' to it's parent view (so it will stretch automatically related to <Text /> width),
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={styles.viewStyle}>
<Text style={styles.textStyle}>My Text</Text>
</View>
</View>
or give width to it directly.
<View style=[{styles.viewStyle}, {width: 50}]>
<Text style={styles.textStyle}>My Text</Text>
</View>
Result of option 1:

React Native : View does not bound to the right

I've been dabbling with some react native code and found that my view does not have a right boundary in both android and iOS renders. So when I add a horizontal margin, the view displays margin only on the left like this.
I am also attaching my code and styles to guide me on where I would have gone wrong.
Login.js
import React,{Component} from 'react';
import {View,ToolbarAndroid,Text, Image, StatusBar,TextInput, TouchableOpacity} from 'react-native';
import styles from './styles.js'
class LogIn extends Component {
constructor(props) {
super(props)
this.state = {hostName: '', userName: '', password: ''}
}
onButtonPress() {
this.props.onSubmit && this.props.onSubmit(this.state);
}
render() {
return(
<Image style={styles.container} source={images.background}>
<StatusBar backgroundColor="#00EF6C00" translucent={true}/>
<View style={styles.logocontainer}>
<FitImage source={images.logo} resizeMode="contain" resizeMethod="scale" style= {{width: 300,position: 'absolute', left: 50, height: 100}}/>
</View>
<View style={styles.comp_container}>
<TextInput placeholder="Host Name" style={{height: 40}} onChangeText={(text) => this.setState({hostName: text})}/>
<TextInput placeholder="User Name" style={{height: 40}} onChangeText={(text) => this.setState({userName: text})}/>
<TextInput placeholder="Password" style={{height: 40}} onChangeText={(text) => this.setState({password: text})}/>
<TouchableOpacity onPress={this.onButtonPress.bind(this)} style={{height: 40, flex: 1, flexDirection: 'row'}}>
<Text style={{flex: 1}}> Submit </Text>
</TouchableOpacity>
</View>
</Image>
)
}
}
LogIn.propTypes = {
onSubmit: React.PropTypes.func,
}
export default LogIn;
Also Please find the attached stylesheet for further information on style
import { StyleSheet } from 'react-native'
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column'
},
toolbar: {
backgroundColor: '#EF6C00',
height: 56,
elevation: 3,
},
textInput: {
borderRadius: 20,
borderColor: "#FFFFFF",
height: 40,
},
button: {
height: 40,
},
logocontainer: {
flex: 1,
flexDirection: 'column',
alignItems: 'stretch',
justifyContent: 'center',
},
comp_container: {
flex: 2,
flexDirection: 'column',
justifyContent: 'center',
marginHorizontal: 16,
}
});
export default styles
Edit: It is almost palpable that the problem should be with react-native unable to decide where the view boundary is. So instead of wrapping within the view, it decides to extent it. I also would love to get to know of any tools which can help me debug the UI issues, like the uiautomationviewer for android.
You do not specify width nor resizeMode in background image so it overflows screen. By default alignItems is stretch, TouchableOpacity fills container on horizontal and also overflows screen.

Categories