this is my first ever stack overflow question. I am trying to build a log-in page (making a full stack app with node express mongodb mongoose) and front end being REACT. I am running into an issue where the input field css does not register until I click into the input field and click out. I am using Formik to develop my form. I have two questions essentially: How can I fix this issue and how can I do an API call to my backend server which is running on localhost:3003. Here is the code:
import React from "react";
import "../App.css";
import { Formik } from "formik";
import * as EmailValidator from "email-validator";
import * as Yup from "yup";
const ValidatedLoginForm = () => (
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
console.log("Logging in", values);
setSubmitting(false);
}, 500);
}}
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required"),
password: Yup.string()
.required("No password provided.")
.min(8, "Password is too short - should be 8 chars minimum.")
.matches(/(?=.*[0-9])/, "Password must contain a number.")
})}
>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<div className="FormCenter">
<form onSubmit={handleSubmit} className="FormFields">
<div className="FormField">
<label htmlFor="email" className="FormField__Label">
Email
</label>
<input
name="email"
type="text"
placeholder="Enter your email"
value={values.email}
onBlur={handleBlur}
onChange={handleChange}
className={
errors.email && touched.email && "error" && "FormField__Input"
}
/>
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
</div>
<div className="FormField">
<label htmlFor="email" className="FormField__Label">
Password
</label>
<input
name="password"
type="password"
placeholder="Enter your password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.password &&
touched.password &&
"error" &&
"FormField__Input"
}
/>
{errors.password && touched.password && (
<div className="input-feedback">{errors.password}</div>
)}
</div>
<div className="FormField">
<button
type="submit"
className="FormField__Button mr-20"
onChange
>
Login
</button>
</div>
</form>
</div>
);
}}
</Formik>
);
export default ValidatedLoginForm;
This looks like a normal behaviour to me. The validation in the form will get triggered when ,
when you submit the form
when the field is touched . Becoz of this you are seeing the error message being displayed when you switch between the fields without entering the values .
You should be firing the api call from the onSubmit, were you will get to access all the form values . I would suggest you to use fetch api . https://developer.mozilla.org/en/docs/Web/API/Fetch_API
Just make your onSubmit as a asyn method and fire your api call inside it .
Related
I am showing a login screen to the user. This login screen (or the whole app, to be fair) can be shown in different languages.
The language switching works fine for all UI components except validation errors.
Currently, if a user leaves the email / password field blank, or enters an invalid email address, gets an error message and then switches languages, the error message won't change to the new language.
After a lot of guesswork and experimentation I have arrived at a point where the language changes after clicking either the 'de' or 'en' buttons twice, followed by typing into the input.
I can't for the life of me work out the right combination of useState and useEffect hooks to make the validation messages translate immediately upon hitting either of the language buttons.
Following code:
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Formik, Field, Form, ErrorMessage } from 'formik'
import * as Yup from 'yup'
import { useTranslation } from 'react-i18next'
import { accountService, alertService } from '#/_services'
function Login({ history, location }) {
//'common' is defined in i18n.js under init's namespace ('ns:') option
//if that's not set, use useTranslation('common'), or whatever the name
//of the json files you use in the language-specific folders is
const { t, i18n } = useTranslation()
const [emailRequired, setEmailRequired] = useState(t('login.email_error_required'))
const [emailInvalid, setEmailInvalid] = useState(t('login.email_error_invalid'))
const [passwordRequired, setPasswordRequired] = useState(t('login.password_error_required'))
const [validationSchema, setvalidationSchema] = useState(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
useEffect(() => {})
const initialValues = {
email: '',
password: '',
}
const onSubmit = ({ email, password }, { setSubmitting }) => {
alertService.clear()
accountService
.login(email, password)
.then(() => {
const { from } = location.state || { from: { pathname: '/' } }
history.push(from)
})
.catch((error) => {
setSubmitting(false)
alertService.error(error)
})
}
return (
<Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={onSubmit}>
{({ errors, touched, isSubmitting }) => (
<Form>
<h3 className="card-header">{t('login.page_title')}</h3>
<div className="card-body">
<div className="form-group">
<label>{t('login.email_input_label')}</label>
<Field name="email" type="text" className={'form-control' + (errors.email && touched.email ? ' is-invalid' : '')} />
<ErrorMessage name="email" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label>{t('login.password_input_label')}</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
<ErrorMessage name="password" component="div" className="invalid-feedback" />
</div>
<div className="form-row">
<div className="form-group col">
<button type="submit" disabled={isSubmitting} className="btn btn-primary">
{isSubmitting && <span className="spinner-border spinner-border-sm mr-1"></span>}
{t('login.login_btn_label')}
</button>
<Link to="register" className="btn btn-link">
{t('login.register_btn_label')}
</Link>
<button
type="button"
onClick={() => {
i18n.changeLanguage('de')
setEmailRequired(t('login.email_error_required'))
setEmailInvalid(t('login.email_error_invalid'))
setPasswordRequired(t('login.password_error_required'))
setvalidationSchema(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
}}
>
de
</button>
<button
type="button"
onClick={() => {
i18n.changeLanguage('en')
setEmailRequired(t('login.email_error_required'))
setEmailInvalid(t('login.email_error_invalid'))
setPasswordRequired(t('login.password_error_required'))
setvalidationSchema(
Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
}),
)
}}
>
en
</button>
</div>
<div className="form-group col text-right">
<Link to="forgot-password" className="btn btn-link pr-0">
{t('login.forgot_password_label')}
</Link>
</div>
</div>
</div>
</Form>
)}
</Formik>
)
}
export { Login }
Things I have tried and their results:
1.)
I once didn't use useState at all, so the validationSchema was composed this way:
const emailRequired = t('login.email_error_required');
const emailInvalid = t('login.email_error_invalid');
const passwordRequired = t('login.password_error_required');
const validationSchema = Yup.object().shape({
email: Yup.string().email(emailInvalid).required(emailRequired),
password: Yup.string().required(passwordRequired),
})
This lead to only one click of the 'de' or 'en' buttons being required before typing into one of the inputs would change the error message to the new translation.
2.)
I made the language change buttons into submit-type buttons:
<button
type="button"
onClick={() => {
i18n.changeLanguage('en')
}}
>
but that just felt wrong. I can't imagine that would be the correct solution. It worked, however, as it simply re-ran the 'OnSubmit' function...
3.)
Various combinations of adding 'validationSchema' and the error variables into useState and useEffect hooks, in the hopes that any of them worked, but it simply didn't.
What I would like to understand is why the Formik object appears to 'insulate' its inner components from updates brought about by useEffect. Why is React not seeing the change in language for the validation errors, but for everything else it does?
I haven't had any responses, but figured it out myself in the meantime.
Turns out the translation keys need to go into the schema:
const validationSchema = Yup.object().shape({
email: Yup.string().email('login.email_error_invalid').required('login.email_error_required'),
password: Yup.string().required('login.password_error_required'),
})
And then the error messages need to be custom components. So they need to change from this:
<ErrorMessage name="password" component="div" className="invalid-feedback" />
To this:
{errors.password && <div className="invalid-feedback">{t(errors.password)}</div>}
So, for email, the whole thing looks like:
<div className="form-group">
<label>{t('login.password_input_label')}</label>
<Field name="password" type="password" className={'form-control' + (errors.password && touched.password ? ' is-invalid' : '')} />
{errors.password && <div className="invalid-feedback">{t(errors.password)}</div>}
</div>
I have no idea why this made a difference, but it's hopefully going to help someone else in the future.
Thanks op, it helped me figure out how to make translations work.
The approach it worked for me is:
const LoginSchema = Yup.object().shape({
username: Yup.string().required('loginForm.required'),
password: Yup.string()
.min(6, 'loginForm.passwordShort')
.required('loginForm.required'),
});
But now extract does not work so you have to manually add these keys inside your .po files.
{errors.username && touched.username ? (
<IonItem
lines="none"
className={`${classes.Error} ion-margin-top ion-justify-content-center`}
>
{t({
id: errors.username,
})}
</IonItem>
) : null}
This approach works for Yup + Lingui.js
I'm working on a form using formik and yup. I have added required schema, but it is not working. I can easily save having empty input fields. I have tried and googled but nothing worked.
I want to make it mandatory and it should give error if field is empty.
snippet of yup schema validation
opening_time: Yup.string().required("Opening time is Requried"),
closing_time: Yup.string().required("Closing time is Requried"),
address: Yup.string().required("Address is Requried"),
about: Yup.string().required("About is Required"),
Input field snippet
<div class="form-group mb-0">
<label>
About<span className="text-danger">*</span>
</label>
<textarea
name="about"
onChange={formik.handleChange}
value={formik.values.about}
class="form-control"
rows="5"
required
/>
{formik.touched.about && formik.errors.about ? (
<div className="err">
{formik.errors.about}
{console.log(formik.errors.about)}
</div>
) : null}
</div>
Try the following:
import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const SignupSchema = Yup.object().shape({
opening_time: Yup.string().required("Opening time is Requried"),
closing_time: Yup.string().required("Closing time is Requried"),
address: Yup.string().required("Address is Requried"),
about: Yup.string().required("About is Required"),
});
function ValidationSchemaExample() {
function updateDoctorProfile(e, values) {
console.log(`e: ${e}`);
console.log(`values: ${values}`)
}
return (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
opening_time: "",
closing_time: "",
address: "",
about: "",
}}
validationSchema={SignupSchema}
onSubmit={values => {
// same shape as initial values
console.log(values);
}}
>
{({ values, errors, touched, handleChange, handleSubmit, isSubmitting }) => (
< div className="form-group mb-0">
<label>
About<span className="text-danger">*</span>
</label>
<textarea
name="about"
onChange={handleChange}
value={values.about}
className="form-control"
required
/>
<button type="submit" onClick={(e) => {
handleSubmit();
updateDoctorProfile(e, values);
}} disabled={isSubmitting}>
Submit
</button>
{touched.about && errors.about ? (
<div className="err">
{errors.about}
{console.log(errors.about)}
</div>
) : null}
</div>
)}
</Formik>
</div >
);
}
export default ValidationSchemaExample;
The only change is that the button tag's onClick attribute is passed the handleSubmit function along with your updateProfile function.
Yup Validation Not Working I Also tried Like This import Yup from 'yup' But Still Not Working.., when I create a simple validate Function without YUP it was worked fine. but when I use YUP that time Validation Not Work.
Added Form Component
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
const initialValues = {
name : "",
surname : "",
address : ""
}
const validateSchema = Yup.object({
name: Yup.string().required('This Field is Required'),
surname: Yup.string().required('This Field is Required'),
address: Yup.string().required('This Field is Required')
});
const onSubmit = values =>{
console.log('FormData->',values);
}
function Form() {
const formik = useFormik({
initialValues ,
onSubmit ,
validateSchema
})
return (
<div className="container x col-md-4 col-md-offset-4" >
<form onSubmit={formik.handleSubmit} >
<div className="form-group">
<input autoComplete="off" onBlur={formik.handleBlur} onChange={formik.handleChange}
value={formik.values.name} name="name" className="name form-control-lg" placeholder="Enter A Name" />
<small>
{formik.touched.name && formik.errors.name ? <div className="error"> {formik.errors.name} </div> : null}
</small>
</div>
<div className="form-group">
<input autoComplete="off" onBlur={formik.handleBlur} onChange={formik.handleChange}
value={formik.values.surname} name="surname" className="surname form-control-lg" placeholder="Enter A Surname" />
<small> { formik.touched.surname && formik.errors.surname ? <div className="error"> {formik.errors.surname} </div> : null} </small>
</div>
<div className="form-group">
<input autoComplete="off" onBlur={formik.handleBlur} onChange={formik.handleChange} value={formik.values.address}
name="address" className="address form-control-lg" placeholder="Enter A Address" />
<small> {formik.touched.address && formik.errors.address ? <div className="error"> {formik.errors.address} </div> : null} </small>
</div>
<button className="btn btn-danger" >Submit</button>
</form>
</div>
)
}
export default Form;
The valid option here is validationSchema not validateSchema. Just set the right one:
const validationSchema = Yup.object({
// ...
});
useFormik({
// ...
validationSchema
})
To use formik and yup, first you have you do npm install yup and npm install formik. if your yarn user then do yarn add formik and yarn add yup.
after that follow this code.
import {Formik} from 'formik';
import * as yup from 'yup';
//functional component am using
export const Auth = () => {
const initialFormValue ={
email: '',password: '',};
const SignInSchema = yup.object().shape({
email: yup.string().email('Invalid Email').required('Email is Required'),
password: yup.string().min(5, 'Too Short').required('Password is Required'),
})
<Formik initialValues={initialFormValue} validationSchema={SignInSchema}
onSubmit={values => console.log(values)}>
{({values, errors, touched, handleChange, handleBlur,
handleSubmit,isSubmitting,
isValid}) => (
<>
<input name='email' type='email' handleChange={handleChange} value=
{values.email} onBlur={handleBlur} label='Email' />
{errors.email && touched.email ? (<Error>{errors.email}</Error>): null}
<input name='password' type='password' handleChange={handleChange} value=
{values.password} onBlur={handleBlur} label='Password' />
{errors.password && touched.password ? (<Error>{errors.password}</Error>):
null}
<button onClick={handleSubmit} type="submit">Send</button>
</>
) }
</Formik>
)}
its not necessary for you to define initial value outside you can do inside formik too <Formik initialValues={email: '', password: ''}> </Formik>
i hope this will help you thank you.
Formik + yup
validation with .test() triggers on change on every field
example:
i have a validation schema:
const schemaValidation = yup.object().shape({
name: yup.string().required(),
email: yup.string().email().required()
.test( // it runs even if previous validation failed!
'checkEmailExists', 'email already taken.',
// it triggers on every onBlur
email => { console.log('email registration runs') return Promise.resolve(true)}
),
other: yup.string().required()
.test(
'checkOthers', 'error',
val => {console.log('other validation runs'); return Promise.resolve(true)}
)
})
render function on my react component looks like:
...
render () {
return(
<Formik
validateOnChange={false}
validateOnBlur={true}
initialValues={{
name: '',
email: '',
other: ''
}}
validationSchema={schemaValidation}
onSubmit={this.handleSubmit}
>
{(props) => (
<Form>
<div className="field">
<Field className="input" type="email" name="email" placeholder="Email" />
<ErrorMessage name="email" />
</div>
<div className="field">
<Field className="input" type="text" name="name" placeholder="Name"/>
<ErrorMessage name="name" />
</div>
<div className="field">
<Field className="input" type="text" name="other" placeholder="Other"/>
<ErrorMessage name="other" />
</div>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
)
so after every single field changed I receive messages from all .test() validators in console:
email registration runs
other validation runs
versions:
react: 16.13.1
formik: 2.1.4
yup: 0.28.4
By default formik validates after change, blur and submit events. Your code disables validation after change events with validateOnChange={false}. In order to only validate on submit events, try setting validateOnBlur to false too.
https://jaredpalmer.com/formik/docs/guides/validation#when-does-validation-run
https://jaredpalmer.com/formik/docs/api/formik#validateonblur-boolean
Hello everyone in 2022,
I understand a lot of formik has changed over the years - but thought I share a relevant solution that's using touched property, feel free to peek at https://codesandbox.io/s/stack-overflow-54204827-llvkzc.
And also shared for another StackOverflow question here, React Native Form Validation.
In summary, it's basically used like below (with yup outside of this snippet), to decide whether to show (if any) input validation errors:
<Formik
initialValues={{
email: "",
password: ""
}}
validationSchema={LoginSchemaA}
onSubmit={(
values: Values,
{ setSubmitting }: FormikHelpers<Values>
) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
>
{({ errors, touched }) => (
<Form>
<label htmlFor="email">Email</label>
<Field
id="email"
name="email"
placeholder="john.doe#email.com"
type="email"
/>
{errors.email && touched.email ? (
<div style={{ color: "red" }}>{errors.email}</div>
) : null}
<label htmlFor="password">Password</label>
<Field id="password" name="password" type="password" />
{errors.password && touched.password ? (
<div style={{ color: "red" }}>{errors.password}</div>
) : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
And as package.json reference, below are the defined versions:
"dependencies": {
...
"formik": "2.2.9",
...
"yup": "0.32.11"
},
Hope this helps for those on these mentioned formik and yup versions above!
I am trying to make a simple login form i want to render an element when an input field is clicked . I have done this in normal react form by rendering an element by changing the value of a boolean variable to true and when the input is written if the user touch somewhere else then the element disapears . kind of toggle thing . but i don't know hoe to do this in formik. my code looks like this.
import React from "react";
import { Formik } from "formik";
import * as EmailValidator from "email-validator";
import * as Yup from "yup";
const ValidatedLoginForm = () => (
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={(values, { setSubmitting }) => {
console.log(values);
console.log("hello there ");
}}
validationSchema={Yup.object().shape({
email: Yup.string()
.email()
.required("Required"),
password: Yup.string()
.required("No password provided.")
.min(8, "Password is too short - should be 8 chars minimum.")
.matches(/(?=.*[0-9])/, "Password must contain a number.")
})}>
{props => {
const {
values,
touched,
errors,
isSubmitting,
handleChange,
handleBlur,
handleSubmit
} = props;
return (
<div className="container">
<div className="row">
<form onSubmit={handleSubmit}>
<br />
<input
name="email"
type="text"
placeholder="Enter your email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
className={errors.email && touched.email && "error"}
/>
<br />
<br />
{errors.email && touched.email && (
<div className="input-feedback">{errors.email}</div>
)}
<br />
<input
name="password"
type="password"
placeholder="Enter your password"
value={values.password}
onChange={handleChange}
onBlur={handleBlur}
className={errors.password && touched.password && "error"}
/> <br />
<br />
{errors.password && touched.password && (
<div className="input-feedback">{errors.password}</div>
)}
<button type="submit" disabled={isSubmitting}>
Login
</button>
</form>
</div>
<div className="row">
<button className="btn btn-default">Value</button>
</div>
</div>
);
}}
Maybe this will help you. Try to add function to onBlur prop then handle handleBlur function.
onBlur={(e) => {
handleBlur(e);
// here handle component showing
}}