I'm making a form with React, Formik, react-bootstrap, and yup for validation. I am trying to display validation errors, but the touched property is not being populated with the fields.
const schema = yup.object({
name: yup.string().required(),
email: yup
.string()
.email()
.required(),
});
const ChildForm = props => {
const { child: { name = '', email = '' } = {} } = props;
const submitHandler = ({name, email}) => console.log(name, email);
return (
<Formik
validationSchema={schema}
onSubmit={submitHandler}
initialValues={{ name, email }}
render={({ handleSubmit, handleChange, values, touched, errors }) =>
{
console.log('touched: ', touched);
return (
<Form noValidate className="mt-4" onSubmit={handleSubmit}>
<Form.Row>
<Form.Group as={Col} controlId="name">
<Form.Label>Full Name</Form.Label>
<Form.Control
name="name"
required
value={values.name}
onChange={handleChange}
isValid={touched.name && !errors.name}
isInvalid={touched.name && errors.name}
type="text"
placeholder="Your child's name"
/>
<Form.Control.Feedback>Looks good!</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">
{errors.name || 'Please enter your child\'s name'}
</Form.Control.Feedback>
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId="email">
<Form.Label>Email Address</Form.Label>
<Form.Control
name="email"
required
value={values.email}
onChange={handleChange}
isValid={touched.email && !errors.email}
isInvalid={touched.email && errors.email}
type="text"
placeholder="Your child's email address"
/>
<Form.Control.Feedback>
No spam, we promise!
</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">
{errors.email || 'Please enter a valid email address'}
</Form.Control.Feedback>
</Form.Group>
</Form.Row>
<Form.Row className="float-right">
<Button variant="success" onClick={handleSubmit}>
<Icon icon={faSave} />
Submit
</Button>
</Form.Row>
</Form>
);
}}
/>
);
}
What am I doing wrong here? The console.log(touched) always shows an empty object.
#djheru Your solution is correct because Formik sets touched flags on blur event instead of on change. Here is Formik author comment about this:
You have to call Formiks handleBlur to notify Formik that blur event has been triggered - so yes, these handlers are needed.
I got it working by accessing the handleBlur function that's passed in the render function argument, and adding that as an onBlur handler for each of the form elements. Not sure if that's needed because I'm using react-bootstrap form components, but the react-bootstrap docs have a Formik example, but the touched object was not getting updated.
(
<Formik
validationSchema={schema}
onSubmit={submitForm}
initialValues={{ name, email }}
render={({
handleSubmit,
handleChange,
handleBlur, // handler for onBlur event of form elements
values,
touched,
errors,
}) => {
return (
<Form noValidate className="mt-4" onSubmit={handleSubmit}>
<Form.Row>
<Form.Group as={Col} controlId="nameControl">
<Form.Label>Full Name</Form.Label>
<Form.Control
name="name"
required
value={values.name}
onChange={handleChange}
onBlur={handleBlur} // This apparently updates `touched`?
isValid={touched.name && !errors.name}
isInvalid={touched.name && errors.name}
type="text"
placeholder="Your child's name"
/>
<Form.Control.Feedback>Looks good!</Form.Control.Feedback>
<Form.Control.Feedback type="invalid">
{errors.name || 'Please enter your child\'s name'}
</Form.Control.Feedback>
</Form.Group>
</Form.Row>
Related
We have the following contact form in React using https://react-bootstrap.github.io/forms/overview/
let contactForm =
(<Form ref={formRef} onSubmit={sendEmail} className='toggle-contact-form'>
<div className='toggle-contact-form__header'>
<p className='p1'>Please Reach out!</p>
<p className='p2'>Use our contact form to reach out with any questions, concerns or issues with the website.</p>
</div>
<Form.Row style={{ paddingTop: 20 }}>
<Form.Group as={Col} controlId='name'>
<Form.Label>Name</Form.Label>
<Form.Control className='cbb-home-input' placeholder='required' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='email'>
<Form.Label>Email Address</Form.Label>
<Form.Control className='cbb-home-input' type='email' placeholder='required' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='phone'>
<Form.Label>Phone Number</Form.Label>
<Form.Control className='cbb-home-input' placeholder='optional' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='message'>
<Form.Label>Message</Form.Label>
<Form.Control className='cbb-home-input' as='textarea' rows='2' placeholder='required' />
</Form.Group>
</Form.Row>
<Form.Row>
<Form.Group as={Col} controlId='button'>
<Button variant='primary' type='submit' disabled={true}>
{isSubmitting ? 'Sending Email...' : 'Submit'}
</Button>
</Form.Group>
</Form.Row>
</Form>);
Currently the button disabled={true}, we'd like to make this conditional on the Form.Control elements for name, message both being not empty, and email being a valid email address. Currently we have no form validation. Is it possible to validate this form as such?
The Bootstrap docs suggest using a library to make this process easier:
It's often beneficial (especially in React) to handle form validation via a library like Formik, or react-formal. In those cases, isValid and isInvalid props can be added to form controls to manually apply validation styles.
But here's how you can do it without a library:
Since we need access to the values of the input fields, we'll need to use controlled components to hold the form data. First we will set up some useState variables to hold the data:
const [name, setName] = useState("");
const [message, setMessage] = useState("");
const [email, setEmail] = useState("");
Then we need to use those state variables to handle the data in form fields by setting the value and onChange props:
...
<Form.Control
value={name}
onChange={(e) => {
setName(e.target.value);
}}
className="cbb-home-input"
placeholder="required"
/>
...
<Form.Control
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
className="cbb-home-input"
type="email"
placeholder="required"
/>
...
<Form.Control
value={message}
onChange={(e) => {
setMessage(e.target.value);
}}
className="cbb-home-input"
as="textarea"
rows="2"
placeholder="required"
/>
...
Now that we have access to the form field data, we can create a variable to keep track of whether the user input is valid:
const isValid = checkValidity(name, message, email);
The checkValidity function can check if name, message, and email meet the requirements we want them too:
const checkEmail = (email) => {
return /^\w+#[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/.test(email);
};
const checkValidity = (name, message, email) => {
return !!name && !!message && checkEmail(email);
};
At this point, the isValid variable will always be updated with whether or not the current user input in the form is valid. Specifically, we are making sure name and message are not empty, and that email passes a simple regex validity check.
Finally, we disable the submit button whenever isValid is false using the disabled prop:
<Button variant="primary" type="submit" disabled={!isValid}>
{isSubmitting ? "Sending Email..." : "Submit"}
</Button>
Here's a full working example on CodeSandbox:
I'm utilising Formik and React-Bootstrap to construct a user registration form. For now, I'm just console.logging the values captured from the form but for some reason my form does not capture the values from <Form.Check> element.
I would really appreciate any help as I've been struggling for a few hours!
const Register = () => {
const formik = useFormik({
initialValues: {
firstname: '',
surname: '',
email: '',
password: '',
role: 'user',
secondaryinterest: [''],
},
validationSchema: Yup.object({
firstname: Yup.string()
.min(2, 'Your name is too short')
.max(100, 'Your name is too long')
.required('We require your name'),
surname: Yup.string()
.min(2, 'Your name is too short')
.max(100, 'Your name is too long')
.required('We require your name'),
email:Yup.string()
.required('Sorry the email is required')
.email('This is not a valid email'),
password:Yup.string()
.required('Sorry the password is required'),
secondaryinterest: Yup.array()
.min(1, "You need to select at least one"),
}),
onSubmit:(values) => {
console.log(values)
}
})
return(
<Form onSubmit={formik.handleSubmit}>
<Form.Group>
<Form.Label>Select the five most relevant for you</Form.Label>
{['Football', 'Golf', 'Rugby', 'Tennis'].map((secondaryinterest) => (
<div key={`${secondaryinterest}`} className="mb-3">
<Form.Check
type='checkbox'
name='secondaryinterest'
placeholder="secondaryinterest"
id={`${secondaryinterest}`}
label={`${secondaryinterest}`}
onChange={formik.handleChange}
{...formik.getFieldProps('secondaryinterest')}
/>
</div>
))}
</Form.Group>
<Row className="mb-3">
<Form.Group as={Col} controlId="formGridFirstname">
<Form.Label>First Name</Form.Label>
<Form.Control
type="text"
name="firstname"
variant="outlined"
{...formik.getFieldProps('firstname')}
/>
{formik.touched.firstname && formik.errors.firstname ? (
<div>{formik.errors.firstname}</div>
) : null}
</Form.Group>
<Form.Group as={Col} controlId="formGridSurname">
<Form.Label>Last Name</Form.Label>
<Form.Control
type="text"
name="surname"
variant="outlined"
{...formik.getFieldProps('surname')}
/>
{formik.touched.surname && formik.errors.surname ? (
<div>{formik.errors.surname}</div>
) : null}
</Form.Group>
</Row>
<Row className="mb-3">
<Form.Group as={Col} controlId="formGridEmail">
<Form.Label>E-mail</Form.Label>
<Form.Control
type="email"
name="email"
variant="outlined"
{...formik.getFieldProps('email')}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
</Form.Group>
<Form.Group as={Col} controlId="formGridPassword">
<Form.Label>Enter your password</Form.Label>
<Form.Control
type="password"
name="password"
variant="outlined"
{...formik.getFieldProps('password')}
/>
{formik.touched.password && formik.errors.password ? (
<div>{formik.errors.password}</div>
) : null}
</Form.Group>
</Row>
<Button variant="primary" type="submit">
Register
</Button>
</Form>
}
Console log output is:
{firstname: "Dave", surname: "Kula", email: "davekula#gmail.com", password: "ddhshja", role: "user", secondary interest: [],...}
This part actually doesn't do anything. What you want to do actually, when you set the checkmark, it should add it to the array of secondaryinterest. However you didn't give instructions and Formik cannot just guess it.
{['Football', 'Golf', 'Rugby', 'Tennis'].map((secondaryinterest) => (
<div key={`${secondaryinterest}`} className="mb-3">
<Form.Check
type='checkbox'
name='secondaryinterest'
placeholder="secondaryinterest"
id={`${secondaryinterest}`}
label={`${secondaryinterest}`}
onChange={formik.handleChange}
{...formik.getFieldProps('secondaryinterest')}
/>
</div>
))}
Easier change for you, I suggest, create new initial values for each checkbox. Make them separate.
I was able to fix it and thought to post the answer in case it helps someone else a a later stage
{['Football', 'Golf', 'Rugby', 'Tennis'].map((secondaryinterest) => (
<div key={`${secondaryinterest}`} className="mb-3">
<Form.Check
type='checkbox'
name='secondaryinterest'
value={`${secondaryinterest}`}
label={`${secondaryinterest}`}
onChange={formik.handleChange}
/>
</div>
))}
I stipulated the name and value fields. Also the {...formik.getFieldProps('secondaryinterest')} was redundant.
I'm using react-datetime compenent in my react-bootstrap form. Formik with Yup is used for validation.
import React from 'react';
import { Container, Form, Button, Alert, Row, Col, InputGroup } from "react-bootstrap";
import "react-datetime/css/react-datetime.css";
import { Formik} from 'formik';
import * as Icon from 'react-bootstrap-icons';
import * as yup from "yup"
import Datetime from 'react-datetime';
import moment from 'moment';
const validDOB = function( current ){
return current.isBefore(moment());
};
const schema = yup.object().shape({
userId: yup.string().required('Please enter a valid User ID'),
userName: yup.string().required('User\'s Name cannot be empty'),
userDOB: yup.string().required('User\'s Date of Birth cannot be empty'),
});
function AddWorkload(){
return (
<>
<Container>
<Row className="justify-content-md-center">
<h3 style={{textAlign: 'center'}}>Add a New Workload</h3>
<Formik
validationSchema={schema}
onSubmit={console.log}
initialValues={{
userId: '',
userName: '',
userDOB: '',
}}
>
{({
handleSubmit,
handleChange,
handleBlur,
values,
touched,
isValid,
errors,
setFieldValue,
}) => (
<Col md lg="6">
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="userIdInput">
<Form.Label>User ID</Form.Label>
<InputGroup>
<Form.Control
placeholder="User ID"
type="text"
name="userId"
value={values.userId}
onChange={handleChange}
isInvalid={!!errors.userId}
aria-describedby="userIdHelpBlock"
/>
<Button variant="dark"><Icon.Search /> Find User</Button>
<Form.Control.Feedback type="invalid">
{errors.userId}
</Form.Control.Feedback>
</InputGroup>
<Form.Text id="userIdHelpBlock" muted>
Please click "Find User" to fill out the details.
</Form.Text>
</Form.Group>
<Form.Group controlId="userName">
<Form.Label>User Name</Form.Label>
<Form.Control
placeholder="User Name"
type="text"
name="userName"
value={values.userName}
onChange={handleChange}
isInvalid={!!errors.userName}
/>
<Form.Control.Feedback type="invalid">
{errors.userName}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="userDOB">
<Form.Label>Date of Birth</Form.Label>
<Datetime
inputProps={{
placeholder: 'DOB',
id: 'userDOB',
name: 'userDOB',
}}
dateFormat="DD-MM-YYYY"
timeFormat={false}
value={values.userDOB}
isValidDate={validDOB}
onChange={(dateFromValue) => {
setFieldValue('userDOB', dateFromValue);
}
}
isInvalid={!!errors.userDOB}
/>
<Form.Control.Feedback type="invalid">
{errors.userDOB}
</Form.Control.Feedback>
</Form.Group>
<div style={{clear: 'both'}}></div>
<br></br>
<div className='text-center'>
<Button variant="dark" type="reset">Reset Form</Button>{' '}
<Button variant="success" type="submit">Save</Button>
</div>
</Form>
</Col>
)}
</Formik>
</Row>
</Container>
</>
);
}
export default AddWorkload;
Validation for userId and userName is working properly. But I can't get the validation to work for userDOB. I also tried yup.date() but it doesn't work. react-datetime uses moment.js. So, what type to use for the schema and how to get the validation working for that compenent.
Any help would be appreciated.
Thanks
I found my issue. Its because Form.Control.Feedback doesn't correspond to the component Datetime.
So, I removed the Form.Control.Feedback and replaced that with a custom div to display the error message for the Datetime component.
<Form.Group controlId="patientDOB">
<Form.Label>Date of Birth</Form.Label>
<Datetime
inputProps={{
placeholder: 'DD-MM-YYYY',
id: 'userDOB',
name: 'userDOB',
className: formik.errors.userDOB && formik.touched.userDOB ? "form-control is-invalid": "form-control"
}}
closeOnSelect={true}
dateFormat="DD-MM-YYYY"
timeFormat={false}
value={formik.values.userDOB}
isValidDate={validDOB}
onChange={(dateFromValue) => {
formik.setFieldValue('userDOB', moment(dateFromValue).format('DD-MM-YYYY'));
}
}
renderInput={(props) => {
return <input {...props} value={(formik.values.userDOB) ? props.value : ''} />
}}
/>
{formik.errors.userDOB && formik.touched.userDOB ? (
<div className="text-danger" style={{fontSize: '0.875em'}}>{ formik.errors.userDOB }</div>
) : null}
</Form.Group>
I am starting in React and now I have the challenge of form validation and for this, looking for ways to work with the validations I found the Formik and Yup libraries
But it turns out that there is no example of how to validate an "input type = 'date'", I only see examples where they use "DatePicker", to which I ask if there is any way to be able to solve this using "input type = 'date'
Then I will show you how I am trying to validate this date.
const validationSchema = Yup.object().shape({
name: Yup.string()
.min(2,"El nombre debe contener mas de 2 caracteres")
.max(60,"El nombre no debe exceder de los 60 caracteres")
.required("*El nombre es requerido"),
inicio: Yup.date()
.min(Yup.ref('originalEndDate'),
({ min }) => `Date needs to be before ${formatDate(min)}!!`,)
});
function formatDate(date) {
return new Date(date).toLocaleDateString()
}
<Formik initialValues={{ name:"",inicio:""}} validationSchema={validationSchema}>
{( {values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting }) => (
<Form.Group>
<Form.Row>
<Form.Label column="sm" lg={1}>
Creada Por:
</Form.Label>
</Form.Row>
<Form.Row className="col-form-label">
<Form.Label column="sm" lg={1}>
Nombre:
</Form.Label>
<Col>
<div class="col-10">
<Form.Control type="text" defaultValue={values.name} name="name" placeholder="Nombre de campaña"
onChange={handleChange}
onBlur={handleBlur}
className={touched.name && errors.name ? "error":null}/>
{/* Applies the proper error message from validateSchema when the user has clicked the element and there is an error, also applies the .error-message CSS class for styling */}
{touched.name && errors.name ? (
<div className="error-message">{errors.name}</div>
): null
}
</div>
</Col>
</Form.Row>
<Form.Row>
<Form.Label column="sm" lg={1}>
Inicio:
</Form.Label>
<Col>
<div className="col-10">
<Form.Control type="date" placeholder="" defaultValue={values.inicio}
onChange={handleChange}
onBlur={handleBlur}
className={touched.inicio && errors.inicio ? "error":null}
/>
</div>
</Col>
<Form.Label column="sm" lg={1}>
Fin:
</Form.Label>
<Col>
<div className="col-10">
<Form.Control type="date" placeholder="2011-08-19"
/>
</div>
</Col>
</Form.Row>
<h5>Indique el objetivo a cumplir para esta campaña</h5>
<Container>
<Form.Check required label="Incrementar volumen de ventas"/>
<Form.Check required label="Incrementar repetición de venta"/>
<Form.Check required label="Mejorar lealtad"/>
<Form.Check required label="Crear awearness"/>
<Form.Check required label="Acción correctiva"/>
</Container>
<Container>
<Button as="input" type="button" value="regresar" />{' '}
<Button as="input" variant="primary" type="submit" value="Crear campaña"/>
</Container>
</Form.Group>
)}
</Formik>
I am looking for a solution for this if it exists
Using transforms:
import { date, object } from "yup";
const today = new Date();
const schema = object({
birthday: date().transform(parseDateString).max(today),
});
const isValid = schema.validateSync({
birthday: "2020-02-02",
});
Ref: https://codedaily.io/tutorials/175/Yup-Date-Validation-with-Custom-Transform
Yup: https://github.com/jquense/yup#mixedtransformcurrentvalue-any-originalvalue-any--any-schema
I'm new to React and I have done a multistep form for signing up using React bootstrap, Formik, and validation with Yup. I want to add functionality that will allow me to format (or mask) user inputs in a couple of fields.
While user typing, I want to format the phone input to this format: 111-111-1111. The postal code to format: N0G 1A3. How I can do this? Any suggestions? Do I need to add any libraries? What should I do?
Here is the code I did:
export const step1Scehma = Yup.object({
firstName: Yup.string()
.required("This field is required"),
lastName: Yup.string()
.required("This field is required"),
email: Yup.string()
.email()
.required("Email is required"),
password: Yup.string()
.required("This field is required")
.matches(
"^(?=.*[A-Za-z])(?=.*d)(?=.*[#$!%*#?&])[A-Za-zd#$!%*#?&]{8,}$",
"Must Contain at least 8 Characters: One Uppercase, One Lowercase, One Number and one
special case Character"
),
phone: Yup.string()
.required("This field is required")
});
export const Step1 = ({ formik }) => {
const { handleChange, values, errors, touched } = formik;
return (
<React.Fragment>
<Container>
<Card.Title className="text-center">New User</Card.Title>
<Card.Body>
<Form.Group as={Col}>
<Form.Label>First Name</Form.Label>
<Form.Control
type="text"
name="firstName"
onChange={handleChange}
value={values.firstName}
isValid={touched.firstName && !errors.firstName}
isInvalid={!!errors.firstName}
className={touched.firstName && errors.firstName ? "error" : null}
/>
{touched.firstName && errors.firstName ? (<div className="error-message">{errors.firstName}</div>): null}
</Form.Group>
<Form.Group as={Col}>
<Form.Label>Last Name</Form.Label>
<Form.Control
type="text"
name="lastName"
onChange={handleChange}
value={values.lastName}
isValid={touched.lastName && !errors.lastName}
isInvalid={!!errors.lastName}
className={touched.lastName && errors.lastName ? "error" : null}
/>
{touched.lastName && errors.lastName ? (<div className="error-message">{errors.lastName}</div>): null}
</Form.Group>
<Form.Group as={Col} >
<Form.Label>Email</Form.Label>
<Form.Control
type="text"
name="email"
onChange={handleChange}
value={values.email}
isValid={touched.email && !errors.email}
isInvalid={!!errors.email}
className={touched.email && errors.email ? "error" : null}
/>
{touched.email && errors.email ? (<div className="error-message">{errors.email}</div>): null}
</Form.Group>
<Form.Group as={Col} >
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
name="password"
onChange={handleChange}
value={values.password}
isValid={touched.password && !errors.password}
isInvalid={!!errors.password}
className={touched.password && errors.password ? "error" : null}
/>
{touched.password && errors.password ? (<div className="error-message">{errors.password}</div>): null}
</Form.Group>
<Form.Group as={Col} >
<Form.Label>Phone Number</Form.Label>
<Form.Control
type="tel"
name="phone"
onChange={handleChange}
value={values.phone}
isValid={touched.phone && !errors.phone}
isInvalid={!!errors.phone}
className={touched.phone && errors.phone ? "error" : null}
/>
{touched.phone && errors.phone ? (<div className="error-message">{errors.phone}</div>): null}
</Form.Group>
</Card.Body>
</Container>
</React.Fragment>
);
};
You can use https://github.com/sanniassin/react-input-mask
https://www.npmjs.com/package/react-input-mask
The examples are very detailed. I used this with Yup and Material UI.
<InputMask mask="999-999-9999" onChange={ handleChange } onBlur={ handleBlur } value={values.billing_phone} error={errors.billing_phone && touched.billing_phone} helperText={(errors.billing_phone && touched.billing_phone) && errors.billing_phone}>
{(inputProps) => <TextField {...inputProps} className={classes.MuiInputBase} id="billing_phone" variant="outlined" name="billing_phone" placeholder={addressObj.phone} />}
</InputMask>
If you're not using a plain <input /> you'll have to wrap your element like I did. You can find the discussion about this on the NPM page.