new to react, just starting to work on a react-redux application.
Just wondering does anyone have any tips on best practices relating to form validation and displaying error messages?
Any thoughts on this greatly appreciated :)
The react way is to have a container handle the state and pass it through the props to the inputs (components). So it's not really a react-redux thing to handle a validation, if of course you don't build through multiple routes and need that global state.
I like using Formik for forms in React. The main benefits are:
Validation and error messages
Handling forms state
Handling form submission
And it is a good idea to use Yup (a schema validator) with Formik.
Here is an example of a form with Formik + Yup:
yarn add formik yup
import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const SignupSchema = Yup.object().shape({
: Yup.string()
.min(2, 'Too Short!')
.max(50, 'Too Long!')
.required('Required'),
email: Yup.string()
.email('Invalid email')
.required('Required'),
});
export const ValidationSchemaExample = () => (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
name: '',
email: '',
}}
validationSchema={SignupSchema}
onSubmit={values => {
// same shape as initial values
console.log(values);
}}
>
{({ errors, touched }) => (
<Form>
<Field name="name" />
{errors.name && touched.name ? (
<div>{errors.name}</div>
) : null}
<Field name="email" type="email" />
{errors.email && touched.email ? <div>{errors.email}</div> : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
Related
Here is my complete edit form. I am type checking with prop types. Also using yup for validation with formik. However my input field is not accepting decimal number. Do i need to add custom validation rules for decimal number? Current form's validation and error feedback working properly for number without decimal format. Any hints how can i solve this ? Thanks in advance !
import React from "react";
import PropTypes from "prop-types";
import { Formik, Form, Field } from "formik";
import { Button, Input, FormGroup, Label, FormFeedback } from "reactstrap";
import * as Yup from "yup";
const projectType = PropTypes.shape({
id: PropTypes.number.isRequired,
actual_design: PropTypes.number.isRequired,
actual_development: PropTypes.number.isRequired,
actual_testing: PropTypes.number.isRequired
});
/**
* Custom form field component to make using Reactstrap and Formik together
* easier and less verbose.
*/
const FormField = ({ label, name, touched, errors }) => (
<FormGroup>
<Label for={name}>{label}</Label>
<Input
type="number"
name={name}
id={name}
tag={Field}
invalid={touched[name] && !!errors[name]}
min={0}
required
/>
{touched[name] && errors[name] && (
<FormFeedback>{errors[name]}</FormFeedback>
)}
</FormGroup>
);
FormField.propTypes = {
label: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
touched: PropTypes.object.isRequired,
errors: PropTypes.object.isRequired
};
/**
* Form for editing the actual hours for a project.
*/
const EditProjectForm = ({ project, onSubmit }) => (
<Formik
initialValues={{
actual_design: projectType.actual_design,
actual_development: projectType.actual_development,
actual_testing: projectType.actual_testing
}}
validationSchema={Yup.object().shape({
actual_design: Yup.number()
.min(0)
.required()
.label("Actual design hours"),
actual_development: Yup.number()
.min(0)
.required()
.label("Actual development hours"),
actual_testing: Yup.number()
.min(0)
.required()
.label("Actual testing hours")
})}
onSubmit={onSubmit}
>
{({ touched, errors, isSubmitting }) => (
<Form>
<FormField
name="actual_design"
type="number"
label="Actual design hours"
touched={touched}
errors={errors}
/>
<FormField
name="actual_development"
label="Actual development hours"
touched={touched}
errors={errors}
/>
<FormField
name="actual_testing"
label="Actual testing hours"
touched={touched}
errors={errors}
/>
<Button type="submit" color="primary" disabled={isSubmitting}>
UPDATE
</Button>
</Form>
)}
</Formik>
);
EditProjectForm.propTypes = {
project: projectType.isRequired,
onSubmit: PropTypes.func.isRequired
};
export default EditProjectForm;
I assume that you see a native browser validation message, because type="number" by default only allows integer with step=1, so you can fix it by adding step="any".
More information you can find there - Is there a float input type in HTML5?
I'm currently in the process of creating a simple form using MUI TextFields, Formik and some Yup validation.
I ran into some issues with performance with lower spec devices when scrolling and I have extracted stuff like MUI styles and other components such as toastify notifications.
I am relatively new to react, but I have used MUI + Formik + Yup before with a much more complex form without any lag when I scroll and I now can't seem to fix it. The documentation for MUI has also been extremely confusing with many different versions, depracated use cases and so on. I figured useMemo() could work in this instance as there is sure to be some excessive re-rendering, but I am not sure how to implement it.
I'll gladly give more info if needed.
import React from "react";
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import {emailSuccess, emailError, emailSubmitting} from "../lib/toasts";
import {useSubmit} from "../lib/useSubmit";
import {postJson} from "../lib/http";
import {useStyles} from "../lib/inputStyle";
import {TextField} from "#mui/material";
import SendRoundedIcon from "#mui/icons-material/SendRounded";
import { useFormik } from "formik";
import * as Yup from "yup";
export default function Contact () {
const toastId = React.useRef(null);
const classes = useStyles();
const formik = useFormik({
initialValues: {
name: '',
email: '',
message: ''
},
validationSchema: Yup.object({
name: Yup
.string()
.max(255)
.required(
'Name is required'),
email: Yup
.string()
.email(
'Must be a valid email')
.max(255)
.required(
'E-mail address is required'),
message: Yup
.string()
.max(1200)
.required(
'You cannot send an empty message')
}),
onSubmit: () => {
//This should be optional... Please fix!
}
});
const { handleSubmit: handleEmail, submitting, error } = useSubmit(
async () => {
emailSubmitting(toastId);
await postJson("/api/mail", {
name: formik.values.name,
email: formik.values.email,
message: formik.values.message,
});
},
() => {
emailSuccess(toastId);
},
);
if (error) {
emailError(toastId)
}
return (
<div className="contact">
<div className="section">
<div className="contact-innerRef"/>
<h4>Contact</h4>
<div className="contact-card">
<h5>Get In Touch</h5>
<hr/>
<form className="contact-form" action="" method="post" encType="text/plain">
<TextField className={classes.root}
error={Boolean(formik.touched.name && formik.errors.name)}
helperText={formik.touched.name && formik.errors.name}
label="Name"
margin="normal"
name="name"
onChange={formik.handleChange}
value={formik.values.name}
variant="outlined"
/>
<TextField className={classes.root}
error={Boolean(formik.touched.email && formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
label="Email"
margin="normal"
name="email"
onChange={formik.handleChange}
value={formik.values.email}
variant="outlined"
/>
<TextField className={classes.root}
error={Boolean(formik.touched.message && formik.errors.message)}
helperText={formik.touched.message && formik.errors.message}
label="Message"
margin="normal"
name="message"
onChange={formik.handleChange}
value={formik.values.message}
variant="outlined"
multiline
rows={7}
/>
</form>
<button onClick={handleEmail} disabled={!formik.isValid || !formik.dirty || submitting}
className="btn">
<SendRoundedIcon height="50%"/>
</button>
<ToastContainer />
</div>
</div>
</div>
);
}```
I am trying to get the validation errors to display on touch but for some reason they're not showing up. I've tried following the tutorial on the official Formik website, but to no avail. I am using React-Bootstrap, Formik, and Yup for validation. What am I doing wrong?
Imports:
import * as React from 'react';
import { Container, Row, Col, Form } from 'react-bootstrap';
import { Formik } from 'formik';
import * as yup from 'yup';
Validation Schema:
const validationSchema = yup.object({
companyName: yup
.string()
.required('Company Name is required')
.min(3, 'Minimum 3 characters allowed')
.max(100, 'Maximum 100 characters allowed'),
});
Form:
<Formik
validationSchema={validationSchema}
initialValues={{
companyName: '',
}}
onSubmit={() => {}}
>
{({ errors, touched }) => (
<Form autoComplete='off'>
<Form.Group>
<Form.Label>
Company Name <span className='text-danger'>(*)</span>
</Form.Label>
<Form.Control type='text' name='companyName' placeholder='Enter Company Name' />
<Form.Control.Feedback type='invalid'>{errors.companyName}</Form.Control.Feedback>
</Form.Group>
</Form>
)}
</Formik>
Seems like your input fields are not connected to the Formik.
You could use the Field from Formik to wire your inputs to Formik.
import * as React from 'react';
import { Container, Row, Col, Form } from 'react-bootstrap';
import { Formik, Field } from 'formik';
import * as yup from 'yup';
const validationSchema = yup.object({
companyName: yup
.string()
.required('Company Name is required')
.min(3, 'Minimum 3 characters allowed')
.max(100, 'Maximum 100 characters allowed'),
});
export default function App() {
return (
<Formik
validationSchema={validationSchema}
initialValues={{
companyName: '',
}}
onSubmit={() => {}}
>
{({ errors, touched }) => (
<Form autoComplete='off'>
<Form.Group>
<Form.Label>
Company Name <span className='text-danger'>(*)</span>
</Form.Label>
<Field type='text' placeholder='Enter Company Name' name="companyName" />
<Form.Control.Feedback type='invalid'>{errors.companyName}</Form.Control.Feedback>
</Form.Group>
</Form>
)}
</Formik>
);
}
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.
I have two identical components, and only few differences (one). There are two many repetitive code and boilerplate, but I am unsure how to refactor this so that I only need to supply a config probably.
LoginPage.js
import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';
import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { LoginValidationSchema } from 'validations/AuthValidationSchema';
function LoginPage({ username, onChangeUsername, onSubmitForm }) {
return (
<div>
<Helmet>
<title>Login</title>
</Helmet>
<Formik
initialValues={{ username, password: '' }}
validationSchema={LoginValidationSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
<FastField type="password" name="password" />
<ErrorMessage name="password" component="div" aria-live="polite" />
<button type="submit" disabled={isSubmitting || !isValid}>
Login
</button>
<FormDebug />
</Form>
)}
/>
<Link to="/auth/forgot_password">Forgot Password</Link>
</div>
);
}
LoginPage.propTypes = {
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};
export default LoginPage;
ForgotPasswordPage.js
import React from 'react';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';
import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import { ForgotPasswordValidationSchema } from 'validations/AuthValidationSchema';
function ForgotPasswordPage({ username, onChangeUsername, onSubmitForm }) {
return (
<div>
<Helmet>
<title>Forgot Password</title>
</Helmet>
<Formik
initialValues={{ username }}
validationSchema={ForgotPasswordValidationSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
<FormDebug />
<button type="submit" disabled={isSubmitting || !isValid}>
Reset Password
</button>
</Form>
)}
/>
</div>
);
}
ForgotPasswordPage.propTypes = {
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};
export default ForgotPasswordPage;
How to you refactor this, if you were me.
I am thinking HOC., but I am unsure how to call pass the "children" which is different
Apologies if you're not looking for a general answer, but I don't think you'll improve maintainability by generalising what may just be seemingly correlated components. I expect these components will drift further apart as you mature your application, e.g. by adding social login, option to 'remember me', captchas, option for retrieving both username and password by email, different handling of an unknown username when retrieving password vs signing in, etc. Also, this is a part of your component you really don't want to get wrong, so KISS and all. Finally, consider if there really is a third use case for such a semi-generalised login-or-retrieve-password form component.
Still, minor improvement could be made by e.g. creating a reusable UsernameField component, the usage of which will be simple and consistent to both cases. Also consider a withValidation HOC adding an error message to a field. If you really want to stretch it, you could have a withSubmit HOC for Formik, passing all props to Formik, rendering children (which you would pass handleChange prop) and a submit button. I assume form itself uses context to pass state to ErrorMessage and FastField.
I may be missing some complexity not stated here, but this looks as simple as creating a generic Functional Component that accepts a couple more passed-in properties. I did a diff and you would only need to add 'title', 'buttonText', and, if you like, 'type' to your props, for sure. You could also send initialValues object as a prop, instead of deriving it from 'type'.
I mean, did you try the following?
import React from 'react';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { Formik, FastField, Form, ErrorMessage } from 'formik';
import PropTypes from 'prop-types';
import { FormDebug } from 'utils/FormDebug';
import * as schema from 'validations/AuthValidationSchema';
function AuthPage({ buttonText, initialValues, title, type, username,
onChangeUsername, onSubmitForm }) {
const authSchema = type === 'login'
? schema.LoginValidationSchema
: schema.ForgotPasswordValidationSchema;
return (
<div>
<Helmet>
<title>{title}</title>
</Helmet>
<Formik
initialValues={initialValues}
validationSchema={authSchema}
onSubmit={onSubmitForm}
render={({ isSubmitting, isValid, handleChange }) => (
<Form>
<FastField
type="text"
name="username"
render={({ field }) => (
<input
{...field}
onChange={e => {
handleChange(e);
onChangeUsername(e);
}}
/>
)}
/>
<ErrorMessage name="username" component="div" aria-live="polite" />
{type === 'forgot' &&
<FastField type="password" name="password" />
<ErrorMessage name="password" component="div" aria-live="polite" />
}
<button type="submit" disabled={isSubmitting || !isValid}>
{buttonText}
</button>
<FormDebug />
</Form>
)}
/>
<Link to="/auth/forgot_password">Forgot Password</Link>
</div>
);
}
AuthPage.propTypes = {
buttonText: PropTypes.string,
initialValues: PropTypes.object,
title: PropTypes.string,
type: PropTypes.oneOf(['login', 'forgot'])
username: PropTypes.string,
onSubmitForm: PropTypes.func.isRequired,
onChangeUsername: PropTypes.func.isRequired,
};
export default AuthPage;
(Only thing I can't remember is whether the conditional render of password field and its ErrorMessage needs to be wrapped in a div or not to make them one element)
If you don't want to pass in initial values:
const initialVals = type === 'login'
? { username, password: ''}
: { username }
...
initialValues={initialVals}
and remove it from propTypes
Only other thing I'm not sure of is why FormDebug is placed differently in the two versions. I've left it after the button.
Those two pages have separate concerns, so I wouldn't suggest pulling logic that can be used for both cases as that tends to add complexity by moving code further apart even if it does remove a few duplicate lines. In this case a good strategy may be to think of things that you reuse within components.
For example you can wrap the formik component in your own wrapper and then wrap for example an input component that works with your new form. A good exercise in reducing boiler plate could be to aim for some variant of this end result
<CoolForm
initialValues={{ username, password: '' }}
validationSchema={LoginValidationSchema}
>
<CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
<CoolFormInputWithError someProps={{howeverYou: 'want to design this'}}>
<CoolFormSubmit>
Login
</CoolFormSubmit>
<FormDebug />
</CoolForm>
That's just an idea, but the nice thing about this strategy is that if you want to refactor formik out for whatever reason, its really simple because all your code now is wrapped in CoolForm components, so you only end up changing the implementation of your own components, although that benefit in this case is very small.