In react native I want to make a dynamic controller component. But i cant access errors with it. I using "react-hook-form" for form elements. So Its my component :
const {
control,
handleSubmit,
formState: {errors},
setValue,
} = useForm();
const DynamicController = ({req, pattern, name, label}) => {
return (
<>
<Text style={[t.textBase]}>{label}</Text>
<Controller
control={control}
defaultValue=""
rules={{
required: {
value: true,
message: 'Bu alan boş bırakılamaz!',
},
}}
render={({field: {onChange, onBlur, value}}) => (
<Input
errorText={errors[name].message}
error={errors[name]}
onBlur={onBlur}
placeholder={label}
onChangeText={onChange}
value={value}
/>
)}
name={name}
/>
</>
);
};
My Input Component is basicly simple input. My problem is when i give error name like that example i cant access errors.
Its how i use my component :
<DynamicController
label="Email"
name="Email"
pattern={true}
req={true}
/>
When i dont fill the element and log the submit its not showing any error. Its simple passing validate. So what can i do where do i make wrong ? thank you for answerings!!!
Is your Input a custom wrapper? If not, a better way do this using react-hook-form would be:
const {
control,
handleSubmit,
formState: {errors},
setValue,
} = useForm(
defaultValues: {
firstName: '', // form fields should be populated here so that the error can be displayed appropriately
lastName: ''
}
);
const DynamicController = ({req, pattern, name, label}) => {
return (
<>
<Text style={[t.textBase]}>{label}</Text>
<Controller
control={control}
defaultValue=""
rules={{
required: {
value: true,
message: 'Bu alan boş bırakılamaz!',
},
}}
render={({field: {onChange, onBlur, value}}) => (
<Input
onBlur={onBlur}
placeholder={label}
onChangeText={onChange}
value={value}
/>
)}
name={name}
/>
{errors[name] && <Text>This is required.</Text>}
</>
);
};
Related
all,
I am using React-hook-form and yup as my validation tool in my react native project. But currently, I encountered an issue which is the handleSubmit function is not working in my ResetPasswordScreen component. And I cannot figure out why(in LoginScreen which has similar logic and it works fine). I appreciate it if anyone can help.
Here is my execution flow in ResetPasswordScreen, input email -> pass validation -> other inputs are displayed. And I am using isCodeSent param to control if other inputs are shown or not. Here is the PasswordResetScreen code
function PasswordResetScreen() {
const [isCodeSent, setIsCodeSent] = useState(false)
const {
control,
handleSubmit,
formState: { errors },
clearErrors,
reset,
} = useForm({
resolver: resetPwResolver,
})
const onSubmit = (data) => {
console.log('onSubmit ========> ', data)
if (isCodeSent) {
// api call to submit reset request...
} else {
// api call to sent code here...
//after sent code successfully, set isCodeSent param value as true to display the rest inputs
setIsCodeSent(true)
}
}
const dismissKeyboard = () => {
Keyboard.dismiss()
}
console.log('render foget pw page =====>', isCodeSent)
return (
<TouchableWithoutFeedback onPress={dismissKeyboard}>
<View style={styles.passwordResetContainer}>
<Text style={[globalStyles.label]}>
{isCodeSent ? '' : 'A passcode will be sent to your email'}
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={styles.input}
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder='Email'
/>
)}
name='email'
/>
<Text style={styles.errorMsg}>
{errors.email ? errors.email.message : ''}
</Text>
{/* if code is sent, other inputs will be displayed */}
{isCodeSent ? (
<>
<Text>isCodeSent-----{isCodeSent}</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={styles.input}
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder='Passcode'
/>
)}
name='passcode'
/>
<Text style={styles.errorMsg}>
{errors.passcode ? errors.passcode.message : ''}
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={styles.input}
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder='New Password'
/>
)}
name='newPassword'
/>
<Text style={styles.errorMsg}>
{errors.newPassword ? errors.newPassword.message : ''}
</Text>
<Controller
control={control}
render={({ field: { onChange, onBlur, value } }) => (
<TextInput
style={styles.input}
onBlur={onBlur}
onChangeText={onChange}
value={value}
placeholder='Confirm Password'
/>
)}
name='confirmPassword'
/>
<Text style={styles.errorMsg}>
{errors.confirmPassword ? errors.confirmPassword.message : ''}
</Text>
</>
) : null}
<Button
btnText={isCodeSent ? 'Submit' : 'Send'}
onPress={handleSubmit(onSubmit)}
/>
</View>
</TouchableWithoutFeedback>
)
}
export default PasswordResetScreen
here is the code of validation part :
// all the reg are defined before
const resetPwSchema = yup.object().shape({
email: yup
.string()
.matches(emailReg, 'Invalid email format')
.required(),
passcode: yup
.string()
.matches(passcodeReg, 'Invalid passcode format')
.required(),
newPassword: yup
.string()
.matches(passwordReg, 'Password must between 6 to 12 digits')
.required(),
confirmPassword: yup
.string()
.matches(passwordReg, 'Password must between 6 to 12 digits')
.required(),
})
export const resetPwResolver = yupResolver(resetPwSchema)
So, the code in onSubmit function 'console.log('onSubmit ========> ', data)' is not output at the console, which means the handleSubmit function is not working. Also this 'console.log('render foget pw page =====>', isCodeSent)' shows isCodeSend value is false all the time, which indicates the handleSubmit is not working.
I am thinking is it because I break the form into two parts but that does not make sense. I really cannot figure out what is the reason. If anyone can help, that would be so great.
I find the solution for that and figure out the reason for this behavior. The solution is: to define two schemas, one is for only the email field, another schema will include all the files (email, passcode, password, confirmPassword).
Here is my new code for schema:
const resetPwEmailSchema = yup.object().shape({
email: yup
.string()
.matches(emailReg, 'Invalid email format')
.required(requiredErrMsg('Email')),
})
export const resetPwEmailResolver = yupResolver(resetPwEmailSchema)
const resetPwSchema = yup.object().shape({
email: yup
.string()
.matches(emailReg, 'Invalid email format')
.required(requiredErrMsg('Email')),
passcode: yup
.string()
.matches(passcodeReg, 'Invalid passcode format')
.required(requiredErrMsg('Passcode')),
newPassword: yup
.string()
.matches(passwordReg, 'Password must between 6 to 12 digits')
.required(requiredErrMsg('Password')),
confirmPassword: yup
.string()
.matches(passwordReg, 'Password must between 6 to 12 digits')
.required(requiredErrMsg('Password')),
})
export const resetPwResolver = yupResolver(resetPwSchema)
The reason why this happen is when hit 'SEND' button, all the fields will be validated because the schema includes all fields(even other hidden fields), but except email field, other fields are empty(they are not shown), so the validation will fail, so the onSubmit function will never be run.
I want to trigger formik errors and touched whenever the input is clicked and the value is not correct .
I pass formik props to the input component like this :
const initialValues = {
title: ''
};
const validationSchema = yup.object({
title: yup.string().max(50, 'less than 50 words !!').required('required !!')
});
function Add() {
<Formik initialValues={initialValues} onSubmit={onSubmit} validationSchema={validationSchema}>
{(props) => {
return (
<Form>
<AddTitle props={props} />
</Form>
);
}}
</Formik>
);
}
Here I'm trying to display error message whenever the input is touched and there is an error with it like this :
import { Input } from 'antd';
function AddTitle(props) {
console.log(props.props);
return (
<Field name="title">
{() => {
return (
<Input
onChange={(e) => {
props.props.setFieldValue('title', e.target.value)
}}
/>
);
}}
</Field>
<ErrorMessage name="title" />
<P>
{props.props.touched.title && props.props.errors.title && props.props.errors.title}
</P>
</React.Fragment>
);
}
But ErrorMessage and the paragraph below it doesn't work when the input is touched and empty .
In console log it shows that input doesn't handle formik touched method and it only triggers the error for it :
touched:
__proto__: Object
errors:
title: "less than 50 words !"
__proto__: Object
How can I use ErrorMessage properly while passing in formik props to a component and using a third library for inputs ?
Fixed the issue by adding the onBlur to the input and ErrorMessage is working fine :
<Field name="title">
{() => {
return (
<Input
onBlur={() => props.props.setFieldTouched('title')}
onChange={(e) => {
props.props.setFieldValue('title', e.target.value);
}}
/>
);
}}
</Field>
<P class="mt-2 text-danger">
<ErrorMessage name="title" />
</P>
I'm using Formik for validating the fields before creating an entity. There is a Select which opens a dropdown and the user must choose one option from there.
I get an error saying that setFieldValue is not defined but where I've researched before I didn't find any definition of this method, it is just used like that so I don't know why is this happening.
This is my code:
import React from 'react';
import { Formik, Form, Field } from 'formik';
import { Button, Label, Grid } from 'semantic-ui-react';
import * as Yup from 'yup';
class CreateCompanyForm extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
industry: '',
};
}
onChange = (e, { name, value }) => {
this.setState({ [name]: value });
};
handleSubmit = values => {
// ...
};
render() {
const nameOptions = [
{ text: 'Services', value: 'Services' },
{ text: 'Retail', value: 'Retail' },
];
const initialValues = {
industry: '',
};
const requiredErrorMessage = 'This field is required';
const validationSchema = Yup.object({
industry: Yup.string().required(requiredErrorMessage),
});
return (
<div>
<div>
<Button type="submit" form="amazing">
Create company
</Button>
</div>
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={values => this.handleSubmit(values)}>
{({ errors, touched }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={e => setFieldValue('industry', e.target.value)} // here it is
/>
<div>
{touched.industry && errors.industry
? errors.industry
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
</div>
);
}
}
The error says: 'setFieldValue' is not defined no-undef - it is from ESLint. How can be this solved?
setFieldValue is accessible as one of the props in Formik.
I tried it on CodeSandbox, apparently, the first parameter in the onChange prop returns the event handler while the second one returns the value selected onChange={(e, selected) => setFieldValue("industry", selected.value) }
<Formik
htmlFor="amazing"
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={(values) => this.handleSubmit(values)}
>
{({ errors, touched, setFieldValue }) => (
//define setFieldValue
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
id="industry" // remove onBlur warning
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={(e, selected) =>
setFieldValue("industry", selected.value)
}
/>
<div>
{touched.industry && errors.industry
? errors.industry
: null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>
try calling it using this structure
onChange = {v => formik.setFieldValue('field', v)}
<Formik
htmlFor="amazing"
initialValues={initialValues}
value={{industry:this.state.industry}}
validationSchema={validationSchema}
onSubmit={(values) => this.handleSubmit(values)}
>
{({ errors, touched }) => (
<Form id="amazing">
<Grid>
<Grid.Column>
<Label>Industry</Label>
<Field
name="industry"
as={Select}
options={nameOptions}
placeholder="select an industry"
onChange={(e) =>
this.setState({
industry: e.target.value,
})
} // here it is
/>
<div>
{touched.industry && errors.industry ? errors.industry : null}
</div>
</Grid.Column>
</Grid>
</Form>
)}
</Formik>;
Replace your onChange by onChange={this.onChange('industry', e.target.value)}
and in your constructor add this line this.onChange = this.onChange.bind(this).
your onChange methode will be:
onChange(e, { name, value }){
this.setState({ [name]: value });
}
I'm working with react-hook-forms and trying to reset all form fields after submit. The problem is that in my case Autocomplete accepts objects as a value.
I've tried to set the defaultValue to {}, but received the following message:
Material-UI: the getOptionLabel method of Autocomplete returned undefined instead of a string for
{}
Are there any variants how Autocomplete could be reset?
Here is a link to the CodeSandbox
A little late to the party but here's a solution if anyone else needs it.
Assuming your autocomplete is wrapped inside a Controller, all you have to do is explicitly check the value in Autocomplete. Refer below for more
<Controller
name="category"
control={control}
rules={{ required: true }}
render={({ field }) => (
<Autocomplete
fullWidth
options={categories}
{...field}
// =====================================================
// Define value in here
value={
typeof field.value === "string"
? categories.find((cat) => cat === field.value)
: field.value || null
}
// =====================================================
onChange={(event, value) => {
field.onChange(value);
}}
renderInput={(params) => (
<TextField
{...params}
label={t("product_category")}
error={Boolean(errors.productCategory)}
helperText={errors.productCategory && "Product Category is required!"}
/>
)}
/>
)}
/>
This should do the trick!
To reset the value of AutoComplete with reset of react hook form you should add the value prop to the AutoComplete, other ways the reset wont function, see the example:
import { useEffect, useState } from 'react'
import {
Autocomplete,
TextField,
reset,
} from '#mui/material'
...
const {
...
setValue,
control,
formState: { errors },
} = useForm()
useEffect(() => {
reset({
...
country: dataEdit?.country ? JSON.parse(dataEdit?.country) : null,
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataEdit])
...
<Controller
control={control}
name="country"
rules={{
required: 'Veuillez choisir une réponse',
}}
render={({ field: { onChange, value } }) => (
<Autocomplete
value={value || null}
options={countries}
getOptionLabel={(option) => option.name}
onChange={(event, values) => {
onChange(values)
setSelectedCountry(values)
setValue('region', null)
}}
renderInput={(params) => (
<TextField
{...params}
label="Pays"
placeholder="Pays"
helperText={errors.country?.message}
error={!!errors.country}
/>
)}
/>
)}
/>
I am using formik and react-native-formik to create form. I am using my custom component for textinput in formik form.
My custom component code :
// import statements ...
class FormInput extends Component {
focus = () => { this.textInputField.focus(); }
render() {
const {props} = this;
return (
<TextInput
{...this.props}
placeholder={props.placeholder}
ref={(ref) => { this.textInputField = ref }}
style={{ height: 50, borderWidth: 1, margin: 5 }}
/>
)
}
}
export default FormInput
Then I create Form from below code :
const FormInput2 = compose(
handleTextInput,
withNextInputAutoFocusInput
)(FormInput);
const Form = withNextInputAutoFocusForm(View);
Then I have created original form as below :
<ScrollView
style={styles.container}
contentContainerStyle={styles.contentContainer}
keyboardShouldPersistTaps="handled"
>
<Formik
initialValues={{ firstName: '', email: '', password: '' }}
onSubmit={values => { }}
validationSchema={signUpValidationSchema}
render={({ values, handleChange, errors, setFieldTouched, touched, isValid, handleSubmit }) => (
<Form>
<FormInput2
icon="user"
placeholder="First Name"
value={values.firstName}
onChangeText={handleChange('firstName')}
onBlur={() => setFieldTouched('firstName')}
/>
{touched.firstName && errors.firstName && (
<CText style={{ fontSize: 10, color: 'red' }}>{errors.firstName}</CText>
)}
{/*
...
...
Others Fiels
*/}
<Button
title="SIGN UP"
disabled={!isValid}
color={Colors.primary}
onPress={handleSubmit}
/>
</Form>
)}
/>
</ScrollView>
But, I am getting done on each and every field keyboard return type. If I press done then form submit method fires.
Can anyone help me how can I implement auto focus ?
I have also tried exporting my custom component as below, but it is also not working :
export default withFormikControl(FormInput)
If anyone is also having the same error then here is the solution...
Custom component should have name property and the order of the component should be the same as initialValues.
Means if initialValues as below :
initialValues={{ firstName: '', lastName: '', email: '', phone: '', password: '', confirmPassword: '' }}
Then first should be firstName field then lastName, email and so on...
So I added name prop in custom component and worked autofocus.
<FormInput2
icon="user"
placeholder="First Name"
value={values.firstName}
label="First Name"
name="firstName" // added this
type="name"
onChangeText={handleChange('firstName')}
onBlur={() => setFieldTouched('firstName')}
/>
{
touched.firstName && errors.firstName && (
<CText style={{ fontSize: 10, color: 'red' }}>{errors.firstName}</CText>
)
}
<FormInput2
label="Last Name"
name="lastName" // added this
type="name"
icon="user"
placeholder="Last Name"
value={values.lastName}
onChangeText={handleChange('lastName')}
onBlur={() => setFieldTouched('lastName')}
/>
{
touched.lastName && errors.lastName && (
<CText style={{ fontSize: 10, color: 'red' }}>{errors.lastName}</CText>
)
}