onChangeText of TextInput is updating state but retrieving the old value - react-native

I have a state:
const [userName, setUserName] = useState('');
Next, I have the TextInput as:
<TextInput style={styles.value}
keyboardType={'default'}
placeholder={'Your Name'}
value={userName}
editable={true}
onChangeText={(value) => {
setUserName(value)
}} />
The on a button's onPress event I'm calling this function:
function saveButtonPressed() {
alert("The Value of Name is " + userName);
}
The problem is I can see the vvalue getting updated in the text field, but in the alert I still see '' and if I save tthe coe again the second time it shows the updated value.

The reason they don't show the current value has to do with your functions forming closures. Even if the value of userName changes, it is still a const and since the function where this was updated still has scope in the 1st re-render the updated value wont show. Instead when the scope changes outside the closure thats when you get the updated value in the 2nd re-render.
The first link has solutions for this.
The second link has a bit more information about hooks.
These should answer your question in detail. https://stackoverflow.com/a/58877875/9745240 https://stackoverflow.com/a/54069332/9745240
And a read about closures should help you.

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]);

How can I create a React Native Button that saves an array value and the Button state to the device using AsyncStorage?

I am trying to create a button that changes the text when clicked and it saves a value to an array. I then need to save the state of the button and the array to the device using AsyncStorage.
I have the first part done (please see Snack link here) but I am struggling to get the AsyncStorage to work properly. If the button had been clicked once, I would expect that the array has the "item 1" value in it and the button would say clicked. Even if the app is closed and reopened, those values should still remain until the button is clicked again.
I have not found any solutions so far. Is there anyone that has some ideas?
This is the workflow that you should follow:
create an object in state (in this.state = {} for classes, or setState() with hooks) for your button's text value
initialize the above value with the value from AsyncStorage (make sure to add some conditional that if it's empty it returns [])
also take note of how Asyncstorage is async, meaning you'll have to add 'await' when you're assigning the value
add some conditional for the text value of your button, whereas it will show a loading icon, (or nothing, doesn't matter) while AsyncStorage is retrieving the initial data
onPress on your button will change the state value AND the AsyncStorage value (or you can only update the AsyncStorage value when you're closing the page with componentWillUnMount, or useEffect(() => return(--do it here--))
If you're using functional components, it would look something like this:
const [textValues, setTextValues] = useState([])
const setInitialValues = async () => {
const info = await AsyncStorage.getItem('myValues')
setTextValues(info)
}
useEffect(() => {
setInitialValues()
return(
AsyncStorage.setItem('myValues', textValues)
)
}, [])
return(
<View>
<Button onPress={() => setTextValues(textValues + 1) title={textValues.length === 0 ? '' : textValues[textValues.length - 1]}}
</View>
)

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.

How to clear input in SlideTextInput (custom TextInput component)?

and of course sorry if the question is somewhat dumb.
In the app I'm developing a user should be able to swipe on the TextInput. Since TextInput only listens to taps I used this gist: https://gist.github.com/MikeShi42/87b65984f0a31e38d553cc056fcda017
(BTW #Michael Shi thanks a ton)
However, once I changed TextInput to SlideTextInput the Clear button ceased to work.
clearInput() {
this.setState({text: ''});
}
render() {
return (
<Button name='clear' action={this.clearInput} />
<SlideTextInput
style={styles.input}
ref='input'
onChangeText={(text) => this.setState({text: text})}
placeholder={this.state.placeholder}
value={this.state.text}
multiline={true}
returnKeyType='done'
blurOnSubmit={true} />
)
}
I also tried this.refs.input.setNativeProps({text: ''}); instead of just passing a new value prop (that should be — and was — sufficient for normal TextInput), and calling forceUpdate(), but again to no avail. I don't see much changes in SlideTextInput.js compared to the original TextInput component, but I must be missing something that would explain such bad behaviour?
UPD: the answer was pretty simple in the end. Instead of linking the component to its native counterpart (ref={this._setNativeRef}) like original TextInput does, SlideTextInput has it ref'ed to a string (ref="input"). I changed it back and voila.
Looking at the code it seems that the value props is not being sent to the original TextInput. It is extending the TextInput but it is returning another component without sending the value prop.
Try:
this.refs.input.setText('');
The answer was pretty simple in the end. Instead of linking the component to its native counterpart (ref={this._setNativeRef}) like original TextInput does, SlideTextInput has it ref'ed to a string (ref="input") (it's not about props in my code it's about SlideTextInput.js file itself). I changed it back and voila.

TextInput OnChange in React native

Please help me. I have a TextInput, my problem is when I type the numeric value, that value going to multiply with some number. When I type 1000, this will get saved to state as 1000, but it saves to store as 100. The results differ.
<TextInput
onChangeText={this.handle_bill_Amount}
style={styles.input}
placeholder="Amount"
value={this.state.Amount}
keyboardType = 'numeric'
enablesReturnKeyAutomatically={true}
placeholderTextColor = "#824242"
underlineColorAndroid="transparent">
</TextInput>
handle_bill_Amount = Amount => {
this.setState({ Amount})
let billamt = this.state.Amount;
console.log(billamt);
}
constructor(props) {
super(props);
this.state = {
Amount: '',
}
this.handle_bill_Amount = this.handle_bill_Amount.bind(this);
}
Guys help me please!
this.setState is async in nature, hence non-deterministic in the next statement. So, when you are logging the value, state might not yet have the updated value.
So for deterministic behavior, it is advised that you use the second argument, which is a callback, to get the correct state. It is invoked when setState is executed.
this.setState({Amount}, () => console.log(this.state.Amount))
I think results are not different, it's just your console.log which confuses you. It will print out 100 instead of 1000 because your are doing console log inside the function which is setting the new state. Try to do it somewhere else, e.g. in componentWillReceiveProps, or componentWillUpdate and you'll see it's ok.
Hope this helps.