How can i make react-bootstrap form components work well with same-named formik components? - reactjs

First, I made a form validated with formik. After that, my team decided we would style with react-bootstrap. This library has a <Form> object, same as Formik. But it is not validating correctly. It either throws input fields as invalid all the time, thus putting its borders red, or as correct all the time.
I suspect it is a matter of combining libraries that work both with forms and have same-named components. I managed to make it work with original bootstrap library. But I still wanted to know why this happens and if it has some solution.
import React, {useState} from 'react';
import '../FormStyles.css';
import * as yup from "yup";
import { Formik, Form, ErrorMessage, Field } from 'formik';
const TestimonialForm = ({ testimonial = null }) => {
const initialValues = {
name: testimonial?.name || "",
description: testimonial?.description || "",
image: testimonial?.image || ""
};
const schema = yup.object().shape({
name: yup.string().min(4, "Name must be at least 4 characters long.").required("You have to provide a name."),
description: yup.string().required("You have to provide a description."),
image: yup.string()
.matches(
/\.(jpg|png)$/,
"We only support .png or .jpg format files."
)
.required("You have to provide an image.")
});
<Formik
initialValues= {initialValues}
validationSchema = {schema}
>
Here's the form with Formik components
{({}) => (
<div>
<Form className="form-container">
<Field
className="input-field"
type="text"
name="name"
placeholder="Testimonial title"/>
<ErrorMessage name="name" />
<Field
className="input-field"
type="file"
name="image"
placeholder="Testimonial image"/>
<ErrorMessage name="image" />
<button className="submit-btn" type="submit">Send</button>
</Form>
</div>
)}
</Formik>
Here's with react-bootstrap components (I only change the import values.) This are the ones that don't validate well.
{({touched, errors}) => (
<div>
<Form className="form-container" onSubmit={onSubmit}>
<Form.Group controlId="name">
<Form.Control
className="input-field"
type="text"
name="name"
isInvalid={name.touched && errors.name}
placeholder="Testimonial title"/>
<Form.Control.Feedback type="invalid">
{errors.name}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="image">
<Form.Control
className="input-field"
type="file"
name="image"
isInvalid={image.touched && errors.image}
/>
<Form.Control.Feedback type="invalid">
{errors.image}
</Form.Control.Feedback>
</Form.Group>
<button className="submit-btn" type="submit">Send</button>
{message && <div>{message}</div>}
</Form>
</div>
)}
</Formik>

Related

react-datetime formik validation

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>

React + Formik + Yup async validation

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!

formik rendering an element when input field is clicked in react?

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
}}

Why isn't the Formik `touched` property being populated?

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>

Formik with react-bootstrap styling

I am trying to use Formik with my react app.
I have react-bootstrap and I am trying to figure out how to style form components with bootstrap styling.
My form is:
// Render Prop
import React from 'react';
import { Link } from 'react-router-dom';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import { Badge, Button, Col, Feedback, FormControl, FormGroup, FormLabel, InputGroup } from 'react-bootstrap';
import * as Yup from 'yup';
const style1 = {
width: '60%',
margin: 'auto'
}
const style2 = {
paddingTop: '2em',
}
const style3 = {
marginRight: '2em'
}
class Basic extends React.Component {
render() {
return (
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
role: '',
password: '',
confirmPassword: '',
consent: false
}}
validationSchema={Yup.object().shape({
firstName: Yup.string()
.required('First Name is required'),
lastName: Yup.string()
.required('Last Name is required'),
email: Yup.string()
.email('Email is invalid')
.required('Email is required'),
role: Yup.string()
.required('It will help us get started if we know a little about your background'),
password: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('Password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], 'Passwords must match')
.required('Confirm Password is required')
})}
onSubmit={fields => {
alert('SUCCESS!! :-)\n\n' + JSON.stringify(fields, null, 4))
}}
render={({ errors, status, touched }) => (
<Form style={style1}>
<h1 style={style2}>Get Started</h1>
<div className="form-group">
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" className={'form-control' + (errors.firstName && touched.firstName ? ' is-invalid' : '')} />
<ErrorMessage name="firstName" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" className={'form-control' + (errors.lastName && touched.lastName ? ' is-invalid' : '')} />
<ErrorMessage name="lastName" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="email">Email</label>
<Field name="email" type="text" placeholder="Please use your work email address" className={'form-control' + (errors.email && touched.email ? ' is-invalid' : '')} />
<ErrorMessage name="email" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="role">Which role best describes yours?</label>
<Field name="role" type="text" placeholder="eg, academic, industry R&D, policy, funder" className={'form-control' + (errors.role && touched.role ? ' is-invalid' : '')} >
</Field>
<ErrorMessage name="role" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<label htmlFor="password">Password</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-group">
<label htmlFor="confirmPassword">Confirm Password</label>
<Field name="confirmPassword" type="password" className={'form-control' + (errors.confirmPassword && touched.confirmPassword ? ' is-invalid' : '')} />
<ErrorMessage name="confirmPassword" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<Field component="select" name="color">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</Field>
<Field name="consent" label="You must accept the and Privacy Policy" type="checkbox" className={'form-control' + (errors.consent && touched.consent ? ' is-invalid' : '')} />
<ErrorMessage name="consent" component="div" className="invalid-feedback" />
</div>
<div className="form-group">
<Button variant="outline-primary" type="submit" style={style3}>Register</Button>
</div>
</Form>
)}
/>
)
}
}
export default Basic;
When I try to add a react-bootstrap form group to the inside the render method, such as:
<Form.Group controlId="formBasicEmail">
<Form.Label>Email address</Form.Label>
<Form.Control type="email" placeholder="Enter email" />
<Form.Text className="text-muted">
We'll never share your email with anyone else.
</Form.Text>
</Form.Group>
I get an error that says:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Check the render method of Formik.
I think this error message is referring to element type to mean 'email' -- but it doesn't make any sense because the formik form i have already has an 'email' type in it and there is not error when I don't try to use react-bootstrap.
Has anyone figured out how to get Formik to work with react-bootstrap?
You're using the wrong Form. Import it from react-bootstrap instead of formik and it should work.

Resources