Why does TextInput try to update value when my onChangeText method has a filter/regex it has to pass? - react-native

I'm in the process of creating my own NumberInput component using react-natives TextInput. However I'm having trouble trying to validate the input the user is trying to submit/enter in to the field. I have a regex to check if the value the user is trying to input is a number, if it is we update the state number, if not I dont want the value to update the state. The regex works but when the user tries to enter any other character than an number, say , or ., the input field flickers showing the character but quickly erases it so to say.
I've recorded a gif showing what I mean (https://i.gyazo.com/9f86dbfd7d7cba17c480a7b4c456e004.mp4). Is there any way to prevent the flickering? I am already implementing the onTextChange so I don't see how the value even tries to get updated.
const Container = () => {
const [number, setCurrentNumber] = useState();
const numberRegex = /^\d+$/;
const numberValidation = (updatedValue) => {
if (numberRegex.test(updatedValue)) {
setCurrentNumber(updatedValue);
}
};
return (
<View>
<TextInput
value={number ? number.toString() : undefined}
onChangeText={(value) => numberValidation(value)}
keyboardType="numeric"
/>
</View>
);
};

Related

how to detect change in useState except for first rendering?

If I change text of TextInput then I change useState named name and setName.
const [name, setName] = useState("")
<TextInput
defaultValue={meData?.me?.name}
onChangeText={(text) => setNameValue(text)}
/>
And I want to change disableConfirm state from true to first, if I change just one word in this TextInput with useEffect.
const [disableConfirm, setDisableConfirm] = useState(true);
useEffect(() => {
setDisableConfirm(false)
}, [nameValue]);
The problem is when screen is first rendered, this useEffect is executed.
So disableConfirm state becomes false even though I don't change any word in TextInput.
How to prevent first rendering here? how to detect only change of TextInput?
Another way to achieve this would be using the TextInput as a controlled component.
First, create a state with a default value.
Look for changes in that state variable, if it changes from what it was originally, then enable the submit button.
Snack Implementation for the same is here
Approach:
Declare a state variable with a default value
const [name, setName] = React.useState(SOME_DEFAULT_VALUE);
Now for the TextInput, what you can do is
<TextInput placeholder="Enter Name" onChangeText={setName} value={name} />
What this does is, it updates the field with the name variable every time you type something in it.
Now, to look for the changes, what you can do is,
useEffect(() => {
if (name !== SOME_DEFAULT_VALUE) {
setDisableConfirm(false);
} else {
/*
You can disable the button here
If you want user to change the textinput value
atleast once from what it was originally,
for that set disableConfirm to false here
*/
}
}, [name]);

Onclick button, state doesn't change

I'm having some troubles understanding the code.
Basically if I do this:
<Text onPress={() => console.log('123')} >123</Text>
,
and I click on the text, it logs me 123 each click.
But I'm doing a dialer app. Basically having a component Tile (representing a single number, also with secondary option (but that will be dealt with later)).
So as I'm including my (currently only single one) Tile in App.js, I want onPress event to call function that changes state of currently dialed number. So if user clicks 3 times on '1', I want the final string to be 111.
The thing is, I can see that the handleNumber() function is called once after running code and never again after clicking on the number, therefore never changing state, not logging anything.
App.js Tile implementation:
const [getNumber, setNumber] = useState();
const handleNumber = (newChar) => {
console.log(newChar);
setNumber(newChar);
}
<Tile onPress={() => handleNumber('1')} style={styles.tile} firstChar={'1'} secondChar={'✉'}/>
Tile.js:
const Tile = (props) => {
return (
<TouchableOpacity onPress={props.onPress()}>
<View>
<Text style={styles.mainChar}>{props.firstChar}</Text>
{props.secondChar ? <Text style={styles.secondChar}>{props.secondChar}</Text> : null}
</View>
</TouchableOpacity>
)
}
One more thing:
As you can see in App.js::handleNumber(),
the implementation is currently wrong, because the final number will always be just the new single number, instead of appending it to the end of string.
But if I wanted to do smth like setNumber(getNumber + newChar), it would give Maximum update depth exceeded error.
The thing here is the way that you're passing the onPress prop to TouchableOpacity. See, you're calling the props.onPress function instead of just passing it to the component, which causes the function to be executed when the component renders (or rerenders).
You should be fine just by using <TouchableOpacity onPress={props.onPress}> and then setNumber(getNumber + newChar) will be fine as well.
As a side note, you could initialize your App.js state with the '1' string that you wish to be your initial value (const [getNumber, setNumber] = useState('1')), and then the Tile component can receive getNumber directly.

Get cursor position, text and key in a React Native TextInput onKeyPress

I need to get the current cursor position, the current text value and the pressed key of a TextInput when the text value changes, either in onKeyPress or in onChangeText.
For example, typing "A" should result in the following:
keyboardKey: 'A'
text: 'A'
cursorPosition: 1
Then, typing "B" should result in:
keyboardKey: 'B'
text: 'AB'
cursorPosition: 2
I tried to achieve that by listening to onKeyPress, onChangeText and onSelectionChange (getting start and end from nativeEvent.selection) but without any success as the events are probably happening asynchronously so using useState() or useRef() didn't help in order to get the latest values of the three in any of the eents.
<TextInput
onChangeText={onChangeText}
onKeyPress={onKeyPress}
onSelectionChange={onSelectionChange}
/>
I also tried getting the text value from a reference of the TextInput in onKeyPress but this didn't work either.
Finally, tried with setting all three values as states and listening for their changes in useEffect but this wouldn't work because the function will get executed if any of the values change and I want this to be called only once per key press. In addition, I'm not getting the latest values of cursorPosition and text for some reason.
useEffect(() => {
console.log('useEffect', keyboardKey, cursorPosition, text)
}, [keyboardKey, cursorPosition, text]);
I don't think there is a way you can get the cursor position value from the TextInput itself. You would have to implement it on your own. You can use the onKeyPress prop to check what key is pressed and increment a counter that should be in the state like so:
const [cursorPosition, setCursorPosition] = useState(0);
const [text, setText] = useState("");
const onKeyPress = ({ nativeEvent: { key: string } }) => {
// Logic to check what key is pressed if needed
// Here I put +1 for this simple example, but you can put whatever value you want here
setCursorPosition(cursorPosition + 1);
};
const onChangeText = (newText: string) => {
setText(newText);
}
<TextInput onKeyPress={onKeyPress} onChangeText={onChangeText} />
You can then use the setCursorPosition function to update your cursor position and then read it from cursorPosition. Same goes for text.

Does event, onBlur, work with the React Admin useInput hook on custom field?

I have a custom field that takes JSON and dynamically prints out TextField objects.
For each TextField I have an onChange and onBlur event. The onChange is for updating the state and the onBlur is for updating the final value to be past to the API and saved.
I'm using the useInput hook to save the updated value, however, when I use onBlur it doesn't save the value.
If I only use onChange it DOES save the value, but only the first key stroke, which is why I switched to onBlur. Any ideas? Here's the code:
const DefaultField = props => {
const {
input,
} = useInput(props.record);
let parsedObj = JSON.parse(props.record.values);
const [defaultState, setDefaultState] = React.useState({
websiteLink: parsedObj.websiteLink,
menuInstructionsLink: parsedObj.menuInstructionsLink,
surveyLink: parsedObj.surveyLink
});
const handleBlur = (event) => {
parsedObj[event.target.name] = event.target.value;
props.record.values= JSON.stringify(values);
//event.target.value is the expected value but it's not persisted when I save it.
};
const handleChange = (event) => {
setDefaultState({ ...defaultState, [event.target.name]: event.target.value });
};
return (
<FormControl component="fieldset">
<FormGroup {...input} id="defaultGroup" aria-label="position">
<React.Fragment>
{ Object.keys(parsedObj).map((key) => (
<TextField
id="standard-full-width"
name={key}
label={key}
onBlur={handleBlur}
onChange={handleChange}
style={{ margin: 8, width: '500px' }}
value={defaultState[key]}
fullWidth
margin="normal"
/>
))}
</React.Fragment>
</FormGroup>
</FormControl>
)
}
I never solved it properly, but I did discover that the dataProvider was being called twice. The first time it would send the expected values, but the second time the values were set back to the original, however I noticed that the field "previousData" had the values I needed. For now I'm just making sure to use the "previousData" field. This isn't the prettiest solution, but for now it's working. Maybe it's a bug or something sinister is going on.

Show string from the beginning React Native TextInput

I have a problem when showing the value from a TextInput, currently it is showing the last part of the string. i.e, given the following string 1 - This is a pretty long string it shows ong string instead of 1 - This is.
I've tried passing selection as a prop but it doesn't work as expected. It only "accommodates" the string when the input is focused again.
selection={{start:0, end:0}}
PS: I do not want to truncate the string, just show the start.
Here is my Input (this is the one from NativeBase but it does have the same props than TextInput from ReactNative)
<Input
ref={getRef}
testID={`input-${testID}`}
value={value}
placeholder={placeholder}
onChangeText={onChangeText}
style={styles.input}//this styling is only for fontSize/color
onFocus={onFocus}
onBlur={onBlur}
keyboardType={keyboardType}
maxLength={maxLength}
disabled={disabled}
/>
define a state called selection like
type selection = {
start: number;
end: number;
};
const [selected, setSelected] = useState<selection>({
start: 0,
end: 0,
});
and onFocus set your selection state as
setSelected({
start: address.length,
end: address.length,
});