React-native how to move screen up on textinput - javascript

I have a login screen created using react-native.
How can I shift my screen up when the user is typing in the textInput?
Do I listen to the onFocus() event and use css styling to change the style of the view?

In 2017 (RN 0.43) there is special component for this: KeyboardAvoidingView

You can use ScrollView to control screen up and down movements. As long as user hasn't focused any TextInput, you can disable scroll. On focus, just shift up the scrollview using Content Offset prop.
<TextInput
onFocus={this.textInputFocused.bind(this)}
/>
textInputFocused() {
//do your stuff here. scroll screen up
}
Hope it helps!

import {KeyboardAvoidingView} from 'react-native';
<KeyboardAvoidingView style={styles.container} behavior="padding" enabled>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<Text style={{height: 100, marginTop: 30}}> test text before input</Text>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(text) => this.setState({text})}
value={this.state.text}
/>
<Text style={{height: 100, marginTop: 20}}>1 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>2 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>3 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>4 test text after input</Text>
<Text style={{height: 100, marginTop: 20}}>5 test text after input</Text>
</KeyboardAvoidingView>
Run in snack :
https://snack.expo.io/H1BE5ZoXV

Night Fury's answer is pretty good, though wouldn't fuss with the ScrollView's contentOffset, I'd use the ScrollResponder:
render() {
return (
<ScrollView ref="myScrollView">
<TextInput
ref="myInput"
onFocus={this._scrollToInput.bind(this)}
/>
</ScrollView>
);
}
_scrollToInput {
const scrollResponder = this.refs.myScrollView.getScrollResponder();
const inputHandle = React.findNodeHandle(this.refs.myInput)
scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
inputHandle, // The TextInput node handle
0, // The scroll view's bottom "contentInset" (default 0)
true // Prevent negative scrolling
);
}
See the method definition: scrollResponderScrollNativeHandleToKeyboard

This package does a greate job, introduces a KeyboardAwareScrollView component that scrolls the view up matching the input with the keyboard, then scrolling back down.

And another solution, working with RN 0.2, this time instead of squashing the content it scrolls.
inputFocused: function(ref) {
this._scroll(ref, 75);
},
inputBlurred: function(ref) {
this._scroll(ref, 0);
},
_scroll: function(ref, offset) {
setTimeout(() => {
var scrollResponder = this.refs.myScrollView.getScrollResponder();
scrollResponder.scrollResponderScrollNativeHandleToKeyboard(
React.findNodeHandle(this.refs[ref]),
offset,
true
);
});
},
...
render: function() {
return <View style={{flex: 1}}>
<ScrollView ref="myScrollView" keyboardDismissMode='interactive' contentContainerStyle={{flex: 1}}>
<TextInput
ref="myInput"
onFocus={this.inputFocused.bind(this, 'myInput')}
onBlur={this.inputBlurred.bind(this, 'myInput')} />
</ScrollView>
</View>
}

It's a crap shoot to get the native keyboard awareness functionality of ScrollView working. For my Android app, it works perfectly in one screen that is nearly identical as the other for which doesn't work. And on iOS, it just doesn't work. This is what's working for me:
import { Keyboard, ScrollView, StyleSheet, View } from 'react-native';
this.state = {
filler: false,
}
componentWillMount() {
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this._keyboardDidShow.bind(this));
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide.bind(this));
}
componentWillUnmount() {
this.keyboardDidShowListener.remove();
this.keyboardDidHideListener.remove();
}
_keyboardDidShow() {
this.setState({filler: true})
setTimeout(() => this.vertical && this.vertical.scrollToEnd({animated: true}), 0);
}
_keyboardDidHide() {
this.setState({filler: false})
}
...
return (
<ScrollView ref={ref => this.vertical = ref}>
<TextInput/>
{ this.state.filler ? <View style={styles.filler}/> : null }
</ScrollView>
)
styles.filler = {
height: 'Keyboard Height'
}
Note: This might only work if your <TextInput/> is at the bottom of the screen which it was in my case.

Related

ScrollView not working in react-native app, content disappears when attempting to add flex and flexGrow

for some reason a ScrollView in one of my components isn't working despite working in every other component I've implemented it in. Attempting to implement solutions to similar problems seems to just make the content I want displayed disappear.
I'm expecting to have a scrollable list of sample restaurant dishes. I created some dummy data to pass in for now but noticed it didn't scroll after reaching the end of the phone screen.
const testFoods = [
{
title: "test",
description: "Lorem Ipsum",
price: "$7.77",
image: "dummyLink",
},
// Same as above but 4 more times, didn't want to clog up the description
];
export default function MenuItems() {
return (
<ScrollView showsVerticalScrollIndicator={false}>
{testFoods.map((food, index) => (
<View key={index}>
<View style={styles.menuItemStyle}>
<FoodDetails food={food} />
<FoodImage food={food} />
</View>
</View>
))}
</ScrollView>
);
}
const FoodDetails = (props) => (
<View style={{ width: 240, justifyContent: "space-evenly" }}>
<Text style={styles.titleStyle}>{props.food.title}</Text>
<Text>{props.food.description}</Text>
<Text>{props.food.price}</Text>
</View>
);
const FoodImage = (props) => (
<View>
<Image
source={{ uri: props.food.image }}
style={{
width: 100,
height: 100,
borderRadius: 8,
}}
/>
</View>
);
const styles = StyleSheet.create({
menuItemStyle: {
flexDirection: "row",
justifyContent: "space-between",
margin: 20,
},
titleStyle: {
fontSize: 19,
fontWeight: "600",
},
});
The result is like so
Result with code above
The Header component with the sample restaurant image is a separate component by the way.
I have more data that can't be seen as for whatever reason the screen refuses to scroll. I'm using my actual phone for the tests but the result is the same when I use an emulator, scrolling doesn't work. After looking online I thought I would try adding a parent View with flex: 1 and a flexGrow: 1 to contentContainerStyle inside the ScrollView like so.
export default function MenuItems() {
return (
<View style={{ flex: 1 }}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ flexGrow: 1 }}
>
{testFoods.map((food, index) => (
<View key={index} style={styles.menuItemStyle}>
<FoodDetails food={food} />
<FoodImage food={food} />
</View>
))}
</ScrollView>
</View>
);
}
But that only resulted in the content disappearing. Reloading the app didn't change anything either Result after trying above code
Attempting to use a FlatList had the same result. I've also read that having percentage based styling values on height for any of the children components can make the scrolling stop working but all my components don't utilize such. Oddly enough when I change the styling on the outer view to height: 400, I'm able to get the scrolling to work but this is very sloppy and will likely only work on phone screens similar to mine. I know the ScrollView component is working fine, as when I add "horizontal" to it the scrolling works just fine and I'm able to scroll to the last item in the dataset. Obviously all the content is horizontal now though. After adding horizontal too ScrollView, scrolling works fine horizontally
Any ideas? Could it be some part of my styling I'm not noticing? I'm unable to test this on IOS so I'm not sure if it's an Android specific problem, would be strange though as scrolling worked fine in my other components.
Here's also the Header component code just in case it could be anything in there, although It shouldn't be.
const testImage =
"https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Restaurant_N%C3%A4sinneula.jpg/800px-Restaurant_N%C3%A4sinneula.jpg";
const testTitle = "Sample Restaurant";
const testDescription = "Thai · Comfort · $$ · 🎫 · 4 ⭐ (217+)";
export default function About() {
return (
<View>
<RestaurantImage image={testImage} />
<RestaurantTitle title={testTitle} />
<RestaurantDescription description={testDescription} />
</View>
);
}
const RestaurantImage = (props) => (
<Image source={{ uri: props.image }} style={{ width: "100%", height: 180 }} />
);
const RestaurantTitle = (props) => (
<Text
style={{
fontSize: 29,
fontWeight: "600",
marginTop: 10,
marginHorizontal: 15,
}}
>
{props.title}
</Text>
);
const RestaurantDescription = (props) => (
<Text
style={{
marginTop: 10,
marginHorizontal: 15,
fontWeight: "400",
fontSize: 15.5,
}}
>
{props.description}
</Text>
);
Wrap your card with TouchableOpacity.
<TouchableOpacity activeOpacity={0} key={index} style={styles.menuItemStyle}>
<FoodDetails food={food} />
<FoodImage food={food} />
</TouchableOpacity>
I hope this thing will work.

scroll when the keypad is open

On my screen, I type in the input field and get search results accordingly. The list is rendered within a ScrollView but it still doesn't let me scroll when the keypad is open (in Android at least).
How can I fix this?
This is the component where the scroll view is rendered.
export const LocationsFound: React.FunctionComponent<LocationsFoundProps> = ({
addressesFound,
}) => {
return (
<>
{addressesFound.length > 0 ? (
<KeyboardAwareScrollView
style={styles.searchResultsContainer}
keyboardShouldPersistTaps={'always'}
keyboardDismissMode={'on-drag'}
>
{addressesFound.map((addressDetails: addressDetailsType) => {
return (
<View
key={addressDetails.placeName}
style={styles.resultContainer}>
<Text
style={styles.text}
onPress={() => handleLocationSelection(addressDetails)}>
{addressDetails.placeName}
</Text>
</View>
);
})}
</KeyboardAwareScrollView>
) : null}
</>
);
};
const styles = StyleSheet.create({
searchResultsContainer: {
width: moderateScale(400),
paddingHorizontal: moderateScale(50),
paddingRight: moderateScale(65),
marginTop: moderateScale(10),
},
resultContainer: {
marginTop: moderateScale(10),
borderBottomWidth: 1,
borderBottomColor: 'grey',
},
text: {
fontSize: moderateScale(15),
},
});
This is the component where the LocationsFound component is called.
return (
<SafeAreaView style={styles.safeAreaViewContainer}>
<View style={styles.container}>
<View style={styles.searchFieldContainer}>
<AddressSearchInput
addressType="favouritePoint"
placeholder="Ort eingeben"
/>
</View>
<View style={styles.dropdown}>
<LocationsFound
addressesFound={locations.addressesFoundList}
/>
</View>
</View>
</SafeAreaView>
);
};
export const styles = StyleSheet.create({
safeAreaViewContainer: {
flex: 1,
},
container: {
height: '100%',
backgroundColor: 'white',
width: '100%',
display:"flex",
flexDirection:"column",
flex: 1
},
dropdown: {
position: 'absolute',
top: moderateScale(215),
zIndex: moderateScale(10),
backgroundColor: '#fff',
flex: 1
},
});
I also tried adding
onScrollBeginDrag={Keyboard.dismiss}
but it doesn't make a difference.
sounds like a height, issue, without all the code, no one is going to give your clear answers without speculation. the keyboard doesn't shrink the view, check out this package, it may help - https://github.com/APSL/react-native-keyboard-aware-scroll-view

Keyboard blocking textinput with Scrollview and KeyboardAvoidingView in react native

I am using RN 0.55.4 + Expo
I tried to use KeyboardAvoidingView to my form but it doesnt change anything with or without KeyboardAvoidingView, its still blocking my form. I am using
tcomb-form
This is my current code
return (
<View style={styles.container}>
<KeyboardAvoidingView>
<ScrollView>
<View>
<Header/>
<View style={styles.inputs}>
<LoginForm
formType={formType}
form={this.props.auth.form}
value={this.state.value}
onChange={self.onChange.bind(self)}/>
{passwordCheckbox}
</View>
<FormButton/>
<View >
<View style={styles.forgotContainer}>
{leftMessage}
{rightMessage}
</View>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>
</View>
)
This is the style
var styles = StyleSheet.create({
container: {
flexDirection: 'column',
flex: 1
},
inputs: {
marginTop: 10,
marginBottom: 10,
marginLeft: 10,
marginRight: 10
},
forgotContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
marginTop: 10,
marginLeft: 10,
marginRight: 10
}
})
This is the display
I also tried https://github.com/APSL/react-native-keyboard-aware-scroll-view library but still same result, keyboard is blocking the view / form.
Anyone know whats wrong?
For iOS you should set the "behavior" parameter of the KeyboardAvoidingView to "padding" :
<KeyboardAvoidingView behavior="padding">
Refering to react-native documentation :
Note: Android and iOS both interact with this prop differently.
Android may behave better when given no behavior prop at all, whereas
iOS is the opposite.
A working example on iOS and Android :
<KeyboardAvoidingView behavior={Platform.OS == "ios" ? "padding" : null}>
It also happened to me... ScrollView and FlatList can work it out by setting a dynamic height depending on your data to FlatList. eg:
<ScrollView>
<FlatList style={{height: dataArr.length * YourInputHeight}}
...
/>
</ScrollView>

positioning react elements

The following code works but wondering if this is really the best way to accomplish the task at hand. Two TextInput fields, when show = true display both of them. When show = false display the first one in the same position on the screen and not display the second one. We've substituted a blank Text element to take up the space of the second TextInput element. Is that the right way to do this? It seems a little hokey.
import React from 'react';
import { StyleSheet, TextInput, View, Button, Text } from 'react-native';
export default class App extends React.Component {
state={show: true,}
handleElement(){}
handleKey(){}
onSubmit=()=>{
this.setState({show:!this.state.show})
// this.setState(prevState =>({show:!prevState.show}))
}
render() {
return (
<View style={styles.container}>
{ this.state.show &&
<View>
<TextInput style={styles.input}
placeholder='Element'
onChangeText={this.handleElement} />
<TextInput style={styles.input}
placeholder='Key'
onChangeText={this.handleKey}/>
<Button title='Add' onPress={this.onSubmit} />
</View>
}
{ !this.state.show &&
<View>
<TextInput style={styles.input}
placeholder='Element'
onChangeText={this.handleElement} />
<Text style={styles.blank}> </Text>
<Button title='Add' onPress={this.onSubmit} />
</View>
}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
input: {
padding:5,
borderColor:'black',
borderWidth:1,
width:100,
marginTop:5,
},
blank: {
borderColor:'white',
borderWidth:1,
padding:5,
width:100,
marginTop:5,
},
});
You can take advantage of Flex positioning to achieve this. Also the layout can be simplified to something like:
render() {
return (
<View style={styles.container}>
<View style={styles.wrapper}>
<TextInput
style={styles.input}
placeholder='Element'
onChangeText={this.handleElement}
/>
{
this.state.show &&
<TextInput
style={styles.input}
placeholder='Key'
onChangeText={this.handleKey}
/>
}
<Button title='Add' onPress={this.onSubmit} />
</View>
</View>
)
}
The styles for the wrapper and the container elements using flex are:
wrapper: {
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'column'
},
container: {
flex: 0.5
}
The flex value of the container depends on your layout and how much space you want that element to take the screen.
I would recommend using visibility:hidden on the second TextInput. It will retain the spacing, just hide the element.

TextInput lost focus after typing one symbol when searching

I have a FlatList
<View style={styles.container}>
<FlatList data={this.state.restaurants}
renderItem={({ item }) => this.renderItem(item.restaurant)}
keyExtractor={restaurant => restaurant.key}
ListHeaderComponent={() => this.renderHeaderComponent()}
ItemSeparatorComponent={this.renderSeparator}/>
</View>
And have TextInput in header it. I am using TextInput as search bar.
renderHeaderComponent() {
return(
<View style={{ flexDirection: 'row', marginTop: 10, borderBottomColor: '#CED0CE', borderWidth: 1, borderColor: 'transparent' }}>
<Icon name='search' size={30} style={{ marginLeft: 10, marginRight: 10 }}/>
<TextInput
style={{height: 40, flex: 1}}
onChangeText={(text) => this.onChangeText(text)}
placeholder='Type text for search'
clearButtonMode='while-editing'
value={this.state.searchText}
/>
</View>
);
};
In onChangeMethod i filter my data.
onChangeText(text) {
const filteredRestaurants = _.filter(this.props.list, (restaurantObject) => {
const restaurant = restaurantObject.restaurant;
const result = restaurant.name.trim().toLowerCase().includes(text.trim().toLowerCase());
return result;
})
this.setState({
searchText: text,
restaurants: filteredRestaurants
});
}
The problem is following. When I type one symbol in TextInput then focus is lost immediately from TextInput? How can I keep focus in TextInput while typing?
You need to use an auto-bound method for this, as ListHeaderComponent is of type ReactClass, and your current method basically re-creates and re-binds its render every time the data updates, which is not what you want. This concept is further explained in this comment
Anyway, for your example, to fix your issues you should
1) Change your ListHeaderComponent prop to
ListHeaderComponent={this.renderListHeader}
2) Now you want to change your renderHeaderComponent method to be an auto-bound method, and by doing this a new render will not be instantiated every time you change data ( Or enter text into the `TextInput)
renderListHeader = () => (
<View style={{ flexDirection: 'row', marginTop: 10, borderBottomColor: '#CED0CE', borderWidth: 1, borderColor: 'transparent' }}>
<Icon name='search' size={30} style={{ marginLeft: 10, marginRight: 10 }}/>
<TextInput
style={{height: 40, flex: 1}}
onChangeText={(text) => this.onChangeText(text)}
placeholder='Type text for search'
clearButtonMode='while-editing'
value={this.state.searchText}
/>
</View>
)
I ran into this, and to solve it I wrapped the renderListHeader in a React.useMemo hook and passed the state hook as an item to the dependency array.
renderListHeader = useMemo(() => (
<View style={{ flexDirection: 'row', marginTop: 10, borderBottomColor: '#CED0CE', borderWidth: 1, borderColor: 'transparent' }}>
<Icon name='search' size={30} style={{ marginLeft: 10, marginRight: 10 }}/>
<TextInput
style={{height: 40, flex: 1}}
onChangeText={(text) => this.onChangeText(text)}
placeholder='Type text for search'
clearButtonMode='while-editing'
value={this.state.searchText}
/>
</View>
), [this.onChangeText])
This is still an issue for SectionList as of react-native 0.61.5. The auto-bound method doesn't work since the ListHeaderComponent re-renders when data is becomes an empty array.
I used the following work-around :
Move the text input code at the same level than the section list
Using position absolute, position it at the place you want.
Wrap it in an Animated.View
Leverage Animated.event to translate Y the Animated.View
Code sample
const animatedScrollYValue = useRef(new Animated.Value(0)).current;
...
<View>
<Animated.View style={{
position: 'absolute',
top: 142,
left: 30,
right: 30,
zIndex: 1,
transform: [{ translateY: Animated.multiply(animatedScrollYValue, new Animated.Value(-1)) }] }}>
// Your text input
</Animated.View>
<Animated.SectionList
scrollEventThrottle={1}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: animatedScrollYValue } } }], { useNativeDriver: true })}
keyExtractor={(item) => item.id}
ListHeaderComponent={// Whatever you want but make you include space for the absolute TextInput}
sections={data}
renderItem={renderItem}
renderSectionHeader={renderHeader}
/>
</View>
I found another workaround for SectionList that seems to work so far, and I'll update this answer if I find it stops working. Rather than rendering my component in ListHeaderComponent I add a dummy section at the start of my data and then use a conditional in renderSectionHeader to render it out.
<SectionList
sections={[{ title: 'header', data: [] }, ...sections]}
renderSectionHeader={({ section }) =>
section.title === 'header' ? (
<MyListHeaderComponent />
) : (
<DefaultSectionHeaderComponent />
)
}
/>
Having worked with some pretty hairy CollectionView screens in Swift/UIKit it's not that different from how we would handle a similar need in that environment so hopefully that means under the hood perf won't be an issue, but again I'll update this answer if that becomes the case.
Another option may be to just add a dummy item to your sections array so that it never becomes empty but I haven't tried that.

Categories

Resources