how to detect change in useState except for first rendering? - react-native

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

Related

Struggling with useEffect and flatlist

I am rendering a component for every item in a flatList. Each component has a label, and when the component is rendered, I have a useEffect that fetches the updated label name for that specific label.
For some reason, it seems to only be running for the last item in the flatList. The last item is the only item with the updated name, while all other still contain the outdated information.
Assuming there is an updated name for each label, why could my useEffect only be running on the last item?
<FlatList
data={labels}
keyExtractor={keyExtractor}
renderItem={renderItem}
/>
Label.js - I would think this would run for every label component rendered. Could there be a possible issue with what I have here? Or must it be somewhere else in my code?
let name = label.name;
useEffect(() => {
updateLabel()
name = label.name
}, [label]);
return (
<>
{name}
</>
)
I see several possible issues. Some important code is missing, so I'll answer what I can.
You're not using state to hold your label name in the Label component (name = label.name), so React will never know to re-render the component when it changes. It's rare to need to use a let variable in React. To hold properties that the component needs to change, use the useState hook.
However, you shouldn't do that here, because of the next point.
It looks like you are updating the label somewhere else, and also locally (name = label.name). Don't do this, it's too easy for the two to get out of sync and cause bugs. If the name is coming from somewhere else, show it and set it from props.
I'm not sure what updateLabel() does or where it comes from (how does the function know what to update the label to?), but if you need it, it should come from props.
If label.name is a string, you can't render it in a fragment. You must render it in a Text component. <Text>{label.name}</Text>
The object that FlatList passes in to the renderItem callback does not have a property called label, you are looking for item - this is the object from the data prop.
function renderLabel({ item }) { // item, not label
return <Label label={item} onPress={() => onPressLead(item)}/>;
}
const Label = ({ label, updateLabel }) => {
// no local label variable
useEffect(() => {
updateLabel(); // what is this supposed to do?
}, []); // no dependencies, if you only want to update the label once on mount
return <Text>{label.name}</Text>; // if label.name is a string
};
// your FlatList is fine as written
Your use effect probably needs the label as a dependency.
useEffect(() => {
updateLabelName()
}, [label]);

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

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

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

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 load "dynamic" value from store to TextInput on loading and handle its onChangeText?

I need to get value from store in redux to TextInput. I know it can be set by value={this.props.Value}. I need to save that value to save later, so I change it by using local state like this
value={this.state.Value}
onChangeText={text => {
this.setState({
Value: text
})
}}
And in constructor of component, I set the value of TextInput to local state
constructor(props) {
super(props);
this.state = {
Value: this.props.Value
}
}
This work fine. But now, that Value is generated on loading this component.
componentDidMount() {
this.props.onLoadView();
}
onLoadView() will dispatch an action that generate new data of Value in store. But constructor function is called before componentDidMount(), that mean component state Value is initial data in store, not the new one. If I change it back to value={this.props.Value} to fix this problem, I can not handle that value to save later.
I know I can change onChangeText to update the value of TextInput to store immediately. But whenever I do not want to save this value, I have to change it back to the initial value. Is there any easier way to solve this problem?
Thank you!
As said in the comment, you can use componentDidUpdate to check your previous props and comparing them to the ones you are using in your component:
componentDidUpdate = () => {
if(this.props.Value !== this.state.Value) this.setState({Value : this.props.Value })
}
As you are storing the previous props.Value in your state you can directly compare the current props with the current state