I'm building a React Native app and using the tomb-form-native library for my forms. In one of my screens, I loop through an array of types and output a form for each type:
{my_types.map(ob =>
<View key={++i}>
<Text>{ob.type} #{ob.num}</Text>
<Form
ref={(c) => {
this.form = {}
this.form[ob.type] = c
}}
type={_formType(this, ob.type)}
options={_formOptions(ob.type)}
value={this.state.value}
onChange={this.onChange.bind(this)}
/>
</View>
)}
<TouchableHighlight style={styles.button} onPress={this.onPress.bind(this)}>
<Text style={styles.buttonText}>Submit</Text>
</TouchableHighlight>
But when I try to get the submitted values in my onPress function, it doesn't work for multiple types. It works for one type if I only call getValue() once:
input = this.form['my_type'].getValue()
console.log(input) // I see in my debugger that it works.
But if I try to get the input for two or more types, I don't see anything in the log...
input = this.form['my_type'].getValue()
console.log(input) // Nothing. It doesn't work.
input2 = this.form['my_other_type'].getValue()
console.log(input2) // Nothing here either.
Is it possible to use the tcomb library to submit multiple forms with one onPress? Maybe it's the way I call my onPress function in the onPress property of TouchableHighlight?
UPDATE
This simplified onPress function suggests my form ref is only working the last time through the loop. If my loop has two items...
onPress() {
let input = this.form[1]
console.log(input) // Undefined.
let input2 = this.form[2]
console.log(input2) // Object.
}
It appears to be possible. If I use an array to track the form refs, it works:
this.form = []
return (
...
{a.map(ob =>
<View key={++i} style={s}>
<Text>{ob.type} #{ob.num}</Text>
<Form
ref={(c) => {
this.form.push(c)
}}
key={i}
type={_formType(this, ob.type)}
options={_formOptions(ob.type)}
value={this.state.value}
onChange={this.onChange.bind(this)}
/>
</View>
)}
<TouchableHighlight style={styles.button} onPress={this.onPress.bind(this)}>
<Text style={styles.buttonText}>Submit</Text>
</TouchableHighlight>
And here is a simplified onPress...
onPress() {
let tF = this.form
tF.forEach(function(f) {
if (f) { // First two times through the loop, f is null, in my application.
console.log(f.getValue()) // It works!
}
})
}
Related
I tried creating a reusable component in React. Which has a textInput and secure text entry handled in the state of the reusable component. But the state is not getting maintained differently when reusing always the last state is updated,
Issue: If i call the reusable const two times on a single screen or on the next screen in stack. The toggle for secure entry keeps changing for the last field loaded and earlier loaded fields state is lost.
i.e., when i click on toggle of Password, text change from hidden to visible or vice-versa happens for Confirm password field.
This is how i call
<View style={styles.inputContainerView}>
<InputTextFieldView
enteredText={passwordEntered}
setEnteredText={setPasswordEntered}
title={Constants.registerPasswordPlaceholder} icon={lockIcon}
isSecureEntry={true}
placeholder={Constants.registerPasswordPlaceholder} />
</View>
<View style={styles.inputContainerView}>
<InputTextFieldView
enteredText={confirmPasswordEntered}
setEnteredText={setConfirmPasswordEntered}
title={Constants.registerConfirmPasswordPlaceholder} icon={lockIcon}
isSecureEntry={true}
placeholder={Constants.registerConfirmPasswordPlaceholder} />
</View>
My component:
const InputTextFieldView = ({ enteredText, setEnteredText, title, icon, isSecureEntry, placeholder }) => {
const [isSecureEntryEnabled, setIsSecureEntryEnabled] = useState(isSecureEntry)
const eyeOpenIcon = require('../../../assets/visibility.png')
const eyeCloseIcon = require('../../../assets/close-eye.png')
useEffect(() => {
console.log('called')
}, [])
toggleSecureTextEntry = () => {
console.log('title', title)
setIsSecureEntryEnabled(!isSecureEntryEnabled)
}
return (
<View style={styles.fieldsContainerView}>
<Text style={styles.titleStyle}>{title}</Text>
<View style={[styles.fieldInputContainerView, {padding: Platform.OS === 'ios' ? 12 : 0}]}>
<Image source={icon} style={styles.fieldIconView} />
<TextInput secureTextEntry={isSecureEntryEnabled} style={{ width: isSecureEntry ? '75%' : '85%' }} onChange={() => setEnteredText()} value={enteredText} placeholder={placeholder} />
{isSecureEntry &&
<TouchableWithoutFeedback onPress={() => toggleSecureTextEntry()}>
<Image source={isSecureEntryEnabled ? eyeOpenIcon : eyeCloseIcon} style={styles.fieldIconView} />
</TouchableWithoutFeedback>
}
</View>
</View>
)
}
I'm guessing that you are using isSecureEntry as the hook to toggle the password fields? If so, it looks like you are passing the same state to both
the password field and the confirm password field. Right now, you essentially have one light switch that controls two different lamps. So you are going to want to have separate separate useState hooks for the password field and confirm password field. Then pass each one to the correct component.
const [passwordSecure, togglePasswordSecure] = useState(true);
const [confirmPasswordSecure, toggleConfirmPasswordSecure] = useState(true);
const togglePasswordField = () => {
togglePasswordSecure(!passwordSecure)
};
const toggleConfirmPasswordField = () => {
toggleConfirmPasswordSecure(!confirmPasswordSecure)
};
Issue was happening due to TouchableWithoutFeedback. Now used TouchableOpacity and it started to work. Not sure why but it may help someone
I have rendered a flat list of multiple text inputs and an icon element in a react native component. What I'm trying to achieve is on click of any of the edit icon, I need to focus the correspondent text input. From the past experiences, I know using refs we can achieve this. However, how can I generate dynamic refs based on an index or any other value and focus to the correspondent text input?
const renderItem = ({ item,index }) => {
return (
<View style={styles.item} key={index}>
<View style={styles.data}>
<TextInput
value={item.name}
ref={(input) => {
this.inputRef[item.name] = input;
}}
/>
<TouchableOpacity onPress={this.editName.bind(this, item.name,index)}>
<Icon
name="pencil"
size={20}
/>
</TouchableOpacity>
</View>
</View>
)
}
Function to focus to input:
editName = (name,index) => {
this.inputRef[name].focus();
}
Error: Can't set property 'someName' of undefined. The app is crashing even before the onClick event happens.
Thanks!
I wouldn't get mixed with the bind function.
If you identify your inputs by their name, stick with this when you apply the editName function as well:
function App() {
const names = ['1', '2', '3'];
const inputRef = useRef([]);
// Note this returns a function, that uses the relevant `name` value
const editName = (name) => () => inputRef.current[name].focus();
return (
<div className="App">
{names.map((name) => (
<div key={name}>
<input
type="text"
ref={(input) => {
inputRef.current[name] = input;
}}
/>
<button onClick={editName(name)}>{`Edit ${name}`}</button>
</div>
))}
</div>
);
}
You can check it online as well
I have a Formik form where I take user input input & run a query after submitting.
When I click on the search button, a list is rendered. All list items have a button of their own. When I click on the green add button (from the list items) for the first time, the button does not work. Console log's content is not printed. Instead, the onBlur event of the inputField is triggered. However, if I click on the + button again, then it works and prints no. This problem is visible in simulators/phones, not on the web mode on the sandbox.
export const AddFriendScreen: React.FunctionComponent = () => {
const initialValues: FormValues = {
input: '',
};
const [showFlatList, setShowFlatList] = useState<UsersLazyQueryHookResult>(
'',
);
const handleSubmitForm = (
values: FormValues,
helpers: FormikHelpers<FormValues>,
) => {
loadUsers({
variables: {
where: {
OR: [
{ phoneNumber: newPhoneNumber },],
},
},
});
helpers.resetForm();
};
return (
<SafeAreaView style={styles.safeAreaViewContainer}>
<View style={styles.searchTopContainer}>
<View style={styles.formContainer}>
<Formik
initialValues={initialValues}
onSubmit={handleSubmitForm}
validationSchema={validationSchema}>
{({ handleChange, handleBlur, handleSubmit, values }) => (
<View style={styles.searchFieldContainer}>
<View style={styles.form}>
<FieldInput
handleChange={handleChange}
handleBlur={handleBlur}
value={values.input}
fieldType="input"
icon="user"
placeholderText="E-Mail oder Telefonnummer oder Name"
/>
<ErrorMessage
name="input"
render={(msg) => <ErrorText errorMessage={msg} />}
/>
</View>
<View style={styles.buttonContainer}>
<ActionButton buttonText="Suchen" onPress={handleSubmit} />
</View>
</View>
)}
</Formik>
</View>
<View style={styles.listHolder}>
{data && showFlatList !== null && (
<UsersFoundList
data={data}/>
)}
</View>
</View>
</SafeAreaView>
);
};
Snack Expo:
https://snack.expo.io/#nhammad/jealous-beef-jerky
[![enter image description here][1]][1]
Edit:
Found a workaround but still open to easier solutions. Still haven't figured out what's causing the issue exactly.
However, this causes the formik error to show up immediately after the form is submitted and data is returned/rendered. For instance, the error suggests that the input field should not be empty.
...
Why is the keyboard interfering in this?
Probably because when you call Keyboard.dismiss() you are actually unfocusing the input, which will trigger the validateOnBlur event.
So when you unfocus a field (blur), it will try to validate (validateOnBlur), and because the field is empty, it will show the validation.
The error should only show up when we are trying to resubmit the form without an input.
If the error should only show when you submit the form, you should pass to the Formik component validateOnBlur={false}. With this, it won't show the error message when you call Keyboard.dismiss(), because it will remove the validation on blur.
But it still validates when the input changes, triggering validateOnChange. You can also pass to the Formik component validateOnChange={false} to disable the validation on change and it will only validate when you submit the form (press the button).
Please notice that validateOnChange and validateOnBlur are true by default.
Edit:
You need to add validateOnBlur={false} but instead of calling Keyboard.dismiss() you need to call fieldRef.current.blur(). But for that to work, you need to use React.forwardRef in your input component.
So you need to create the fieldRef
const fieldRef = useRef();
Pass to the component
<FieldInput
ref={fieldRef} // passing the ref
handleChange={handleChange}
handleBlur={handleBlur}
value={values.input}
fieldType="input"
icon="user"
placeholderText="input"
/>
And you need to wrap around your FieldInput with React.forwardRef and pass the ref to the Input component.
// wrapping the component with React.forwardRef
export const FieldInput: React.FunctionComponent<FieldInputProps> = React.forwardRef(({
handleChange,
handleBlur,
fieldType,
placeholderText,
value,
style,
rounded,
//ref,
}, ref) => {
return (
<Item rounded={rounded} style={[styles.inputItem, style]}>
<Input
ref={ref} // passing the ref
autoFocus={true}
autoCapitalize="none"
style={styles.inputField}
placeholder={placeholderText}
keyboardType="default"
onChangeText={handleChange(fieldType)}
onBlur={handleChange(fieldType)}
value={value}
/>
</Item>
);
});
And now in the handleSubmitForm you call fieldRef.current.blur()
const handleSubmitForm = (
values: FormValues,
helpers: FormikHelpers<FormValues>,
) => {
// other logic from your submit
if (fieldRef && fieldRef.current)
fieldRef.current.blur();
};
With this, you will solve the problems of your question and from the comments.
Working example
Explanation about useRef
We need to use the useRef hook so we can get the element of the Input component to be able to call the blur function so don't be focused in the input and can click in the plus button.
We need to use the useRef hook because that is how you create a ref in functional components.
This is my function for rendering items in a flatlist
renderItem = ({ item }) => {
var integer = Number(item.key)
return (
<View>
<Text style={styles.row}>
{item.text}
</Text>
<View style={{flexDirection:'row'}}>
{this.createButtonYes(integer)}
{this.createButtonNo(integer)}
{this.answer(this.state.buttonStates[integer])}
</View>
<Text >__</Text>
</View>
)
}
And the problem I am facing is the function this.answer is not being called when the state of buttonStates changes
answer = (val) => {
if(val){
return(
<Text style={{fontSize:20}}>YES</Text>
)
}else if(val==false){
return(
<Text style={{fontSize:20}}>NO</Text>
)
}else{
return(
<Text style={{fontSize:20}}>Not Answered</Text>
)
}
}
I assumed that every time the state changes the function would be called but that does not seem to be the case, so does anyone have a solution? What I want is whenever the buttons are pressed the state will change and then this.answer will take the changed state and display what it has to accordingly.
Thanks
EDIT:
Code for the button:
buttonYesHelp = num =>{
const newItems = [...this.state.buttonStates];
newItems[num] = true;
return newItems
}
createButtonYes = (num) => {
return(
<TouchableOpacity style={styles.buttonYes}
onPress =
{
()=> {{this.setState({ buttonStates:this.buttonYesHelp(num) })}}
}>
<Text style={styles.buttonTextStyle}>YES</Text>
</TouchableOpacity>
)
}
num is the index of the thing I want to change in the list
EDIT:
I have tried multiple different things but the problem I keep running into is that when I render the button I want it to react to a state variable but it never seems to change based on the state even when the state is changing.
For example, in this.answer I assumed that it would return the text based on the state of buttonStates but it seems to only account for the initial state and nothing after
I was able to achieve this in a different piece of code with identical syntax but for some reason this is not working
Summary
I have a react native functional component where I collect a value from the user in TextInput and then need to pass that value to a function when a button is pressed. I know how to do this in a react native class component with state, however I'm trying to use a functional component.
I'm close to figuring it out but I'm just missing something. The code I have so far is below.
saveUserInput handles saves the text from the user and returns input. When I pass saveUserInput into forgotPassword, which is my function that sends userInput to my backend to reset password, saveUserInput is defined as the function as opposed to the value it returns. How can make this work as a functional component?
Code
export default (ForgotPasswordScreen = () => {
const saveUserInput = userInput => {
const input = userInput;
return input;
};
return (
<View style={styles.container}>
<Text style={styles.headerText}>Forgot Password</Text>
<Text style={styles.forgotText}>
Enter your username or email address below to reset your password
</Text>
<TextInput
onChangeText={userInput => saveUserInput(userInput)}
placeHolder={"username or email"}
/>
<Text
style={styles.buttonText}
onPress={saveUserInput => forgotPassword(saveUserInput)}
>
Forgot Password Button
</Text>
</View>
);
});
export default (ForgotPasswordScreen = () => {
let input = '';
const saveUserInput = userInput => {
input = userInput;
};
return (
<View style={styles.container}>
<Text style={styles.headerText}>Forgot Password</Text>
<Text style={styles.forgotText}>
Enter your username or email address below to reset your password
</Text>
<TextInput
onChangeText={userInput => saveUserInput(userInput)}
placeHolder={"username or email"}
/>
<Text
style={styles.buttonText}
onPress={() => forgotPassword(input)}
>
Forgot Password Button
</Text>
</View>
);
});
This should do the trick without using hooks. Pull out the input variable to be in scope for all other components to use.
But life could be much easier using useState hooks.