I am working with a KeyboardAvoidingView and it's working perfectly fine except for one small issue. In the code below I have a TouchableOpacity that when clicked runs the function addName() which appends to an array and creates a new TextInput - basically when you click it, it adds a new TextInput to the ScrollView.
The KeyboardAvoidingView works perfectly fine except every time a new TextInput is added/rendered, I have to scroll down to see it. Do you know how I can make it automatically scroll to the bottom when a new TextInput is rendered?
Here is my code for the KeyboardAvoidingView:
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS == "ios" ? "padding" : "height"}
>
<HeaderComponent
name={this.props.route.params.bill.barName + " Tab"}
navigation={this.props.navigation}
goHome={true}
goHomePrompt={true}
/>
<View
style={{
marginTop: 30,
marginLeft: 10,
}}
>
<Text
style={{ color: "white", fontSize: 18 }}
allowFontScaling={false}
>
Add people to split with...
</Text>
</View>
<ScrollView keyboardShouldPersistTaps={"handled"}>
{this.state.nameInput.map((value, index) => (
<View style={styles.nameContainer} key={index}>
<View
style={{
width: "90%",
}}
>
<TextInput
style={styles.textInputContainer}
value={value}
onChange={this.handleText(index)}
placeholder={"Enter name..."}
placeholderTextColor={"#333333"}
maxLength={50}
/>
</View>
<View
style={{
width: "10%",
}}
>
<TouchableOpacity
onPress={() => this.handleDelete(index)}
>
<Icon name="cancel" type="material" color="red" />
</TouchableOpacity>
</View>
</View>
))}
<TouchableOpacity onPress={() => this.addName()}>
<Text style={styles.addPerson}>+ Add Person</Text>
</TouchableOpacity>
</ScrollView>
<View style={styles.bottomContainer}>
<TouchableOpacity
style={styles.continueButton}
onPress={() => {
// filters through array to make sure there are no empty strings
let nameInput = this.state.nameInput.filter(
(name) => name !== ""
);
if (
nameInput.length > 1 &&
new Set(nameInput).size === nameInput.length
) {
this.setState({ nameInput, loading: false });
} else {
alert(
"Please make sure there are at least two people and no duplicate names!"
);
}
}}
>
<Text style={styles.continueButtonText} allowFontScaling={false}>
Continue to Split Tab
</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
And here is my code for the addName() function:
addName = () => {
let nameInput = this.state.nameInput.concat("");
this.setState({
nameInput,
});
};
This page has the solution I was looking for: Is it possible to keep a ScrollView scrolled to the bottom?
Related
I have encountered a weird issue in the newest react native where the value in the text input in a component remains when a tab is switched.
I can't figure what is going on and I thought it should re-render when tab is changed but it doesn't.
Here's my code
app.js
export default function App() {
const [tab, setTab] = useState("TAB1")
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity
style={{ borderRadius: 5, borderWidth: 1, marginRight: 5, padding: 20 }}
onPress={() => setTab("TAB1")}
>
<Text>Tab 1</Text>
</TouchableOpacity>
<TouchableOpacity
style={{ borderRadius: 5, borderWidth: 1, padding: 20}}
onPress={() => setTab("TAB2")}
>
<Text>Tab 2</Text>
</TouchableOpacity>
</View>
<View style={{ marginTop: 20}}>
{
tab === "TAB1" ? (
<View>
<InputComponent tab={tab} />
</View>
) : (
<View>
<InputComponent tab={tab} />
</View>
)
}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
marginTop: 100,
padding: 10
},
});
inputcomponent.js
export function InputComponent({ tab }) {
const [value, setValue] = useState("");
return (
<View>
<Text>{tab}</Text>
<TextInput placeholder="INPUT HERE" value={value} onChangeText={setValue}/>
</View>
)
}
It seems like the input component re-renders but the text input within it doesn't change.
Demo Issue
This is such a good question. This is because we are importing it once and passing it to two different components. It changes the tab but uses the same textinput state because they are using the same key.
To fix this pass in the key prop so React knows that tab changed:
{
tab === "TAB1" ? (
<View>
<InputComponent key={1} tab={tab} />
</View>
) : (
<View>
<InputComponent key={2} tab={tab} />
</View>
)
}
Snack: https://snack.expo.io/mVVLb9uId
Read about keys: https://reactjs.org/docs/lists-and-keys.html#keys
The code below shows how all the functions are implemented
On iOS everything works fine, the button changes slide, the dots on the bottom change state based on the page and on the last page it displays a signup button,
On Android, the button works only on the first page, the last page doesnt show the signup button, and next button doesnt work on the second page!
export default function introScreen({ navigation }) {
const [sliderState, setSliderState] = useState({ currentPage: 0 });
const scrollRef = useRef();
const { width, height } = Dimensions.get('window')
const notchSize = StatusBar.currentHeight
const setSliderPage = (event: any) => {
const { currentPage } = sliderState;
const { x } = event.nativeEvent.contentOffset;
const indexOfNextScreen = Math.floor(x / width);
if (indexOfNextScreen !== currentPage) {
setSliderState({
...sliderState,
currentPage: indexOfNextScreen,
});
}
};
const { currentPage: pageIndex } = sliderState;
const onPressTouch = () => {
scrollRef.current?.scrollTo({
x: width*(pageIndex+1),
animated: true,
});
}
return (
<>
<StatusBar hidden />
<SafeAreaView style={{ flex: 1, backgroundColor:'black' }}>
<ScrollView
style={{ flex: 1 }}
horizontal={true}
scrollEventThrottle={16}
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
onScroll={(event: any) => {
setSliderPage(event);
}}
ref={scrollRef}
>
<View style={{ width, height, alignItems: 'center', }}>
<Image source={require('../../assets/images/Intorduction/1.png')} style={styles.imageStyle} />
<BlurView tint={'dark'} intensity={70} style={{width:'80%',borderRadius:10, marginTop:10}}>
<Text style={[styles.textStyle,{color:'rgb(255, 190, 46)'}]}bla bla</Text>
</BlurView>
</View>
<View style={{ width, height, alignItems: 'center' }}>
<Image source={require('../../assets/images/Intorduction/2.png')} style={styles.imageStyle} />
<BlurView tint={'dark'} intensity={70} style={{width:'80%',borderRadius:10, marginTop:10}}>
<Text style={[styles.textStyle,{color:'rgb(255, 190, 46)'}]}>bla bla</Text>
</BlurView>
</View>
<View style={{ width, height, alignItems: 'center' }}>
<Image source={require('../../assets/images/Intorduction/3.png')} style={styles.imageStyle} />
<BlurView tint={'light'} intensity={70} style={{width:'80%',borderRadius:10, marginTop:10}}>
<Text style={[styles.textStyle,{color:'rgb(241, 250, 238)'}]}>bla bla</Text>
</BlurView>
</View>
</ScrollView>
{pageIndex != 2 ?
<View style={styles.paginationWrapper}>
{Array.from(Array(3).keys()).map((key, index) => (
<View style={[styles.paginationDots, { opacity: pageIndex === index ? 1 : 0.2 }]} key={index} />
))}
</View>
:
<></>
}
{pageIndex != 2 ?
<BlurView tint={'dark'} intensity={70} style={styles.nextButton}>
<TouchableOpacity style={{flex:1, justifyContent:'center', alignItems:'center'}} onPress={onPressTouch} >
<Text style={{color:'rgb(241, 250, 238)',fontFamily: 'poiret-one', fontSize:25}}>
Next
</Text>
</TouchableOpacity>
</BlurView>
:
<BlurView tint={'light'} intensity={70} style={styles.signUpButton}>
<TouchableOpacity style={{flex:1, justifyContent:'center', alignItems:'center'}} onPress={() =>navigation.navigate('Login')} >
<Text style={{color:'black',fontFamily: 'poiret-one', fontSize:25}}>
Sign Up
</Text>
</TouchableOpacity>
</BlurView>
}
</SafeAreaView>
</>
);
}
I have narrowed down the problem to the width and the 'event.nativeEvent.contentOffset', the returning number is close to 1 but it is (0.9), therefor math.floor was giving 0 as the index number,
I have changed Math.floor to Math.round and it is working perfectly fine,
If anyone wants a Introduction Slider for their app they can use this
on my react-native app I would like to have premium content for people who paid a subscription.
My issue is how I make the content to display as unavailable(if you are not premium ) and the same as the other content if you are premium. Basically I would like the premium content to be displayed with a "lock overlay" on it for non-premium users.
However, I do not know how I set this conditional. It is a matter of state? If yes where should be positioned this state considering that is unidirectional?
Just to be clear I will have premium and non premium content
class Browser extends Component {
scrollX = new Animated.Value(0);
renderRecommended = () => {
return (
<View style={[styles.flex, styles.column, styles.recommended]}>
<View style={[styles.row, styles.recommendedHeader]}>
<Text
style={{
fontSize: theme.sizes.font * 1.4,
alignSelf: 'center',
color: 'white',
fontFamily: 'Nunito-Bold',
}}>
Recommended
</Text>
</View>
<View style={[styles.column, styles.recommendedList]}>
<FlatList
horizontal
scrollEnabled
showsHorizontalScrollIndicator={false}
scrollEventThrottle={16}
snapToAlignment="center"
style={[styles.shadow, {overflow: 'visible'}]}
data={this.props.destinations}
keyExtractor={(item, index) => `${item.id}`}
renderItem={({item, index}) =>
this.renderRecommendation(item, index)
}
/>
</View>
</View>
);
};
renderRecommendation = (item, index) => {
const {destinations} = this.props;
const isLastItem = index === destinations.length - 1;
const {navigation} = this.props;
return (
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate('PreScreen', {
item,
})
}>
<View
style={[
styles.flex,
styles.column,
styles.recommendation,
styles.shadow,
index === 0 ? {marginLeft: theme.sizes.margin} : null,
isLastItem ? {marginRight: theme.sizes.margin / 2} : null,
]}>
<ImageBackground
style={[styles.imageback]}
source={{uri: item.preview}}
/>
</View>
<View
style={[
index === 0 ? {marginLeft: theme.sizes.margin - 10} : null,
isLastItem ? {marginRight: theme.sizes.margin / 2} : null,
]}>
<Text
style={{
fontSize: theme.sizes.font * 1.25,
fontWeight: '200',
color: 'white',
marginLeft: 10,
//paddingBottom: 20,
fontFamily: 'Nunito-Bold',
}}>
{item.title}
</Text>
<Text
style={{
color: theme.colors.caption,
marginLeft: 10,
fontFamily: 'Nunito-SemiBold',
}}>
{item.location}
</Text>
</View>
</TouchableOpacity>
);
};
render() {
return (
<ScrollView style={styles.container}>
<BackgroundSvg style={styles.background} />
<ScrollView
style={styles.contentContainer}
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingBottom: theme.sizes.paddin}}>
{this.renderRecommended()}
{this.renderRecommended2()}
{/* <View style={styles.mainContainerView}>
<TouchableOpacity style={styles.singInButton} gradient>
<Text style={styles.logInText}>
Activate premium subscription
</Text>
</TouchableOpacity>
</View> */}
</ScrollView>
</ScrollView>
);
}
}
Browser.defaultProps = {
destinations: mocks,
reading: readingList,
};
export default Browser;
My code is the one on the top. Just to simplify I am accesing some elements from JSON and I am creating Flatlist based on this. What I want is is to give some of the JSON files a bolean with premium or not and in this way to make some elements available for user or not.
As part of your destinations array, for each object, you can specify a boolean field called isPremiumItem. Your users should have a boolean field like isPremiumUser to show the type of their subscription.
Then in renderRecommended method you can check the user subscription status (isPremiumUser), and also the specific item status (isPremiumItem). Then you can render accordingly.
I have been in trouble seeking a solution to the silly problem. There is something missing from my code which I am unable to understand for now. Looking forward to your answers and information regarding the below code:
Constructor:
constructor(props) {
super(props)
this.TotalQuery = this.TotalQuery.bind(this);
this.state = {
isLoading: true,
Query: [],
}
this.UserID();
}
Function()
TotalQuery(product_id){
fetch(`http://:3000/api/v1/users/queries/${product_id}`, {
method: 'GET',
}).then((response) => response.json()).then((resp => {
this.setState({
Query: resp
})
})) .catch((error)=>{
console.log("Api call error1");
})
}
Calling this inside the Flatlist like below:
<FlatList
data={this.state.UserProducts}
keyExtractor={(item, index) => index.toString()}
renderItem= { ({item}) => (
<View style={styles.order}>
<View style={styles.orderHeader}>
<View style={styles.ohInfo}>
<Text style={styles.ohLabel}>Ref#</Text>
<Text style={styles.ohValue}>#2019-{item.product_id}</Text>
</View>
<View style={[styles.ohInfo, { backgroundColor: '#E7E7E7' }]}>
<Text style={styles.ohLabel}>Amount</Text>
<Text style={styles.ohValue}>€{item.price}</Text>
</View>
<View style={styles.ohInfo}>
<Text style={styles.ohLabel}>Messages</Text>
{this.TotalQuery(item.product_id)}
{this.state.Query.map((i, index) => (
<Text style={styles.ohValue}>{i.total}</Text>))}
</View>
</View>
<View style={styles.profileImgContainer}>
<View>
<ImageBackground style={styles.profileImgContainer}>
<Image source={{ uri: item.url }} style={[styles.profileImg]} />
</ImageBackground>
</View>
</View>
<View style={styles.orderBottom}>
<View style={styles.orderBottomLf}>
<Image resizeMode={'contain'} style={{ width: 14, height: 14 }}
source={require('../../assets/images/icon-pending-black.png')} />
<Text
style={[styles.orderStatusText]}>
{(item.status === 1) ? <Text style={styles.Approved}>Approved</Text> : null}
{(item.status === 2) ? <Text style={styles.Sold}>Sold</Text> : null}
{(item.status === 3) ? <Text style={styles.UnderReview}>Under Review</Text> : null}
{(item.status === 4) ? <Text style={styles.Inactive}>Inactive</Text> : null}
{(item.status === 5) ? <Text style={styles.Deleted}>Deleted</Text> : null}
</Text>
</View>
<View style={styles.orderBottomRg}>
<TouchableOpacity style={styles.profileImgContainer1} onPress={() => this.Sold(item.product_id)}>
{(item.status === 1) ? <Image style={[styles.profileImg1]} source={require('../../assets/images/checked.png')}/> : null}
</TouchableOpacity>
</View>
<View style={styles.orderBottomRg}>
<TouchableOpacity style={styles.profileImgContainer2} onPress={() => {this.Delete(item.product_id)}}>
{(item.status === 1 || item.status === 3 || item.status === 4) ? <Image style={[styles.profileImg2]} source={require('../../assets/images/delete.png')}/> : null }
</TouchableOpacity>
</View>
</View>
</View>
)}
/>
Above is the flatlist rendering, everything is rendering from it only. Please check.
There are multiple problems with your code.
The problem is that you are calling a function within the Flatlist renderItem method.
The way Flatlist works is you give it a data set and then it will call renderItem for each entry in that data set.
And then, any time your component re renders or the child item re renders the Flatlist will do this again.
Plus, it looks like you want to call this.TotalQuery(item.product_id) for each item in your data set but you are saving the return value to a single state value, so each call with overwrite the previous.
I would recommend moving your renderItem code into its own Component, and then each Component instance can have it's own state object where you can save the return value from your function call.
I have this code :
renderRooms = ({item:room}) => {
return(
<View style={styles.cards}>
<View style={styles.cardRight}>
<View style={{ marginLeft:2, alignItems: 'center', flex:1 }}>
<Text style={{fontSize:12, marginTop:10, marginBottom: 5, color:'#B0B0B0'}}>
Harga/malam
</Text>
<Text style={{fontSize:12, textDecorationLine:'line-through'}}>
{`${'Rp.'}${room.pricing.normal}`}
</Text>
<Text style={{fontSize:14, fontWeight:'800', color:'#26de81', }}>
{`${'Rp.'}${room.pricing.discount}`}
</Text>
<CheckBox
// center
iconRight
title={this.state.showContinueToPayment == true ? 'Batal' : 'Pilih'}
checked={this.state.showContinueToPayment}
containerStyle={{padding:5, borderColor:'white', backgroundColor:'white'}}
onPress={() => this.setState({showContinueToPayment: !this.state.showContinueToPayment})}
/>
</View>
</View>
</View>
)
}
on this part :
<CheckBox
iconRight
title={this.state.showContinueToPayment == true ? 'Batal' : 'Pilih'}
checked={this.state.showContinueToPayment}
containerStyle={{padding:5, borderColor:'white', backgroundColor:'white'}}
onPress={() => this.setState({showContinueToPayment: !this.state.showContinueToPayment})}
/>
I want to be able to set the state of my checkbox, but I can't make it done.
I'm using a FlatList, when I render my app, this renderRooms give me 2 list. I think the problem is these 2 list should have their own state. But I don't know how to do it.
Looking for some helps. Thanks beforehands.