KeyboardAvoidingView, ScrollView and Flatlist in a form - react-native

I have a form where I am using KeyboardAvoidingView and ScrollView so that when a user clicks on an input field the screen will scroll to that particular field
Within my form I have an input field that is searchable and I am using a FlatList to display the results for the user to choose from. Currently I am getting the error:
VirtualizedLists should never be nested inside plain ScrollViews
I've looked at many posts around this but am yet to find a solution (unless I've missed something):
1 - How to put FlatList inside of the ScrollView in React-native?
2 - FlatList inside ScrollView doesn't scroll
3 - How to make a FlatList scrollable inside of a ScrollView in react native?
This is what I have so far:
export const SignUpMember = ({navigation}) => {
const renderHeader = formikProps => {
return (
<>
<FormField
keyboardType={'default'}
fieldName={'firstName'}
label={'First Name'}
/>
<FormField
keyboardType={'default'}
fieldName={'lastName'}
label={'Last Name'}
/>
<FormField
onChangeText={text => {
formikProps.values.clubName = text;
searchItems(text);
}}
value={formikProps.values.clubName}
keyboardType={'default'}
fieldName={'clubName'}
label={'Club Name'}
placeholder={'Search Club By Name...'}
/>
</>
);
};
const renderFooter = formikProps => {
return (
<>
<FormField
keyboardType={'phone-pad'}
fieldName={'telephone'}
label={'Telephone'}
/>
<FormField
keyboardType={'email-address'}
fieldName={'email'}
label={'Email'}
/>
<FormField
keyboardType="default"
secureTextEntry={true}
fieldName={'password'}
label={'Password'}
type={'password'}
/>
<FormField
keyboardType="default"
secureTextEntry={true}
fieldName={'passwordConfirmation'}
label={'Confirm Password'}
type={'password'}
/>
<Button
mt="2"
bg="brand.blue"
type="submit"
onPress={formikProps.handleSubmit}>
Sign Up
</Button>
</>
);
};
return (
<KeyboardAvoidingView
keyboardVerticalOffset={headerHeight}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<ScrollView
_contentContainerStyle={styles.container}
nestedScrollEnabled={true}>
<Box p="1" py="8" w="90%" maxW="290">
<VStack space={3} mt="5">
<Formik
initialValues={initialFormValues}
onSubmit={values => handleFormSubmit(values)}
validationSchema={userValidationSchema}>
{formikProps => (
<View>
<FlatList
nestedScrollEnabled={true}
data={flatListData}
renderItem={({item}) => (
<Pressable
onPress={() => {
formikProps.values.clubName = item.name;
setFlatListData([]);
}}>
<Text style={styles.flatList}>{item.name}</Text>
</Pressable>
)}
keyExtractor={item => item.name}
ListHeaderComponent={renderHeader(formikProps)}
ListFooterComponent={renderFooter(formikProps)}
/>
</View>
)}
</Formik>
</VStack>
</Box>
</ScrollView>
</KeyboardAvoidingView>
);
};
export default SignUpMember;
How can I piece this together?

Related

ScrollView inside Actionsheet doesn't scroll in android

Facing an issue where scrolling doesn't take effect in android but, in IOS works fine.
I tried using contentContainerStyle and wrapping the Scrollview with View but nothing changed.
<Actionsheet isOpen={isOpen} onClose={onClose}>
<Actionsheet.Content position={"absolute"} bottom={"0"}>
<Stack space={8} width={"100%"} px={"4"} py={"5"}>
<PrimaryText fontWidth={"bold"} fontSize={"lg"}>
{question}
</PrimaryText>
<Input
// double bind with the hook
value={searchInput}
onChangeText={(value) => setSearchInput(value)}
// style the input field
placeholder={t("commonComponents.search")}
variant={"unstyled"}
p={"3"}
textAlign={I18nManager.isRTL ? "right" : "left"}
fontFamily={fonts[i18n.language].medium}
fontSize={16}
borderWidth={"1"}
borderColor={colors.gray[200]}
InputRightElement={
<Box mr={"2"}>
<EvilIcons name="search" size={30} color={colors.gray[400]} />
</Box>
}
/>
<HStack justifyContent={"space-between"}>
<PrimaryText fontWidth={"medium"} fontSize={"sm"}>
{t("commonComponents.allItems")}
</PrimaryText>
<PrimaryText fontSize={"sm"} color={colors.gray[400]}>
{t("commonComponents.selected", {
selLength: selectedList.length,
})}
</PrimaryText>
</HStack>
<ScrollView height={"300"}>
{optionsList
.filter(
(opt) =>
opt.name_en
.toLowerCase()
.includes(searchInput.toLowerCase()) ||
opt.name_ar
.toLowerCase()
.includes(searchInput.toLowerCase())
)
.map((item) => {
return (
<SelectTile
key={item.id}
icon={item.icon}
title={item["name_" + i18n.language]}
isSelected={selectedList.some(
(selItem) => selItem.id === item.id
)}
/>
);
})}
</ScrollView>
</Stack>
</Actionsheet.Content>
</Actionsheet>
This part is the only part I want to scroll to work on.
In the end, I want to achieve multi-select features inside an ActionSheet.

React Native - add specific clearButton on input field when the keyboard is open

I am trying to create a specific clear button to use on both ios and android devices. I have created a reusable component for the several fields I have. When I press the fields since the keyboard opens the X button shows in all fields not only the field I have pressed. In the code below emptyField is a value set in a parent component.
const [keyboardShow, setKeyboardShow] = useState<boolean>(false);
useEffect(() => {
const showKeyboard = Keyboard.addListener('keyboardDidShow', () => {
setKeyboardShow(true);
});
const hideKeyboard = Keyboard.addListener('keyboardDidHide', () => {
setKeyboardShow(false);
});
return () => {
showKeyboard.remove();
hideKeyboard.remove();
};
}, []);
<TouchableComponent>
<TextInput
key={currencyTypeId}
ref={forwardRef}
style={styles.input}
onChangeText={onChangeText}
value={inputValue}
editable={editable}
autoCorrect={false}
autoCompleteType='off'
returnKeyType={returnKeyType}
placeholder={placeholder}
placeholderTextColor={placeholderColor}
keyboardType={keyboardType}
/>
</TouchableComponent>
{inputValue.length > 0 && keyboardShow && (
<View style={styles.customButton}>
<TouchableOpacity onPress={emptyField}>
<CloseIcon width={12} height={12}/>
</TouchableOpacity>
</View>
)}
Seems 'keyboardDidShow' and 'keyboardDidHide' events triggered in each reusable component.
You can try another approach. Just use onBlur and onFocus events. It's isolated for each component:
<TouchableComponent>
<TextInput
onBlur={() => setIsFocused(false)}
onFocus={() => setIsFocused(true)}
key={currencyTypeId}
ref={forwardRef}
style={styles.input}
onChangeText={onChangeText}
value={inputValue}
editable={editable}
autoCorrect={false}
autoCompleteType="off"
returnKeyType={returnKeyType}
placeholder={placeholder}
placeholderTextColor={placeholderColor}
keyboardType={keyboardType}
/>
</TouchableComponent>
{inputValue.length > 0 && isFocused && (
<View style={styles.customButton}>
<TouchableOpacity onPress={() => {}}>
<CloseIcon width={12} height={12} />
</TouchableOpacity>
</View>
)}

Problem with updating state in react native component

I have an array with values i want to present and then if the user press the edit button the presentation is changed to a list of TextInput components. When done editing the user can press Save or Cancel. If Cancel is pressed the values in the textInput fields should not be saved to the original array of values.
My problem is that even when pressing Cancel the data in the original array seems to be updated.
This is the code:
`
const handlePress = (text, index) => {
const newSchedule = [...scheduleTempState]
newSchedule[index].value = text
setScheduleTempState(newSchedule)
}
const handlePress2 =()=>{
setScheduleTempState([]);
console.log("handlepress2")
setEdit(false)
}
const handlePress3 =()=>{
setScheduleTempState(scheduleState);
console.log("handlepress3")
setEdit(true)
}
return (
edit
?
<View style={styles.scheduleRow}>
<View style={styles.buttonView}>
<TouchableOpacity onPress = { ()=>{saveSchedule(projectId,scheduleState);updateClient() ;setEdit(false)}} >
<MaterialIcons name="save" size={16} color="green" />
</TouchableOpacity>
<TouchableOpacity onPress = { ()=>{handlePress2()}} >
<MaterialIcons name="cancel" size={16} color="red" />
</TouchableOpacity>
</View>
<View>
<FlatList
horizontal = {true}
data={scheduleTempState}
keyExtractor={item => item.id}
renderItem={({item, index}) => {
return (
<View style={styles.decimalInputView}>
<TextInput
style={styles.cellInput}
onChangeText={(text) => {handlePress(text, index)}}
value = {item.value} />
</View>
)
}}
/>
</View>
</View>
:
<View style={styles.scheduleRow}>
<View style={styles.buttonView}>
<TouchableOpacity onPress = { ()=>handlePress3()} >
<MaterialIcons name="edit" size={14} color="black" />
</TouchableOpacity>
</View>
<View >
<FlatList
horizontal={true}
data={scheduleState}
renderItem={renderScheduleItem}
keyExtractor={item => item.id}
/>
</View>
</View>
);
}`
I guess my problem has something to do with states not being updated, but i can not see how the edited values can be saved when i press cancel.
Problem:
You are updating the scheduleTempState by referencing your scheduleState. So when you mutate scheduleTempState, it also mutates the scheduleState.
Solution: Please use spread operator to scheduleState which can help to create a new copy of a reference.
const handlePress3 =()=>{
setScheduleTempState([...scheduleState]);
...
}
Suggestion: It would be better to use explanatory names for function. It will make the code more readable. For example:
onChangeText() instead of handlepress()
onCancelEditing() instead of handlepress2()
onEdit instead of handlepress3()
Hope you will get the idea.

Input text inside a map function does not change react native

I have problem with input text when I try to change it return to previous value was working fine out side map func. but inside it refuse to change it value. I think it re render itself on change text.
so how do I change text and save name
userdata.map((l, i) => (
<Input label='First Name' value={l.name}
onChangeText={setname} />
<Button title="Modify" onPress={() =>
modifyDetails(
name,
)
} />
))
Edit: Based on Mohammad code I have done this
<Modal
animationType="slide"
transparent={true}
visible={modalVisible}
onRequestClose={() => {
Alert.alert("Modal has been closed.");
}}
>
<ScrollView>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<TouchableHighlight
onPress={() => {
setModalVisible(!modalVisible);
}}
>
<Text style={styles.textStyleClose}>X</Text>
</TouchableHighlight>
<UserDataWithCallback/> // inputs called here
</View>
</View>
</ScrollView>
</Modal>
But now renders empty inputs. Any solution.
useCallback will solve your problem.\
const userDataWithCallback = React.useCallback((l, i) => {
return (
<> // maybe a key prop is needed too
<Input
label='First Name'
value={l.name}
onChangeText={setname} />
<Button
title="Modify"
onPress={() => modifyDetails(name)} />
</>
)
}, [])
userdata.map((l, i) => (
userDataWithCallback(l, i)
))

Get warning after update react : VirtualizedLists should never be nested inside plain ScrollViews with the same orientation

i'm working on a react native app and i updated my react version.
I use Flatlist inside a ScrollView.
I got this warning :
VirtualizedLists should never be nested inside plain ScrollViews with the same orientation - use another VirtualizedList-backed container instead.
My component :
return (
<KeyboardAvoidingView behavior="padding" enabled style={styles.keyboardAvoidingView}>
<SafeAreaView style={styles.keyboardAvoidingView}>
<Header
..
}}
containerStyle={styles.headerContainer}
rightComponent={{
...
}}
/>
<ScrollView style={styles.body}>
<View style={styles.titleSection}>
<Text style={styles.stepTitle}>Étape 1/3</Text>
<Text style={styles.questionTitle}>Quel contact voulez-vous partager ?</Text>
</View>
<View>
<FlatList
data={contacts}
renderItem={({ item, index }) => (
<TouchableHighlight onPress={() => this.onItemClickHandler(index, item.id, item.firstName, item.lastName)} style={styles.touchableHighlight}>
<View>
<ListItem
chevron={
(
<Icon
color={this.state.selectedIndex === index
? `${defaultTheme.palette.white.main}`
: `${defaultTheme.palette.primary.dark}`}
name="chevron-right"
size={40}
type="material"
/>
)
}
containerStyle={
this.state.selectedIndex === index
? styles.selected
: styles.notSelected
}
leftElement={
(
<Icon
...
/>
)
}
title={`${item.firstName} ${item.lastName}`}
titleStyle={this.state.selectedIndex === index
? [styles.titleStyle, styles.titleSelected]
: [styles.titleStyle, styles.defaultTitle]}
/>
</View>
</TouchableHighlight>
)}
extraData={this.state.selectedIndex}
keyExtractor={(item) => item.email}
ListHeaderComponent={this.renderHeader(searchValue)}
style={styles.flatListStyle}
ListFooterComponent={this.renderFooter}
/>
{
(contacts.length > 0 && page > 0)
&& <CustomButton title="Afficher la suite" onPress={() => this.makeRemoteRequest()} loading={loading} disabled={loading} />
}
</View>
</ScrollView>
</SafeAreaView>
</KeyboardAvoidingView>
);
I had the same issue. See my code
What I did is the component which I wanted to render outside the flatlist I included that inside the ListHeaderComponent and removed the Scrollview component. Now its working fine without any warning.
Below is the previous code:
<ScrollView >
<ReadCard data={this.state.data}/>
<FlatList
data={this.state.data.related}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
ItemSeparatorComponent={ListSeprator}
/>
</ScrollView>
Below is the changed code without any warning:
<FlatList
data={this.state.data.related}
keyExtractor={this._keyExtractor}
renderItem={this._renderItem}
ItemSeparatorComponent={ListSeprator}
ListHeaderComponent={
<ReadCard data={this.state.data}/>
}
/>