One form you will likely need to build is a login form. Accepting an email, password can seem simple but there are a lot of complicated interactions to tackle. Validating the minimum length of a password, validating emails, preventing submission during errors, displaying errors correctly. There are many things that Formik
will handle and step out of the way and allow you to build a login form with ease.
We'll use yup
and all we will need form yup
is object
and string
.
import { object, string } from "yup";
For our login validation we'll use the structure of email
and password
.
Looking at email
structure. We'll first say it's required
. Then also say that the structure needs to match email
. Which will validate that it's a proper email structure. We can change the message we show to the user based upon if it's empty we'll say Required
, and if it's email
we'll add a more specific message.
string().required("Required").email("Valid email required");
Now looking at password
structure it will need to be a string
. With a minimum of 8
characters and also required
.
string().min(8, "Required").required("Required");
Bringing it all together we have our Login
validation which we use object
to create a shape of our data and use our validations to apply to email
field and our password
field.
const LoginValidation = object().shape({ email: string().required("Required").email("Valid email required"), password: string().min(8, "Required").required("Required"), });
Now we need to setup our form. There is a lot of styling going on here which we will ignore.
The key features here we are setting up our initial values for email
and password
. Passing our Formik
component the onSubmit
function, and finally passing our LoginValidation
to the validationSchema
.
Our submit button is type="submit"
which when pressed will touch all fields, and trigger our validation. If all validation passes then the onSubmit
function will be called. Any errors and it will not be called.
const handleSubmit = (values) => { console.log(values); }; return ( <div className="h-screen flex items-center justify-center flex-col bg-gray-100"> <Formik initialValues={{ email: "", password: "", }} onSubmit={handleSubmit} validationSchema={LoginValidation} > {() => { return ( <Form className="bg-white w-6/12 shadow-md rounded px-8 pt-6 pb-8"> <div className="flex items-center justify-between"> <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit" > Sign In </button> </div> </Form> ); }} </Formik> </div> );
Our input will take advantage of useField
. We will accept the name
for the field as a property, as well as the label for the field to display. The rest will be props
that can be spread onto the input.
const Input = ({ name, label, ...props }) => { const [field, meta] = useField(name); return ( <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold" for={field.name}> {label} </label> <input {...field} {...props} /> </div> ); };
For toggling our border when an error happens we use the meta
field to check error
and touched
. This will then set the inputs border color to red
so the user knows there is an error.
meta.error && meta.touched ? "border-red-500" : "";
Then we'll add our ErrorMessage
and pass the field.name
to it. We'll say it's a component="div"
so that it will force itself to the next line and then apply class names.
const Input = ({ name, label, ...props }) => { const [field, meta] = useField(name); return ( <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold" for={field.name}> {label} </label> <input className={`${ meta.error && meta.touched ? "border-red-500" : "" } shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline`} {...field} {...props} /> <ErrorMessage name={field.name} component="div" className="text-red-500 text-xs" /> </div> ); };
All together the glue code for a Login form is pretty minimal. We declared some validation, passed it to our form, glued an input in using useField
, and handling whether or not onSubmit
is called automatically. The rest of this is just styling.
import React from "react"; import { Formik, Form, Field, useField, ErrorMessage } from "formik"; import { object, string } from "yup"; const LoginValidation = object().shape({ email: string().required("Required").email("Valid email required"), password: string().min(8, "Required").required("Required"), }); const Input = ({ name, label, ...props }) => { const [field, meta] = useField(name); return ( <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold" for={field.name}> {label} </label> <input className={`${ meta.error && meta.touched ? "border-red-500" : "" } shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline`} {...field} {...props} /> <ErrorMessage name={field.name} component="div" className="text-red-500 text-xs" /> </div> ); }; function App() { const handleSubmit = (values) => { console.log(values); }; return ( <div className="h-screen flex items-center justify-center flex-col bg-gray-100"> <Formik initialValues={{ email: "", password: "", }} onSubmit={handleSubmit} validationSchema={LoginValidation} > {() => { return ( <Form className="bg-white w-6/12 shadow-md rounded px-8 pt-6 pb-8"> <Input name="email" label="Email" />; <Input name="password" label="Password" type="password" /> <div className="flex items-center justify-between"> <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline" type="submit" > Sign In </button> </div> </Form> ); }} </Formik> </div> ); } export default App;