I'm creating a typing race game with React Native which is producing an unexpected bug on Android.
When the user finishes typing a word/spacebar, I set the input
state to an empty string.
However, if the user finishes typing a word/spacebar then immediately presses the first letter of the next word/spacebar, the onChangeText
event returns the state which existed prior to setting it to a empty string, along with the new letter. It should instead return the new letter by itself.
The bug only occurs when typing very quickly on Android (emulator and real device), IOS hasn't been tested, and the web app works fine typing at any speed.
Snack to try it yourself with complete code: https://snack.expo.dev/@ariato/typing-race-bug-example?platform=android
const Race = () => {
const [input, setInput] = useState("");
const [wordIndex, setWordIndex] = useState(0);
const [letterIndex, setLetterIndex] = useState(0);
const [errorLength, setErrorLength] = useState(0);
const totalIndex = getTotalIndex(wordIndex, letterIndex);
const onChange = (val: string) => {
setInput(val);
checkInput(val);
};
const checkInput = (val: string) => {
const inputLength: number = val.length;
const answer = wordList[wordIndex].substring(0, inputLength);
if (val === answer) {
setErrorLength(0);
if (val === wordList[wordIndex]) {
const isLastWord = wordIndex === wordList.length - 1;
if (isLastWord) {
setWordIndex(0);
} else {
setWordIndex((wordIndex) => wordIndex + 1);
}
setLetterIndex(0);
setInput("");
} else {
setLetterIndex(inputLength);
}
} else {
setErrorLength(inputLength - letterIndex);
}
};
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<View style={styles.textContainer}>
{wordList.map((word, wordIdx) => (
<Text key={wordIdx}>
{word.split("").map((letter, letterIdx) => (
<Text key={letterIdx} style={getStyles(wordIdx, letterIdx)}>
{letter}
</Text>
))}
</Text>
))}
</View>
<TextInput
value={input}
onChangeText={onChange}
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
spellCheck={false}
importantForAutofill="no"
autoFocus
keyboardType="visible-password"
style={styles.textInput}
/>
</KeyboardAvoidingView>
);
};
export default Race;
This screenshot is after typing It'
, and then typing s
and a spacebar in rapid succession. The next character to type is underlined. For each incorrect character in the input there is a red letter.
These logs show how the input
state is set to an empty string, then becomes It's_
in one key stroke, as though the internal state of the TextInput
did not get changed to an empty string.
I tried:
- Moving the first setInput() in the onChange() to the if/else statements in checkInput() so that it would only trigger once per event, in case something strange was happening with setState() batching. No change, as expected.