Issue: React-Native - Keyboard closes on each keystroke for TextInput
Asked Answered
B

9

17

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:

  1. Adding/removing Key to different components used in .map()
  2. Adding keyboardShouldPersistTaps='handled' to the ScrollView that the .map() is called in, including all other variations avaialble

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 }
Bickerstaff answered 9/5, 2020 at 17:12 Comment(0)
H
9

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>
            )
          }
        })
      }
Higginbotham answered 9/5, 2020 at 17:42 Comment(1)
Thank you. This worked a treat. I appreciate you making this easy to understand as well!Bickerstaff
A
7

One solution to this is to make use of the onEndEditing prop on Input. You can use local state with the onChange prop and then once the user clicks away / clicks done, the onEndEditing function is called and you can apply the local state to the parent state.


 const TextInput = ({ value, onChange }) => {
      const [currentValue, setCurrentValue] = useState(`${value}`);
      return (
          <Input
            value={currentValue}
            onChangeText={v => setCurrentValue(v)}
            onEndEditing={() => onChange(currentValue)}
          />
      );
 };

This way the parent onChange prop is only called once the field is finished being updated, rather than on each keystroke. Unless you are doing something fancy this works fine.

Antimagnetic answered 26/3, 2021 at 6:59 Comment(0)
C
4

This error occurs if you use TextInput inside ScrollView or FlatList. I tried all the above solutions but they didn't work from my side.

After too much struggle in my code finally I got a solution, which is: Use stateless variable in onChangeText prop of TextInput and update state variable in onEndEditing prop, remove value props and pass statevariable in prop --> defaultValue in TextInput for example;

import React,{useState} from 'react';
import {View,Text, TextInput} from 'react-native';
import { ScrollView } from 'react-native-virtualized-view';

export default function Test(){
    const [name, setName] = useState('')
    var n = ''
   return (
       <View>
        <ScrollView>
            <TextInput
                placeholder='Your name'
                onChangeText={(val)=>n = val}
                onEndEditing={()=>setName(n)}
                defaultValue={name}
            />
        </ScrollView>
       </View>
   );
}

After Textinput loses focus or you end your editing in TextInput the Scrollview re-rendered on updating state variable "name" in onEndEditing prop, everything in TextInput removes and defaultValue is set in it immediately which is a state variable value.

Cornie answered 14/12, 2022 at 10:5 Comment(0)
F
2

This happens when we enclose a text input under any list(Flatlist, virtualizedLists, or even lists made with map method). As the list re render after any state change the the keyboard tends to disappear. The simple solutions is make the text input outside of the list. The TextInput from react native was not working fine for me, so I used react-native-gesture-handler TextInput and the problem is solved!

Furlani answered 23/5, 2023 at 21:59 Comment(1)
thanks for the explanation! however my problem is still not solved but this points me in the right directionCashmere
B
1

So in my case i got upto all above mentioned answers , first one was not applicable for me as i have multiple lists and complex data that I am rendering so here is my solution which is quite simple by using useRef Note: take out value prop and use .current value for updation and manipulating string in apis where ever you need it

 //declaration above return
 const inpRef = React.useRef(null);
 //for Text input
 onChangeText={(Text) => {
                        inpRef.current = Text
                    }}
 //take out the value prop too 
Bacteriolysis answered 20/12, 2023 at 1:4 Comment(1)
This is below my code var inpRefField = React.useRef({current: ""}); <TextInput value={inpRefField.current} onEndEditing={() => {filteredData(SEARCH_TYPE_ATTEDANCE_REPORT)}} onChangeText={(text) => { console.log("onChangeText :-->", text); inpRefField.current = text}}/> keyboard dismiss issue Resolved, but new issue coming. Issue: after press ReturnKey. keyboard will close. But after again focus on field user cann't change any previous text, old text remains as it is. console.log inside onChangeText also print new text value, but inpRefField.current's value doesn't update.Dyad
H
0

In my case with FieldArray redux-form.

I added a timeout for TextInput.

const [displayText, setDisplayText] = useState(input.value && input.value.toString());

const timerRef = useRef(null);

const handleChange = text => {
    setDisplayText(text);

    if (isDelayOnChangeNeeded) {
      if (timerRef.current) {
        clearTimeout(timerRef.current);
      }
      timerRef.current = setTimeout(() => {
        input && input.onChange(text);
      }, 1500);
    } else {
      input && input.onChange(text);
    }
  };
Hirz answered 7/9, 2021 at 7:35 Comment(0)
M
0

Replace onChangeText to onEndEditing

onEndEditing={(bidAmts) => setBidAmt(bidAmts)}

Instead of

onChangeText={(bidAmts) => setBidAmt(bidAmts)}
Myelencephalon answered 10/11, 2021 at 7:2 Comment(0)
P
0
const [userName, setUserName] = useState(null);
....
function UserNameTextView() {
return (
     <View>
        <TextInput
          style={styles.textFieldContainer}
          placeholder="username"
          onChangeText={text => setUserName(text)}
          // removed below value prop
          value={userName}
        />
      </View>
);
}

For example in the above code the keypad gets dismissed on each and every keystroke. reason behind that is by default whatever we type in TextInput will be retained in the field, no need to assign it to the value property (like I did in the above code) and this causes the view to re-render and dismisses the keypad. remove the value prop and it will work as expected.

Pageboy answered 3/5, 2022 at 12:40 Comment(0)
C
0
  const getChildComponent = () => {
    let tmpComp: ReactNode = null;
    tmpComp = GetRegisterStepComponent(); // in this function rerender state 
    return tmpComp;
  };
Colin answered 23/6, 2023 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.