Formik is a library that helps developers do deal with Forms in React and React Native.
As developers we need to:
- Handle form data
- Validation
- Visual feedback with errors messages
- Form submission
With Formik, we can do all that in an easy, scalable, and performant way.
npm i --save formik
Creating a Login Form with:
- Password
- Remember me
import React from "react";
/**
* LoginForm Component
*/
const LoginForm = () => {
return (
<form>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input type="email" id="email" />
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input type="password" id="password" />
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input type="checkbox" id="rememberMe" />
<button style={{ display: "block" }}>submit</button>
</div>
</form>
);
};
export default LoginForm;
- Import
useFormik
hook and addinitialValues
property with eachinput
name. - Add a
name
attritbute to eachinput
element corresponding toinitialValues
key names. - Add
formik.values[name]
tovalue
attribute on eachinput
element.- For checkboxes add
defaultChecked={formik.values[name]}
for initial value.
- For checkboxes add
- Add
formik.handleChange
toonChange
attribute on eachinput
element. - Print the
formik
object inside<pre>
tag. - Fill in all
input
elements and see the changes onformik
object.
import React from "react";
import { useFormik } from "formik";
/**
* LoginForm Component
*/
const LoginForm = () => {
const formik = useFormik({
initialValues: {
email: "",
password: "",
rememberMe: false,
},
});
return (
<form>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input
type="email"
id="email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
/>
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
/>
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
name="rememberMe"
id="rememberMe"
onChange={formik.handleChange}
defaultChecked={formik.values.rememberMe}
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</form>
);
};
export default LoginForm;
- Add
formik.handleSubmit
toonSubmit
attribute on theform
element. - Add
onSubmit
as a second property on the object insideuseFormik
hook. - log the
values
passed toonSubmit
function.
import React from "react";
import { useFormik } from "formik";
/**
* LoginForm Component
*/
const LoginForm = () => {
const formik = useFormik({
initialValues: {
email: "",
password: "",
rememberMe: false,
},
onSubmit: (values) => console.log(JSON.stringify(values, null, 4)),
});
return (
<form onSubmit={formik.handleSubmit}>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input
type="email"
id="email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
/>
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
/>
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
name="rememberMe"
id="rememberMe"
onChange={formik.handleChange}
defaultChecked={formik.values.rememberMe}
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</form>
);
};
export default LoginForm;
- Install Yup
npm install yup --save
- Import Yup
import * as yup from "yup"
- Create validationSchema property on the object inside
useFormik
hook. - Submit the form with no values and see the
errors
object inside the printedformik
object.
import React from "react";
import { useFormik } from "formik";
import * as yup from "yup";
/**
* LoginForm Component
*/
const LoginForm = () => {
const formik = useFormik({
initialValues: {
email: "",
password: "",
rememberMe: false,
},
onSubmit: (values) => console.log(JSON.stringify(values, null, 4)),
validationSchema: yup.object({
email: yup.string().email().required(),
password: yup.string().required(),
}),
});
return (
<form onSubmit={formik.handleSubmit}>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input
type="email"
id="email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
/>
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
/>
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
name="rememberMe"
id="rememberMe"
onChange={formik.handleChange}
defaultChecked={formik.values.rememberMe}
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</form>
);
};
export default LoginForm;
- Create a
<div>
tag and check forformik.errors[name]
to display the error message beneath each element that has a validation.
import React from "react";
import { useFormik } from "formik";
import * as yup from "yup";
/**
* LoginForm Component
*/
const LoginForm = () => {
const formik = useFormik({
initialValues: {
email: "",
password: "",
rememberMe: false,
},
onSubmit: (values) => console.log(JSON.stringify(values, null, 4)),
validationSchema: yup.object({
email: yup.string().email().required(),
password: yup.string().required(),
}),
});
return (
<form onSubmit={formik.handleSubmit}>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input
type="email"
id="email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
/>
{formik.errors.email && (
<div style={{ color: "red" }}>{formik.errors.email}</div>
)}
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
/>
{formik.errors.password && (
<div style={{ color: "red" }}>{formik.errors.password}</div>
)}
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
name="rememberMe"
id="rememberMe"
onChange={formik.handleChange}
defaultChecked={formik.values.rememberMe}
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</form>
);
};
export default LoginForm;
- Add
formik.handleBlur
withonBlur
attribute with allinput
elements. - Visit each
input
element and see thetouched
object inside the printedformik
object. - Add
formik.touched[name]
beforeformik.errors[name]
with the error tag element.
import React from "react";
import { useFormik } from "formik";
import * as yup from "yup";
/**
* LoginForm Component
*/
const LoginForm = () => {
const formik = useFormik({
initialValues: {
email: "",
password: "",
rememberMe: false,
},
onSubmit: (values) => console.log(JSON.stringify(values, null, 4)),
validationSchema: yup.object({
email: yup.string().email().required(),
password: yup.string().required(),
}),
});
return (
<form onSubmit={formik.handleSubmit}>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input
type="email"
id="email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.email && formik.errors.email && (
<div style={{ color: "red" }}>{formik.errors.email}</div>
)}
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.password && formik.errors.password && (
<div style={{ color: "red" }}>{formik.errors.password}</div>
)}
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
name="rememberMe"
id="rememberMe"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
defaultChecked={formik.values.rememberMe}
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</form>
);
};
export default LoginForm;
You can pass a custom validation message on each field as a string argument of the yup validation function.
import React from "react";
import { useFormik } from "formik";
import * as yup from "yup";
/**
* LoginForm Component
*/
const LoginForm = () => {
const formik = useFormik({
initialValues: {
email: "",
password: "",
rememberMe: false,
},
onSubmit: (values) => console.log(JSON.stringify(values, null, 4)),
validationSchema: yup.object({
email: yup
.string()
.email("Please enter a valid email address")
.required("Email field is required"),
password: yup.string().required("Password field is required"),
}),
});
return (
<form onSubmit={formik.handleSubmit}>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input
type="email"
id="email"
name="email"
value={formik.values.email}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.email && formik.errors.email && (
<div style={{ color: "red" }}>{formik.errors.email}</div>
)}
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
name="password"
value={formik.values.password}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
/>
{formik.touched.password && formik.errors.password && (
<div style={{ color: "red" }}>{formik.errors.password}</div>
)}
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
name="rememberMe"
id="rememberMe"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
defaultChecked={formik.values.rememberMe}
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</form>
);
};
export default LoginForm;
Let us make our code more organized and decrease the lines of code by doing the following:
- Substitute
value={formik.values[name]}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
with
{...formik.getFieldProps(name)}
- Separate
initialValues
,onSubmit
, andvalidationSchema
import React from "react";
import { useFormik } from "formik";
import * as yup from "yup";
/**
* LoginForm Component
*/
const LoginForm = () => {
const initialValues = {
email: "",
password: "",
rememberMe: false,
};
const onSubmit = (values) => console.log(JSON.stringify(values, null, 4));
const validationSchema = yup.object({
email: yup
.string()
.email("Please enter a valid email address")
.required("Email field is required"),
password: yup.string().required("Password field is required"),
});
const formik = useFormik({
initialValues,
onSubmit,
validationSchema,
});
return (
<form onSubmit={formik.handleSubmit}>
<div style={{ padding: 20 }}>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input
type="email"
id="email"
name="email"
{...formik.getFieldProps("email")}
/>
{formik.touched.email && formik.errors.email && (
<div style={{ color: "red" }}>{formik.errors.email}</div>
)}
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
name="password"
{...formik.getFieldProps("password")}
/>
{formik.touched.password && formik.errors.password && (
<div style={{ color: "red" }}>{formik.errors.password}</div>
)}
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
name="rememberMe"
id="rememberMe"
defaultChecked={formik.values.rememberMe}
{...formik.getFieldProps("rememberMe")}
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</form>
);
};
export default LoginForm;
Formik provides a few Components to be used that will save us more time, reduce code duplication, and make the code more consistent and organized.
- Import
Formik
,Form
,Field
andErrorMessage
fromformik
. - Wrap and return the entire form inside
<Formik>
Component withformik
as a parameter. - Add
initialValues
,onSubmit
andvalidationSchema
as Props to<Formik>
Component. - Replace the native
<form>
element with the<Form>
Component. - Wrap and return the
label
,input
, and thevalidation div message
inside<Field>
withformikField
as a parameter. - Add the
name
attribute to the<Field>
component instead of the<input>
element. - spread
{...formikField.field}
inside the<input>
element. - Wrap and return the
validation div
inside<ErrorMessage>
component withErrMessage
as a parameter and add thename
attribute to it. - log
formikField
inside a<pre>
to see the entireformikField
object.
import React from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
import * as yup from "yup";
/**
* LoginFormikComponents Component
*/
const LoginFormikComponents = () => {
const initialValues = {
email: "",
password: "",
rememberMe: false,
};
const onSubmit = (values) => console.log(JSON.stringify(values, null, 4));
const validationSchema = yup.object({
email: yup
.string()
.email("Please enter a valid email address")
.required("Email field is required"),
password: yup.string().required("Password field is required"),
});
return (
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
>
{(formik) => {
return (
<Form>
<div style={{ padding: 20 }}>
<Field name="email">
{(formikField) => {
return (
<>
<label htmlFor="email" style={{ display: "block" }}>
email
</label>
<input type="email" id="email" {...formikField.field} />
<ErrorMessage name="email">
{(errMessage) => {
return (
<div style={{ color: "red" }}>{errMessage}</div>
);
}}
</ErrorMessage>
<pre>{JSON.stringify(formikField, null, 4)}</pre>
</>
);
}}
</Field>
<Field name="password">
{(formikField) => {
return (
<>
<label htmlFor="password" style={{ display: "block" }}>
password
</label>
<input
type="password"
id="password"
{...formikField.field}
/>
<ErrorMessage name="password">
{(errMessage) => {
return (
<div style={{ color: "red" }}>{errMessage}</div>
);
}}
</ErrorMessage>
<pre>{JSON.stringify(formikField, null, 4)}</pre>
</>
);
}}
</Field>
<Field name="rememberMe">
{(formikField) => {
return (
<>
<label htmlFor="rememberMe" style={{ display: "block" }}>
remember me
</label>
<input
type="checkbox"
id="rememberMe"
defaultChecked={formikField.field.value}
{...formikField.field}
/>
<pre>{JSON.stringify(formikField, null, 4)}</pre>
</>
);
}}
</Field>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</Form>
);
}}
</Formik>
);
};
export default LoginFormikComponents;
As we can see inside each <Field>
component there are
<label>
<input>
<ErrorMessage>
So, it's a good chance to create a Reusable Component that includes all of that.
FormikErrorMessage.jsx
import React from "react";
import { ErrorMessage } from "formik";
/**
* FormikErrorMessage Component
*/
const FormikErrorMessage = ({ name }) => {
return (
<ErrorMessage name={name}>
{(errMessage) => {
return <div style={{ color: "red" }}>{errMessage}</div>;
}}
</ErrorMessage>
);
};
export default FormikErrorMessage;
FormikField.jsx
import React from "react";
import { Field } from "formik";
import FormikErrorMessage from "./FormikErrorMessage";
/**
* FormikField Component
*/
const FormikField = ({ name, type, label }) => {
return (
<Field name={name}>
{(formikField) => {
return (
<>
<label htmlFor={name} style={{ display: "block" }}>
{label}
</label>
<input
type={type}
id={name}
{...formikField.field}
defaultChecked={formikField.field.value}
/>
<FormikErrorMessage name={name} />
<pre>{JSON.stringify(formikField, null, 4)}</pre>
</>
);
}}
</Field>
);
};
export default FormikField;
And here is the final code after using the reusable components.
LoginFormikComponentsShared.jsx
import React from "react";
import { Formik, Form } from "formik";
import * as yup from "yup";
import FormikField from "./shared/FormikField";
/**
* LoginFormikComponents Component
*/
const LoginFormikComponents = () => {
const initialValues = {
email: "",
password: "",
rememberMe: false,
};
const onSubmit = (values) => console.log(JSON.stringify(values, null, 4));
const validationSchema = yup.object({
email: yup
.string()
.email("Please enter a valid email address")
.required("Email field is required"),
password: yup.string().required("Password field is required"),
});
return (
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
>
{(formik) => {
return (
<Form>
<div style={{ padding: 20 }}>
<FormikField label="Email" name="email" type="email" />
<FormikField label="Password" name="password" type="password" />
<FormikField
label="Remember Me"
name="rememberMe"
type="checkbox"
/>
<button style={{ display: "block" }}>submit</button>
</div>
<pre>{JSON.stringify(formik, null, 4)}</pre>
</Form>
);
}}
</Formik>
);
};
export default LoginFormikComponents;