react-native reseting TextInput after ClearTextOnFocus - react-native

I am building a form in React-Native and have set ClearTextOnFocus to true as it is easier to handle dynamic formating for editing.
I am trying to add a reset function by setting all local state to the redux store, but if the user has not typed anything in a selected TextInput, the local state has not changed, and react native does not re-render the TextInput; leaving it blank.
Anyone have any thoughts on how I can unclear the TextInput or force React to re-render. Code is a work in progress, but here are the relevant bits.
Thanks
class GoalScreen extends Component {
componentWillMount = () => this.setPropsToState();
onReset = () => {
this.setPropsToState();
}
onChange = text => this.setState({ [text.field]: text.input });
setPropsToState = () => {
const { name } = this.props.goal;
this.setState({ name });
};
render() {
const { name } = this.state;
return (
<View style={styles.screenContainer}>
<Text style={styles.text}> Name </Text>
<TextInput
placeholder="a brand new bag"
keyboardType="default"
autoCorrect={false}
style={styles.inputField}
clearTextOnFocus
onChangeText={text => this.onChange({ input: text, field: 'rate' })}
value={name}
/>
</View>
}
}

So, I'm not using Redux, and my use case might be a bit different than yours, but I thought my solution might still be relevant here, if only to confirm that (after hours of wrangling with this) it appears that passing true to the clearTextOnFocus prop prevents further updates to a TextInput component.
I tried every conceivable workaround (like setNativeProps(), forceUpdate()) but nothing worked, so I ended up having to basically write my own logic for clearing and resetting the input text.
This component should 1) clear input text on focus and then 2) reset it to its previous value if the user hasn't pressed a key:
class ResettableInput extends Component {
state = {
Current: this.props.value,
Previous: ""
};
KeyPressed = false;
//cache current input value for later revert if necessary, and clear input
onFocus = () => {
this.setState({ Previous: this.state.Current, Current: "" });
};
//record whether key was pressed so input value can be reverted if necessary
onKeyPress = () => {
this.KeyPressed = true;
};
onChangeText = text => {
this.setState({ Current: text });
};
//if no key was pressed, revert input to previous value
onBlur = () => {
if (!this.KeyPressed) {
this.setState({ Current: this.state.Previous, Previous: "" });
}
};
render = () => {
return (
<TextInput
onChangeText={this.onChangeText}
value={this.state.Current}
onBlur={this.onBlur}
onFocus={this.onFocus}
onKeyPress={this.onKeyPress}
/>
);
};
}

Related

Why my input closes when new Component is showing?

I have a a Header there is a text input and I have a Main component there are the list of products of the searched text.
So If the input text is empty then I want to show him his last searched things. If he type anything then I want to show him the products with the same name as the input text. I make a condition like this:
const SearchRoot = ({ }: ISearchRoot) => {
const searchParam = useSelector((state: RootState) => state.Search.searchParam);
return (
<>
{ searchParam.length > 0 ?
<SearchList />
:
<LatestHistory />
}
</>
)
}
So if I type anything then my input are closing automatically. But if I press again the keyboard and typing then its not closing and working. So its only happend when the component changing from <LatestHistory to <SearchList only one time. So how can I make my keyboard always open when the component is chaning ?
Search
const Search = () => {
return (
<View style={s.container}>
<StatusBar backgroundColor='#fff' />
<SearchHeader />
<SearchRoot />
</View>
)
}
SearchHeader
const SearchHeader = () => {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParams>>();
const dispatch = useDispatch();
// filters
const [searchText, setSearchText] = useState<string>('');
const handleChangeText = (e: string) => {
dispatch(setSearch({searchParam: e}));
setSearchText(e)
};
const handleClearText = () => {
dispatch(setSearch({searchParam: ''}));
setSearchText('');
};
const handlePressSeach = () => {
searchText.length > 0 &&
navigation.navigate('Searched', {
searchText,
searchType: tabType
});
};
const handleGoBack = () => navigation.goBack();
return (
<View style={s.container}>
<View style={s.header}>
<View style={s.backContainer}>
<GoBackIcon
onPress={handleGoBack}
color='#555'
/>
</View>
<View style={s.inputContainer}>
<SearchInput
value={searchText}
onChangeText={handleChangeText}
onPressSearched={handlePressSeach}
onPressClearTextField={handleClearText}
autoFocus={true}
style={s.sInput}
/>
</View>
</View>
</View>
)
}
SearchRoot
const SearchRoot = ({ }: ISearchRoot) => {
const searchParam = useSelector((state: RootState) => state.Search.searchParam);
return (
<>
{ searchParam.length > 0 ?
<SearchMain />
:
<Text>Vorschläge</Text>
}
</>
)
}
I am very thankful for your help!!
I just read your problem statement and matched it with your Code.
✦First of all your sequence is wrong.
✦First Comes the Previous Searched Result.
✦Then when you press it then it should clear the previous search result.
✦Then On text change Property
✦Then OnPress Searched
Maybe that's Why it closes. If that's Not the case then!
(In my Opinion)
when then you click the search bar the previously searched result disappears and this is done by the function handleClearText. Now When the Function Completes it's Functionality the search bar closes. The Second time you open the search bar it works as their's no Previous Search result to clear.
So the Problem is with the handleClearText function.
My Proposed Solution in case 2 is
We can call the search method again (at the end) of handleClearText function.
then it will re open the search bar with the new state.
I hope it resolves your issue, Regards...
first of all const [searchText, setSearchText] = useState(''); this whole logic move to parent class search and from there pass it to siblings to avoid any confusion.whenever re-rendering happens values will be passed correctly to child.I think somewhere that is going wrong.Either use state and prop drilling or store, dont mix it up.

Not able to get value from async storage It shows 'name' value to 'loading', However same code work in class components name 'value' change

After using the setItem method value of 'name' variable should change to value entered in the textInput but console.log(item) shows 'loading' After clicking submit button at first time. Which is initial value of the 'name' variable in useState.
Once I entered the submit button again the name variable value change to value entered in textInput.
Expected: When I click submit button at first time, item.name variable should change to textInput value
Using same code in class component work fine.
export default function App() {
const arr =[]
const [text, setText] = useState()
const[item,setItem] = useState([
{name: 'loading'}
])
const storedata= async ()=> {
// setItem({name: text})
arr.push({name: text})
try {
await AsyncStorage.setItem('mykey',JSON.stringify(arr));
console.log(await AsyncStorage.getItem('mykey'))
}
catch (error) {
console.log(error)
}
try{
setItem({item: JSON.parse(await AsyncStorage.getItem('mykey'))})
console.log('printing item',item)
}
catch(e){
console.log(e)
}
}
return (
<View style={styles.container}>
<TextInput onChangeText={(text) => setText(text)}
placeholder='type'
/>
<Button title='change'
onPress={storedata}
/>
</View>
);
}
Output:
Button click first time: printing item [{"name":"loading"}]
Button click second time: printing item {"item":[{"name":"hi"}]}
class App extends React.Component{
arr=[]
id=0
state= {
text: '',
item:[
{name: 'loading'}
]
}
storedata = async () => {
this.arr.push({ name: this.state.text})
await AsyncStorage.setItem('mylist',JSON.stringify(this.arr));
this.setState({
item: JSON.parse(await AsyncStorage.getItem('mylist'))
})
console.log(item)
}
The setState function is an asynchronous function. setState calls are batched to improve the performance of the application. So the results are not reflected immediately. That's why, you are getting the previous value for the item variable.
You can find more info here.

React Hook Form with Children in React Native

I have a form with ~15 fields where each section is a unique child component. I want to know how to pass data between the parent form and child components(using control because this is react native)
Right now, I see the proper value for testResult in onSubmit logs but data is undefined for some reason. This means my parent form is somehow not picking up the value in the child.
Parent Form:
const Stepper = () => {
const form = useForm({ defaultValues: {
testResult: "",
}
});
const { control, handleSubmit, formState: { errors }, } = form;
const testResult = useWatch({ control, name: "testResult" });
const onSubmit = (data) => {
console.log("watched testResult value: ", testResult);
console.log("form submission data: ", data);
};
return (
<WaterStep form={form} />
<Button onSubmit={handleSubmit(onSubmit())} />
)
}
Child component:
const WaterStep = ({ form }) => {
const { control, formState: { errors }, } = form;
return (
<Controller
name="testResult"
control={control}
rules={{
maxLength: 3,
required: true,
}}
render={({ field: onBlue, onChange, value }) => (
<TextInput
keyboardType="number-pad"
maxLength={3}
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
)}
Here I'm trying the first approach this answer suggests, but I've also tried the second with useFormContext() in child https://stackoverflow.com/a/70603480/8561357
Additionally, must we use control in React Native? The examples that use register appear simpler, but the official docs are limited for React Native and only show use of control
Update: From Abe's answer, you can see that I'm getting undefined because I'm calling onSubmit callback in my submit button. I mistakenly did this because I wasn't seeing any data getting logged when passing onSubmit properly like this handleSubmit(onSubmit). I still think my issue is that my child component's data isn't being tracked properly by the form in parent
The problem is most likely in this line:
<Button onSubmit={handleSubmit(onSubmit())} />
Since you're executing the onSubmit callback, you're not allowing react-hook-forms to pass in the data from the form. Try replacing it with the following
<Button onSubmit={handleSubmit(onSubmit)} />
For anyone still looking for guidance on using react-hook-form with child components, here's what I found out to work well:
Parent Component:
const Stepper = (props) => {
const { ...methods } = useForm({
defaultValues: {
testResult: "",
},
});
const onSubmit = (data) => {
console.log("form submission data: ", data);
};
const onError = (errors, e) => {
return console.log("form submission errors: ", errors);
};
return (
<FormProvider {...methods}>
<WaterStep
name="testResult"
rules={{
maxLength: 3,
required: true,
}}
/>
<Button onSubmit={handleSubmit(onSubmit)} />
)
}
Child:
import { useFormContext, useController } from "react-hook-form";
const WaterStep = (props) => {
const formContext = useFormContext();
const { formState } = formContext;
const { name, label, rules, defaultValue, ...inputProps } = props;
const { field } = useController({ name, rules, defaultValue });
if (!formContext || !name) {
const msg = !formContext
? "Test Input must be wrapped by the FormProvider"
: "Name must be defined";
console.error(msg);
return null;
}
return (
<View>
<Text>
Test Input
{formState.errors.testResult && <Text color="#F01313">*</Text>}
</Text>
<TextInput
style={{
...(formState.errors.phTestResult && {
borderColor: "#f009",
}),
}}
placeholder="Test Value"
keyboardType="number-pad"
maxLength={3}
onBlur={field.onBlur}
onChangeText={field.onChange}
value={field.value}
/>
</View>
);
};
Here's what we're doing:
Define useForm() in parent and de-structure all its methods
Wrap child in <FormProvider> component and pass useForm's methods to this provider
Make sure to define name and rules as props for your child component so it can pass these to useController()
In your child component, define useFormContext() and de-structure your props
Get access to the field methods like onChange, onBlur, value by creating a controller. Pass those de-structured props to useController()
You can go to an arbitrary level of nested child, just wrap parents in a <FormProvider> component and pass formContext as prop.
In Ancestor:
...
const { ...methods } = useForm({
defaultValues: {
testResult: "",
},
});
const onSubmit = (data) => {
console.log("form submission data: ", data);
};
...
<FormProvider {...methods}>
<ChildOne/>
</FormProvider>
In Parent:
const ChecklistSection = (props) => {
const formContext = useFormContext();
const { formState } = formContext;
return (
<FormProvider {...formContext}>
<WaterStep
name="testResult"
rules={{
maxLength: 3,
required: true,
}}
/>
</FormProvider>
)}
Thanks to https://echobind.com/post/react-hook-form-for-react-native (one of the only resources I found on using react-hook-form with nested components in react-native)
....
And a further evaluation of my blank submission data problem, if you missed it:
As Abe pointed out, the reason I didn't see data or errors being logged upon form submission was because onSubmit was not being called. This was because my custom submission button, which I didn't include in my original question for simplicity's sake, had a broken callback for a completion gesture. I thought I solved onSubmit not being called by passing it as a call onSubmit(), but I was going down the wrong track.

Picker onValueChange() called twice

I want to support localization using react-18next.
This component shows a Picker, sets a LocalStorage key (the selected language) and change the app language.
I noticed the onValueChange method is called twice. The first call (using a proper selection tap action on a Picker item) have the correct parameter (the language I have chosen), the second call have the value of the first Picker item, whenever the values is (!).
Example: if I select the English Picker item, I see the Picker switch to English (first _changeLanguage() call), and then again to "Device" (second _changeLanguage() call). I'm sure it's an async ops problem, not sure where..
#translate(['settings', 'common'], { wait: true })
export default class Settings extends Component {
state = {};
constructor(props) {
super(props);
}
componentWillMount() {
this.getLang();
}
async _changeLanguage(ln) {
const { t, i18n, navigation } = this.props;
console.warn("_changeLanguage: ",ln)
await this.promisedSetState({lang:ln})
if(ln=="device") {
console.warn("removing lang setting")
await AsyncStorage.removeItem('#App:lang');
} else {
console.warn("lang setting: ", ln)
await AsyncStorage.setItem('#App:lang', ln);
i18n.changeLanguage(ln)
}
};
//get Language from AsyncStorage, if it has been previously set
async getLang() {
const value = await AsyncStorage.getItem('#App:lang');
console.warn("getLangfrom asyncstorage:", value)
if(value) await this.promisedSetState ({lang:value})
}
promisedSetState = (newState) => {
return new Promise((resolve) => {
this.setState(newState, () => {
resolve()
});
});
};
render() {
const { t, i18n, navigation } = this.props;
const { navigate } = navigation;
return (<View>
<Picker
selectedValue={this.state.lang}
onValueChange={(itemValue, itemIndex) =>this._changeLanguage(itemValue) }>
<Picker.Item color="#666" label="detected from device" value="device" />
<Picker.Item label="English" value="en" />
<Picker.Item label="Deutsch" value="it" />
</Picker>
</View>);
}
}
The code is based on the react-i18next Expo example
https://github.com/i18next/react-i18next/tree/master/example/v9.x.x/reactnative-expo
I'm not sure if this belongs here but if anyone is encountering the problem of onValueChange firing twice and is using react-native-picker-select instead of the default Picker then this solution may work for you.
I ended up using the itemKey property instead of value and setting the key of each item to be the same as its value. Note the example item would end up shaped like so: { key: 'value1', value: 'value1', label: 'Your Label' } where the key and value are identical.
Original Code:
<RNPickerSelect
style={styles.picker}
value={value}
onValueChange={this.onValueChange}
placeholder={placeholder}
items={options}
/>
Fixed Code:
<RNPickerSelect
style={styles.picker}
itemKey={value}
onValueChange={this.onValueChange}
placeholder={placeholder}
items={options}
/>
I got my inspiration from this: GitHub issue.
Searching around the react-native Picker seems bugged.
This quickfix solved my problem https://github.com/facebook/react-native/issues/13351#issuecomment-450281257

Detecting send/submit button in a multi-line TextInput

The React Native TextInput component doesn't support the onSubmitEditing event if it specified as a multi-line input.
Is there a way to detect when the user presses the enter/submit/send (depending on which keyboard layout is specified) button after entering some text?
I realize this is an old post, but I stumbled here from Google and wanted to share my solution. Because of some things that needed to happen in the case of submit, vs simply blurring, I wasn't able to use onBlur to interpret submit.
I utilized an onKeyPress listener to track the Enter key, and then proceeded with the submit. (Note, this is currently only supported in iOS until this PR is merged.)
// handler
onKeyPress = ({ nativeEvent }) => {
if (nativeEvent.key === 'Enter') {
// submit code
}
};
// component
<TextInput
autoFocus={true}
blurOnSubmit={true}
enablesReturnKeyAutomatically={true}
multiline={true}
onChangeText={this.onChangeText}
onKeyPress={this.onKeyPress}
returnKeyType='done'
value={this.props.name}
/>
Note, the blurOnSubmit is still required to prevent the return key from being passed to your onChangeText handler.
On iOS this should work according to the documentation.
Use the onBlur function:
Callback that is called when the text input is blurred
In combination with the ios only blurOnSubmit:
If true, the text field will blur when submitted. The default value is
true for single-line fields and false for multiline fields. Note that
for multiline fields, setting blurOnSubmit to true means that pressing
return will blur the field and trigger the onSubmitEditing event
instead of inserting a newline into the field.
I'll try testing this.
Edit:
Done testing
blurOnSubmit doesn't work like it's supposed to in react-native 0.14.2. Even when set to true, the return/done button and enter key just create a newline and do not blur the field.
I'm not able to test this on newer versions at the moment.
Try it! It works in the middle of the line also!
<TextInput
placeholder={I18n.t('enterContactQuery')}
value={this.state.query}
onChangeText={text => this.setState({ query: text, allowEditing: true })}
selection = {this.state.selection}
onSelectionChange={(event) => this.setState({ cursorPosition: event.nativeEvent.selection, selection: event.nativeEvent.selection, allowEditing: true })}
onSubmitEditing={() => {
const { query, cursorPosition } = this.state;
let newText = query;
const ar = newText.split('');
ar.splice(cursorPosition.start, 0, '\n');
newText = ar.join('');
if (cursorPosition.start === query.length && query.endsWith('\n')) {
this.setState({ query: newText });
} else if (this.state.allowEditing) {
this.setState({
query: newText,
selection: {
start: cursorPosition.start + 1,
end: cursorPosition.end + 1
},
allowEditing: !this.state.allowEditing
});
}
}}
multiline = {true}
numberOfLines = {10}
blurOnSubmit={false}
editable={true}
// clearButtonMode="while-editing"
/>
constructor(props) {
super(props);
this.state = {
query: '',
cursorPosition: [0,0],
selection: null,
allowEditing: true
}
}
constructor () {
super()
this.state = {
text : '',
lastText : '',
inputHeight:40
}
}
writing(text){
this.setState({
text : text
})
}
contentSizeChange(event){
if(this.state.lastText.split("\n").length != this.state.text.split("\n").length){
this.submitTextInput();
}else{
this.setState({
inputHeight : event.nativeEvent.contentSize.height
})
}
}
submitTextInput(){
Alert.alert('submit input : ' + this.state.text);
this.setState({
text : ''
})
}
render() {
return (
<View style={{flex:1,backgroundColor:'#fff'}}>
<TextInput
style={{height:this.state.inputHeight}}
multiline={true}
onChangeText={(text) => this.writing(text)}
onContentSizeChange={(event) => this.contentSizeChange(event)}
onSubmitEditing={() => this.submitTextInput()}
value={this.state.text}
/>
</View>
);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.2/react-dom.min.js"></script>