Full disclaimer upfront for this one - I've been working with react native for around a week or two, and I suspect that I've encountered this issue without fully understanding why!
Issue: On each keystroke within a TextInput field, the keyboard closes automatically and only records the first keystroke.
Situation: I am using a prefefined array as the default for useState. The TextInput fields are called using .map() based on the current state. The onChangeText() updates the sate to capture changes to the array. The state is updated with each keystroke.
Things Tried:
Does anyone know what is causing the keyboard to close on each keystroke, and how I can prevent this from happening whilst continuing to capture changes to the TextInput fields in the main state?
Snippet below of the code I'm working on (I've removed some of the unrelated detail):
import React, { useState } from 'react';
import {
View,
Text,
Button,
TextInput,
SectionList,
SafeAreaView,
TouchableOpacity,
ScrollView,
Modal,
} from 'react-native';
import { Picker} from '@react-native-community/picker';
//import custom components
import { styles, Break } from './MasterStyles';
import { inputData, ingredients } from './inputData';
function addNewLoaf() {
const [ingredientsList, setIngredientsList] = useState(ingredients);
const [selectedLoaf, setSelectedLoaf] = useState('Regular Loaf');
const [flourModalVisibility, setFlourModalVisibility] = useState(false);
const [newLoaf, setNewLoaf] = useState('');
function IngredientsRecorder() {
return (
<View style={styles.ingredientsContainer}>
<View style={{flexDirection: 'column'}}>
<View>
<Text style={styles.metricTitle}>
Volume of Ingredients:
</Text>
</View>
{
ingredientsList.map(e => {
if(e.isVisible && e.ingredient){
return (
<View style={{flexDirection: 'row', alignItems: 'center'}} key={e.id}>
<View style={{flex:2}}>
<Text style={styles.metricText}>{e.name}:</Text>
</View>
<View style={{flex:3}}>
<TextInput
placeholder='amount'
style={styles.inputText}
keyboardType='number-pad'
value={e.amount}
onChangeText={value => ingredientsAmountHandler(value, e.id)}
/>
</View>
<View style={{flex:1}}>
<Text style={styles.ingredientsText}>{e.units}</Text>
</View>
</View>
)
}
})
}
</View>
</View>
)
}
const ingredientsAmountHandler = (text, id) => {
// setAmount(enteredText);
let newArray = [...ingredientsList]
let index = newArray.findIndex(element => element.id === id)
newArray[index].amount = text
setIngredientsList(newArray)
}
return (
<SafeAreaView style={styles.container}>
<View style={styles.page}>
<Text style={styles.titleText}>Add a New Loaf</Text>
<Break />
<View style={{flexDirection: 'row'}}>
<TextInput
placeholder='What would you like to call your loaf?'
style={styles.inputText}
onChangeText={loafNameInputHandler}
value={newLoaf}
/>
<Button title='Create Loaf' color='#342e29' onPress={addNewLoafHandler} />
</View>
<Break />
<ScrollView styles={styles.page} keyboardShouldPersistTaps='handled'>
<LoafSelector />
<FlourSelector />
<IngredientsRecorder />
</ScrollView>
</View>
<Break />
</SafeAreaView>
);
}
export { addNewLoaf }
Luckily, React Native team has a fix for this. The keyboardDismissMode property can be set inside the ScrollView. Setting it to on-drag, ensures that the keyboard is dismissed when a drag begins. The simulator below shows this property being used.
react-native#19096: Doesn't support Android's onKeyPreIme. react-native#19366: Calling .focus () after closing Android's keyboard via back button doesn't bring keyboard up again. react-native#26799: Doesn't support Android's secureTextEntry when keyboardType="email-address" or keyboardType="phone-pad".
react-native#19366: Calling .focus () after closing Android's keyboard via back button doesn't bring keyboard up again. react-native#26799: Doesn't support Android's secureTextEntry when keyboardType="email-address" or keyboardType="phone-pad".
When the keyboard is up, tapping anywhere else on the page will require you to do it twice. The first time you tap anywhere outside the TextInput, it dismisses the keyboard. Once the keyboard is dismissed the second tap enables the action. This can be seen in the example below.
Since you are changing the list all of your inputs are getting re-rendered. One way to avoid this would be storing your current editing text into another state value and merge it to the list after the input is submitted or lost focus. Here is the minimal example:
let defaultTemp={editingIndex:-1,text:''}
let [temp,setTemp] = useState(defaultTemp); //We will store current being edited input's data and index
{
ingredientsList.map((e,i) => {
if(e.isVisible && e.ingredient){
return (
<View style={{flexDirection: 'row', alignItems: 'center'}} key={e.id}>
<View style={{flex:2}}>
<Text style={styles.metricText}>{e.name}:</Text>
</View>
<View style={{flex:3}}>
<TextInput
placeholder='amount'
style={styles.inputText}
keyboardType='number-pad'
value={temp.editingIndex===i?temp.text:e.amount}
//the input got focus
onFocus={()=>setTemp({editingIndex:i,text:e.amount})}
//the input lost focus
onBlur={()=>{
ingredientsAmountHandler(temp.text, e.id)
setTemp(defaultTemp)
}
onChangeText={text => setTemp({text,editingIndex:i})}
/>
</View>
<View style={{flex:1}}>
<Text style={styles.ingredientsText}>{e.units}</Text>
</View>
</View>
)
}
})
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With