Formik errors object is not being updated properly on input - reactjs

coming into a new codebase here and it's also my first time using Formik and Yup so I will try to be as concise as possible, but bear with me.
I have some forms that were built with Formik and use a validation schema provided by Yup. I also have a useState variable to indicate whether or not an item is present in Yup's errors object that is used to prevent the user from continuing to the next form until the error is fixed. The validation works correctly initially, as when an invalid input is provided, the corresponding error is shown in the errors object. However, if I type something valid into the field and then go back and change it so that it's invalid, the errors object doesn't update to reflect that new error until the next input event, which throws off the value of the state variable checking it. A basic example:
Enter Email: testgmail.com ---> errors: {email: "Invalid email"}
Enter Email: test#gmail.com ---> errors: {}
Enter Email: test#gmail ---> errors: {}
It's not until I trigger the next input that errors populates back to {email: "Invalid email"}.
Any idea how to fix this problem? I will try to supply some cleaned up code below, but again apologies as it's a brand new codebase to me so I'm not sure how useful it will be.
const validate = Yup.object().shape({
name: Yup.string().required("This field is required"),
email: Yup.string()
.email("Invalid email format")
.required("Your email is required"),
code: Yup.string().required("This field is required"),
});
function handleInputChange(e: any, errors: FormikErrors, errorSetter: any, codeSetter: any, codeInfo: any) {
codeSetter({ ...codeInfo, [e.target.name]: e.target.value });
if (Object.keys(errors).length > 0) {
errorSetter(true);
}
else errorSetter(false)
}
<Formik
initialValues={{
name: codeInfo.name,
email: codeInfo.email,
code: codeInfo.code,
}}
onSubmit={() => {}}
validationSchema={validate}
>
{({
handleSubmit,
handleChange,
values,
errors,
touched,
handleBlur,
}) => (
<form className={styles.form} onSubmit={handleSubmit}>
<div className={styles.formTextfields}>
<div className={styles.textfields}>
<span className={styles.tag}>Email*</span>
<TextField
name="email"
value={values.email}
placeholder="test#gmail.com"
onChange={handleChange}
onBlur={handleBlur}
onInput={(e: any) => handleInputChange(e, errors)}
variant={
errors.email && touched.email ? "error" : "focus"
}
/>
{errors.email && touched.email ? (
<div className={styles.error}>{errors.email}</div>
) : null}
</div>
</form>
)}
</Formik>

Maybe there's something I am missing but I don't understand how that handleInputChange function is working... You can just try to remove that prop.
Formik is already handling field change and field blur with the handleChange and handleBlur handlers so it is already making validation checks.
Here's some working code you might use (I've removed name and code fields as they don't appear in any field on your code)
const validate = Yup.object().shape({
email: Yup.string()
.email("Invalid email format")
.required("Your email is required"),
});
const Component = () => {
return (
<Formik
initialValues={{
email: "",
}}
onSubmit={() => {}}
validationSchema={validate}
>
{({
handleSubmit,
handleChange,
values,
errors,
touched,
handleBlur,
}) => {
return (
<form onSubmit={handleSubmit}>
<div>
<span>Email*</span>
<TextField
name="email"
value={values.email}
placeholder="test#gmail.com"
onChange={handleChange}
onBlur={handleBlur}
/>
{errors.email && touched.email ? <div>{errors.email}</div> : null}
</div>
</form>
);
}}
</Formik>
);
};

Related

Validate form field by onChange immediately, not after additional manipulations

I have a simple form field with formik & yup validation.
The form is submitted by changing the form field. The field is of type text, but only numbers must be entered.
In this case, if a number exceeding 10 is entered, an error should be displayed and the form should be banned.
The problem is that the field only works after the blur.
If I enter one character (onChange) - field is not valid and "Required". Only after blur or type second symbol - valid.
const schema = yup.object().shape({
name: yup
.string()
.trim()
.required('Required')
.test('amount', 'Test errror', (val) => {
console.log('S1>>>' + val);
return parseFloat(val) <= 10;
}),
});
const handleSubmit = useCallback(async (values) => {
try {
console.log('Success');
} catch (e) {
console.log('Unhandled login error :', e);
}
}, []);
<Formik
validateOnChange
initialValues={{ name: '' }}
validationSchema={schema}
onSubmit={handleSubmit}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
}) => (
<Form className="form" onChange={handleSubmit}>
<div className="mb-4">
<Form.Group className="position-relative">
<div className="position-relative">
<Form.Control
type="text"
name="name"
className={`${touched.name ? 'is-touch ' : ''} ${
errors.name && touched.name ? ' is-invalid' : ''
} ${!errors.name && touched.name ? ' is-valid' : ''}`}
value={values.name.replace(/\D/g, '')}
onBlur={handleBlur}
onChange={handleChange}
placeholder="Name"
touched={touched.name}
/>
</div>
{errors.name && (
<Form.Control.Feedback type="invalid" className="d-block">
{errors.name}
</Form.Control.Feedback>
)}
</Form.Group>
</div>
</Form>
)}
</Formik>
So, How to validate a form field already from the first character and without blur (only onChange) and if I type (without blur) > 10 - show error?
Show error immediately, not after additional manipulations.
In the documentation, there are some props for these kind of manipulations.You can check setFieldTouched or validateField prop on this page:
https://formik.org/docs/api/formik

React: Yup conditional validation only when a field is visible

I'm trying to implement form validation using formik & yup in React. I have a login/register form with three fields (name,email,password). Name field is conditionally rendered when 'create an account' button is clicked. I want the name field to be required only when form is in register state. I'm using a state variable isLogin to save the form's current state, also using it to initialise showName boolean in formik's initialValues. Right now, name field has no validation applied on it and form can be submitted if name field is empty.
My Code
const [isLogin, setIsLogin] = useState(true);
const initialAuthFormValues = {
hideName: isLogin,
name: "",
email: "",
password: "",
};
My Validation Schema
const authFormValidationSchema = Yup.object().shape({
hideName: Yup.boolean(),
name: Yup.string().when("hideName",{
is: false,
then: Yup.string().required("Name is required"),
}),
email: Yup.string().required("Email is required"),
password: Yup.string().required("Password is required"),
});
My Component looks like this
<Formik
initialValues={initialAuthFormValues}
validationSchema={authFormValidationSchema}
onSubmit={submitHandler}
>
{(formik) => (
<Form>
{!isLogin && (
<TextField
label="Name"
type="text"
name="name"
placeholder="Name"
/>
)}
<TextField
label="Email"
type="text"
name="email"
placeholder="Email"
/>
<TextField
label="Password"
type="password"
name="password"
placeholder="Password"
/>
<div className={classes.actions}>
<Button type="submit"> {isLogin ? "Login" : "Create Account"} </Button>
<Button
type="reset"
className="toggle"
onClick={() => { setIsLogin((prevState) => !prevState); }}
>
{isLogin ? "Create new account" : "Login with existing account"}
</Button>
</div>
</Form>
</section>
)}
</Formik>
You can use authFormValidationSchema as a state , and update this state in case that button clicked :
const authFormValidationSchema = {
email: Yup.string().required("Email is required"),
password: Yup.string().required("Password is required"),
};
const [authFormValidationSchema, setAuthFormValidationSchema] = useState(Yup.object());
track the status when button is clicked in a useEffect, and update state of authFormValues accordingly :
useEffect(()=>{
let authFormValidationSchema_ = JSON.parse(JSON.stringify(authFormValidationSchema))
if(!buttonIsClicked)
setAuthFormValidationSchema(Yup.object(authFormValidationSchema_));
else setAuthFormValidationSchema(Yup.object({...authFormValidationSchema_, name: Yup.string().required("Name is required") }));
},[buttonIsClicked]);
Finally found the solution to the problem : In my code, action is my prop
First, create a variable in your JS that will hold the logic:
let passwordTest
if (action === "Register") {
passwordTest = {
password2: Yup
.string().trim()
.required("Please confirm your password")
.oneOf([Yup.ref('password'), null], 'Passwords must match')
} else {
passwordTest = null
}
Basically above you are saying that if the props is Register (if this
form is rendered for registering purpose and not login), then create
the field apply the validation listed, otherwise, this field will be
null.
Then, in your Yup schema destructure your variable :
const validationSchemaYup = Yup.object().shape({
other fields validation logic...
...passwordTest,
other fields validation logic...
Here you add your new custom validation logic, don't forget to destructure!

Update specific value in Initial Values Formik

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.

Formik validate initial values on page load

Code below validate when submit and onFocusOut of textbox fine, What I expect it to trigger validation first reload of the page with initial values.
Tried validateOnMount, as well as others but not worked.
Whats missing here?
const RoleValidationSchema = Yup.object().shape({
Adi: Yup.string()
.min(2, "En az 2 karakter olmalıdır")
.max(30, "En fazla 30 karakter olmalıdır")
.required("Gerekli!")
})
const Role = (props) => {
return (
<div>
<Formik
onSubmit={(values, { validate }) => {
debugger
validate(values);
alert("Submietted!")
props.handleFormSubmit()
}}
initialValues={{
Adi: "d"
}}
validationSchema={RoleValidationSchema}
validateOnMount={true}
validateOnChange={true}
validateOnBlur={true}
render={({ errors, touched, setFieldValue, ...rest }) => {
debugger
return (
<Form>
<Row>
<Col sm="12">
<Label for="Adi">Rol Adi</Label>
<FormGroup className="position-relative">
<Field
autoComplete="off"
id="Adi"
className="form-control"
name="Adi"
type="text"
/>
<ErrorMessage name="Adi">
{(msg) => (
<div className="field-error text-danger">{msg}</div>
)}
</ErrorMessage>
</FormGroup>
</Col>
<Tree dataSource={treeData} targetKeys={targetKeys} expandKeys={[]} onChange={onChange} />
</Row>
<button type="submit" className="btn btn-success" > Kaydet</button>
</Form>
)
}}
/>
</div>
)
}
This worked for me to show validation error on form load.
useEffect(() => {
if (formRef.current) {
formRef.current.validateForm()
}
}, [])
Try to add enableReinitialize Prop to your Formik Component
<Formik
enableReinitialize
......
/>
I had a bit specific case with dynamic Yup schema. Schema was generated based on API data. And validateOnMount was failing as when component was mounted there was no schema yet (API response not received yet).
I end up with something like this.
Inside component I used useEffect. Notice: I used useRef to be able to reference Formik outside of form.
useEffect(() => {
if (formRef.current) {
// check that schema was generated based on API response
if(Object.keys(dynamicValidationSchema).length != 0){
formRef.current.validateForm()
}
}
}, [initialData, dynamicValidationSchema])
Formik:
<Formik
innerRef={formRef}
enableReinitialize
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={props.handleSubmit}
>
It's possible to provide a initial value for validation in the "initialIsValid" prop.
const validationSchema= Yup.object()
const initialValues = { ... }
const initialIsValid = schema.isValidSync(initialValues)
return <Formik
initialValues={initialValues}
validationSchema={validationSchema}
initialIsValid={initialIsValid }
...
>
...
</Formik>
The only solution I found is to explicitly add initialErrors={{..}} to Formik.
In the below example, it's added depending on some condition. Unfortunately that duplicates the Yup validation/message which I also have in the schema. Nothing else worked, no validateOnMount or anything else.
<Formik
enableReinitialize
validationSchema={schema}
onSubmit={ (values) => {
submitForm(values);
}}
initialValues={initialFormValues}
/* Unfortunately a duplication of Yup Schema validation on this field,
but can't force Formik/Yup to validate initially */
initialErrors={!isEmptyObject(initialFormValues) &&
initialFormValues.inactiveApproverCondition &&
{'approverId': 'Selected Approver is no longer eligible. Please choose a different Approver to continue.'}}
>
You should try updating your code follow this
render={({ errors, touched, setFieldValue, isValid, ...rest })
and
<button type="submit" className="btn btn-success" disabled={!isValid}> Kaydet</button>

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