Update specific value in Initial Values Formik - reactjs

I am using Formik and Yup validation for my form which has firstname ,lastname & username. Username should be without spaces so I am using a onChange event and value. Yup validation is working for firstname and Lastname but not for username. When logging my values found out that username is not getting updated. I am a newbie please help me out. Thanks in advance.
const CustomTextInput =({label, ...props}) =>{
const [field, meta] = useField(props);
return(
<>
<label className="required" htmlFor={props.id || props.name}>{label}</label>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
):null}
<input className="text-input" {...field} {...props}/>
</>
)
}
function App(){
const [regnum, Setreg]= useState("");
function avoid(e){
Setreg(e.target.value.replace(/\s+/g,''));
}
return(
<Styles>
<Formik
initialValues={{
firstname:'',
lastname: '',
username:'',
phone1:'',
email:''
}}
validationSchema={
Yup.object({
firstname: Yup.string()
.required('Required'),
lastname: Yup.string()
.required('Required'),
username: Yup.string()
.min(4,"Username should be greater than 4 characters")
.max(15,"Wooah! Username cannot be that big")
.required('Required'),
})
}
onSubmit= {(values, { setSubmitting , resetForm }) => {
setTimeout(()=> {
//My api call here
resetForm()
setSubmitting(false);
},2000)
}
}>
{props => (
<Form>
<CustomTextInput label="First Name" name="firstname" type="text" placeholder="first Name"/>
<CustomTextInput label="Last Name" name="lastname" type="text" placeholder="Last Name"/>
<CustomTextInput label="UserName" name="username" type="text" value={regnum} onChange={(event)=>avoid(event)} placeholder="Spaces will be removed"/>
<div>
<button type="submit" >{props.isSubmitting ? "Loading..." : "Submit"}</button>
</div>
</Form>
</Formik>
</Styles>
);
}
export default App;

The question is, do you want to prevent the user from using a username with spaces? If so, the easiest way is to do it through yup.
validationSchema={
Yup.object({
firstname: Yup.string()
.required('Required'),
lastname: Yup.string()
.required('Required'),
username: Yup.string()
.required('Required').matches("/\s/", "Cannot contain spaces"),
})
}
Otherwise, if you allow the user to write the username with spaces, but you want it for whichever reasons without spaces, you have to manipulate the data in the submitHandler.
By giving it a custom onChange handler to username, you overrode formik since formik uses it's onChange under the hood. Manipulate the data in the submitHandler, prior to submitting, let formik do it's thing.

Related

Can't submit multiple step form with yup validations and react-hook-form

I am having troubles with react-hook-form and yup, I need to create a multistep form (there are many other inputs but I just leave a few to give you an example), so!
the stepper works fine until the finish step, when I click "Register" (last step, should submit) it shows the following error:
here is my code:
const RegisterClient = props => {
const [currStep, setCurrStep] = React.useState(1)
const schema1 = Yup.object().shape({
// company
company_name: Yup.string().required("Name required"),
email: Yup.string().email().required("Email required"),
password: Yup.string()
.matches(
passwordRules,
"Password must contain at least 8 digits (one uppercase, one lowercase, one number and a symbol)."
)
.required("Required"),
})
const scheme2 = Yup.object().shape({
// representative
representative_name: Yup.string().required(
"Representative name is required"
),
representative_email: Yup.string()
.email()
.required("Representative email required"),
})
const {
control,
handleSubmit,
formState: { errors },
getValues,
} = useForm({
resolver: yupResolver(currStep === 1 ? schema1 : currStep === 2 && scheme2),
defaultValues: {
company_name: "",
email: "",
password: "",
representative_name: "",
representative_email: "",
},
})
React.useEffect(() => {
console.log(errors)
}, [errors])
function stepper() {
const steps = {
1: <Step1 control={control} errors={errors} />,
2: <Step2 control={control} errors={errors} />,
3: <FinalStep control={control} errors={errors} />,
}
return steps[currStep]
}
function previousStep() {
console.log("previous...")
if (currStep === 1) return
setCurrStep(curr => curr - 1)
}
function nextStep() {
console.log("next: ", getValues())
setCurrStep(curr => curr + 1)
return
}
const onSubmit = async user => {
if (currStep < 3) nextStep()
else {
console.log("user: ", user)
}
}
return (
<React.Fragment>
<Form className="form-horizontal" onSubmit={handleSubmit(onSubmit)}>
{stepper()}
<div className="mt-3 d-flex gap-4 justify-content-between">
<Button
outline
color="dark"
type="button"
onClick={previousStep}
disabled={currStep === 1}
>
Previous
</Button>
<Button color="primary">
{loading ? (
<React.Fragment>
Loading... <Spinner size="sm">Loading...</Spinner>
</React.Fragment>
) : currStep === 3 ? (
"Register"
) : (
"Next"
)}
</Button>
</div>
<div className="mt-4 text-center">
<p className="mb-0">
By registering you agree to the Skote{" "}
<Link to="#" className="text-primary">
Terms of Use
</Link>
</p>
</div>
</Form>
</React.Fragment>
)
}
export default RegisterClient
function Step1({ control, errors }) {
return (
<React.Fragment>
<FormGroup>
<Label for="company_name">Company Name</Label>
<Controller
name="company_name"
control={control}
render={({ field }) => (
<Input
{...field}
placeholder="Company Name"
type="text"
invalid={Boolean(errors.company_name)}
/>
)}
/>
{errors.company_name && (
<FormFeedback>{errors.company_name?.message}</FormFeedback>
)}
</FormGroup>
<Row>
<Col sm="12" md="6" lg="6" xl="6">
<FormGroup>
<Label for="email">Email</Label>
<Controller
name="email"
control={control}
render={({ field }) => (
<Input
{...field}
placeholder="Email"
type="email"
invalid={Boolean(errors.email)}
/>
)}
/>
{errors.email && (
<FormFeedback>{errors.email?.message}</FormFeedback>
)}
</FormGroup>
</Col>
<Col sm="12" md="6" lg="6" xl="6">
<FormGroup>
<Label for="password">Password</Label>
<Controller
name="password"
control={control}
render={({ field }) => (
<Input
{...field}
placeholder="Password"
type="password"
invalid={Boolean(errors.password)}
/>
)}
/>
{errors.password && (
<FormFeedback>{errors.password?.message}</FormFeedback>
)}
</FormGroup>
</Col>
</Row>
</React.Fragment>
)
}
function Step2({ control, errors }) {
return (
<React.Fragment>
<FormGroup>
<Label for="representative_name">Representative name</Label>
<Controller
name="representative_name"
control={control}
render={({ field }) => (
<Input
{...field}
placeholder="Representative name"
type="text"
invalid={Boolean(errors.representative_name)}
/>
)}
/>
{errors.representative_name && (
<FormFeedback>{errors.representative_name?.message}</FormFeedback>
)}
</FormGroup>
<FormGroup>
<Label for="representative_email">Representative email</Label>
<Controller
name="representative_email"
control={control}
render={({ field }) => (
<Input
{...field}
placeholder="Representative email"
type="text"
invalid={Boolean(errors.representative_email)}
/>
)}
/>
{errors.representative_email && (
<FormFeedback>{errors.representative_email?.message}</FormFeedback>
)}
</FormGroup>
</React.Fragment>
)
}
function FinalStep() {
return <h2>This is the final step...</h2>
}
My second option is to have a single schema like this:
const schema = Yup.object().shape({
company: Yup.object().shape({
// company
company_name: Yup.string().required("Name required"),
email: Yup.string().email().required("Email required"),
password: Yup.string()
.matches(
passwordRules,
"Password must contain at least 8 digits (one uppercase, one lowercase, one number and a symbol)."
)
.required("Required"),
}),
representative: Yup.object().shape({
// representative
representative_name: Yup.string().required(
"Representative name is required"
),
representative_email: Yup.string()
.email()
.required("Representative email required"),
})
})
but in this case, I have no idea how to make react-hook-form check for the specific schema inside the main one, am I explain?
I need help please :(
Well, I'm not the guy who just wait for responses... I already figure it out, so here's my solution if someone has the same issue in the future:
const schema = [
Yup.object().shape({
// company
company_name: Yup.string().required("Name required"),
description: Yup.string().required("Description required"),
phone: Yup.string().required("Phone required"),
email: Yup.string().email().required("Email required"),
validateEmail: Yup.string()
.email()
.required("Please confirm your email")
.oneOf([Yup.ref("email")], "Email do not match"),
password: Yup.string()
.matches(
passwordRules,
"Password must contain at least 8 digits (one uppercase, one lowercase, one number and a symbol)."
)
.required("Required"),
validatePassword: Yup.string()
.required("Please confirm your password")
.oneOf([Yup.ref("password")], "Passwords do not match"),
}),
Yup.object().shape({
// representative
representative_name: Yup.string().required(
"Representative name is required"
),
representative_position: Yup.string().required(
"Representative position is required"
),
representative_email: Yup.string()
.email()
.required("Representative email required"),
representative_phone: Yup.string().required("Phone required"),
}),
Yup.object().shape({
line_business: Yup.string().required(),
status: Yup.string().required(),
user_type: Yup.string().required(),
}),
]
const {
control,
handleSubmit,
formState: { errors },
getValues,
} = useForm({
resolver: yupResolver(schema[currStep - 1]), // this is because my steps start with 1, and the schema array start with position 0
defaultValues: {
company_name: "",
description: "",
phone: "",
email: "",
validateEmail: "",
password: "",
validatePassword: "",
representative_name: "",
representative_position: "",
representative_email: "",
representative_phone: "",
line_business: "1",
status: "2",
user_type: userType,
},
})
NOTICE: that in my case, the last object in the schema array check validation for values that I have defined with default values because I don't need to change those, I think that if you just add any validation without the .required() should work as well 🤷🏻‍♂️

react-hook-form use mutiple resolver

i am trying to create a general input like this
const TextInput = ({
name,
register = () => {},
errors,
serverErrors,
...props
}) => {
return (
<div >
<input
type="text"
{...register(name, {
pattern: { value: /^[A-Za-z]+$/i, message: "Invalid Input" },
})}
{...props}
/>
{errors?.[name] && (
<span className="text-errorColor">{errors?.[name]?.message}</span>
)}
</div>
);
};
I will use this input in form and use Yup to validate this form
const schema = yup
.object({
first_name: yup
.string()
.required("This field is required")
.max(20, "max 20 characters"),
})
.required();
const SignupForm = ({ signUpContact, signUpType }) => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<TextInput
name="first_name"
register={register}
errors={errors}
serverErrors={error}
placeholder="First Name"
/>
</form>
);
};
but the problem is that the validations in TextInput Competent aren't running
i think i can't use Register Validation with Yup validation.
as you see I won't duplicate validation [A-Za-z] every time I use TextInput, is there any way to do this?
For input just register the field using react-hook-form
<input
type="text"
{...register(name)}
{...props}
/>
Use yup to handle all the validation logic
yup
.string()
.trim()
.matches(/^[A-Za-z]+$/i , 'Invalid Input')
.max(20, "max 20 characters"),
.required();

How to chain on custom validations using Yup

I want to add a .test() method to a validationSchema. The test is to look for common email domain misspellings. For instance, to check if gmail was misspelled "gmial" or "gnail".
const validationSchema = Yup.object({
email: Yup.string().lowercase()
.email('You have entered an invalid email')
.required('This field is required')
.test('test-name', 'Did you mean #gmail??????', function(value){
if (value.includes('gmial' || 'gnail'))
return this.createError('')
})
<Formik>
<Form>
<div className='form-control'>
<label htmlFor='email'>Email</label>
<Field type='text' id='email' name='email' />
<ErrorMessage name='email' component={TextError} />
</div>
</Form>
</Formik>
Return a boolean result in your test.
const validationSchema = Yup.object({
email: Yup.string()
.lowercase()
.email('You have entered an invalid email')
.required('This field is required')
.test('Gmail?', 'Did you mean #gmail?', (value) => value.includes('gmial' || 'gnail')),
});

onchange in a form using formik the value of the field is not updated

I'm new to react, and I'm trying to apply validations to a form.
For some reason when adding the property:
onChange={onChange}
I want to send the values to the parent component. That's why I'm using the onchange.
Nothing I write is shown in my text fields, why does this happen?
export const Son = props => {
const { onChange } = props;
return (
<Formik
initialValues={{
fullname: "",
email: ""
}}
validationSchema={Yup.object().shape({
fullname: Yup.string()
.min(2, "Your name is too short")
.required("Please enter your full name"),
email: Yup.string()
.email("The email is incorrect")
.required("Please enter your email")
})}
onSubmit={(values, { setSubmitting }) => {
const timeOut = setTimeout(() => {
console.log(values);
setSubmitting(false);
clearTimeout(timeOut);
}, 1000);
}}
>
{({
values,
errors,
touched,
handleSubmit,
isSubmitting,
validating,
valid
}) => {
return (
<Form name="contact" method="post" onSubmit={handleSubmit}>
<label htmlFor="fullname">
Fullname
<Field
type="text"
name="fullname"
autoComplete="name"
placeholder="your fullname"
onChange={onChange}
/>
</label>
{<ErrorMessage name="fullname">{msg => <p>{msg}</p>}</ErrorMessage>}
{/*errors.fullname && touched.fullname && <p>{errors.fullname}</p>*/}
<br />
<label htmlFor="email">
Email
<Field
type="email"
name="email"
autoComplete="email"
placeholder="your email"
onChange={onChange}
/>
</label>
<ErrorMessage name="email">{msg => <p>{msg}</p>}</ErrorMessage>
<br />
<button type="submit" disabled={!valid || isSubmitting}>
{isSubmitting ? `Submiting...` : `Submit`}
</button>
</Form>
);
}}
</Formik>
);
};
this is my live code:
https://stackblitz.com/edit/react-qotvwb?file=components/son_component.js
you're not using the formik handleChange at all.
I highlighted the changes that I made in https://stackblitz.com/
and you can test this working here

Formik onSubmit function is not working on my code

I am creating a form by using react and formik.Below is my code:
<div>
<Formik
initialValues={{
email: ""
}}
onSubmit={(values: FState, setSubmitting: any) => {
console.log("Enter in submit function", values);
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
validationSchema={validationSchemaGlobal}
>
{({
errors,
touched,
handleBlur,
handleChange,
isSubmitting,
values,
handleSubmit
}: any) => (
<div>
<Cards>
<form onSubmit={handleSubmit}>
<CardBody>
<h4>
Enter Your Email Address and we'll send you a link to reset your
password
</h4>
<Field
type="text"
id="email"
value={values.email}
component={CustomInput}
onChange={handleChange}
onBlur={handleBlur}
/>
{errors.email && touched.email ? (
<div style={{ color: "red" }}>{errors.email}</div>
) : null}
</CardBody>
<CardFooter>
<br />
<button type="submit" disabled={isSubmitting}>
Send Password Reset Link
{/* {isSubmitting && <i className="fa fa-sponner fa-spin"/>} */}
</button>
</CardFooter>
</form>
</Cards>
</div>
)}
</Formik>
</div>
In this formik form, onSubmit function not working. I dont know why? Please tell me guys what is problem with my code?
Check your validationSchema. I ran into this problem and found that my validator was returning something that signaled to Formik the form was invalid, but no other warnings or messages were coming up. It just wouldn't submit.
Replace that prop with validator={() => ({})} i.e. just an empty object being returned. That should pass validation and trigger your onSubmit. You can restore your functionality from there.
<Formik
initialValues={{
email: ""
}}
onSubmit={() => { console.log("submit!"); }}
validator={() => ({})}
>
{/* */}
</Formik>
In my case I use Yup as validator and I accidentally had firstName and lastName in my validationSchema as required but I did not have those values in my form.
My validationSchema was,
const SignupSchema = Yup.object().shape({
firstName: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
lastName: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
email: Yup.string()
.email('Invalid email')
.required('Required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.max(24, 'Password can be maximum 24 characters')
.required('Required')
})
I just deleted firstName and lastName,
const SignupSchema = Yup.object().shape({
email: Yup.string()
.email('Invalid email')
.required('Required'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.max(24, 'Password can be maximum 24 characters')
.required('Required')
})
So check your validationSchema and see if you require something that does not exist in your form.
I imported Form from react-bootstrap instead of formik, so I was having this issue. The issue was solved by importing the Form of formik. Sometimes, directly using Form.Control of react-bootstrap instead of Field of formik also gives this issue.
If you really have to use Form.Control you can use render prop.
A little bit late for the original question but I experienced the same issue and solved it easy but hard to find.
When I passed the "name" prop to the component I had written "DateOfBirth" instead of with lowercase, which means it didn't match my validationSchema.
My schema looks like this:
export const userSchema = yup.object().shape({
firstName: yup.string().min(1).max(50).required('Field is required'),
lastName: yup.string().min(1).max(50).required('Field is required'),
dateOfBirth: yup.date().required('Invalid input'),});
This menas the name of the component has to match
Before (Didn't work):
<DateTimePicker name="DateOfBirth" label="Date of birth" />
After (Worked):
<DateTimePicker name="dateOfBirth" label="Date of birth" />
In my case, onSubmit was not working because I forgot to wrap my form in the <form></form> tag. A stupid issue, but it can be the reason for this behavior. If the above solutions don't work, check that you have the form tag.
In my case, mistakenly I have passed validationSchema to wrong prop.
Error:
<Formik
initialValues={initialValues}
validate={validationSchema} <----- Error
>
Proper way:
<Formik
initialValues={initialValues}
validationSchema={validationSchema} <----- Good
>
This may happen because the form is being submitted but it is invalid , this may happen because the validation schema is not matching ur form for more than one reason ,
in my case , it was because there was a string , and it is been sent as null , so I just added .nullable() to the validation schema for that field.
Had extra field in my validationSchema which was declared with Yup. however that field wasn't declared in Formik hence it didn't work. After removing the field from validationSchema, it works.
I am mentioning one more possibility through which i handled.
change the button type and add onClick like this
<Button type="button" onClick={submitForm}>
also add submitForm prop at top along with values, touched etc
{({ submitForm, errors, handleChange, handleSubmit, touched, values }) => (
now its working
My mistake was I was not initializing error with blank on validation
const errors:any={};
Here is full code for login form, check the validate function
<Formik
initialValues={{ email: "", password: "" }}
validate={(formValues) => {
const errors:any={};
if (!formValues.email) {
errors.email = "Invalid email";
}
if (!formValues.password) {
errors.password = "Password is required";
}
return errors;
}}
onSubmit={async (values) => {
console.log("submit", values);
dispatch(login({ username: values.email, password: values.password }));
if (loginState.isError) {
alert(loginState.message);
}
}}
>{({ values, handleChange, errors, dirty, isValid, isSubmitting, handleSubmit, setFieldValue, setFieldTouched, setFieldError }) => (
<Form onSubmit={handleSubmit}>
<FormGroup>
<Label>Email</Label>
<Input type="email" name="email" valid={!isEmpty(errors.email)} value={values.email} onChange={handleChange}></Input>
<FormFeedback className="font-weight-bold" type="invalid" role="alert"> {errors.email}</FormFeedback>
</FormGroup>
<FormGroup>
<Label>Password</Label>
<Input type="password" name="password" value={values.password} valid={!isEmpty(errors.password)} onChange={handleChange}></Input>
<FormFeedback className="font-weight-bold" type="invalid" role="alert"> {errors.password}</FormFeedback>
</FormGroup>
<FormGroup className="text-center">
<p> {isValid === true ? "is valid" : "not valid"} </p>
<Button type="submit" color="primary" className="mt-3">Login</Button>
</FormGroup>
</Form>
)}
</Formik>
I solved this because I declared the onsubmit function without the const word (I know it's stupid)
I was having the same issue. My onSubmit function was not executing onClick on submit button.
The problem was in Yup.validation schema. There was an extra field that I did not use. I remove that field and boom.
Posting this as I had the same pronlem and the problem was even different:
My validation function was returning an errors object that always contained all fields, all with empty strings when they were correct.
Form submission seems disabled when the errors object is not empty.
In my case, the issue was Button component was outside the Formik component
<Formik initialValues={} validate={validate} onSubmit={handleSubmit}>
<Form>
</Form>
</Formik>
<Button type="submit">Submit</Button>
Moving the Button inside Form solved my issue
<Formik initialValues={} validate={validate} onSubmit={handleSubmit}>
<Form>
<Button type="submit">Submit</Button>
</Form>
</Formik>
Use instead of button tag as i worked for me.

Resources