Easy Peasy Introduction
Easy Peasy is an abstraction of Redus that allows you to quickly and easily manage your React State. No configuration is required.
Installation
- React and React Dom must be installed, with version higher than
v16.8.0.
npm install react
npm install react-dom
- Then, install Easy Peasy
npm install easy-peasy
Overview: What is Formik?
- Formik is a small group of react components and hooks for building forms in React.
- It helps with three annoying parts:
- Getting values in and out of form state
- Validation and Error Messages
- Handling Form Submission
- Formik assists in organizing, testing, refactoring, and reasoning your forms.
The Basics:
A simple newsletter sign-up form.
- The example found in the following path is a newsletter sign-up form:
examples/React/packages/formik
- The
initialValuesare set in the Formik instantiation along with anonSubmithook. - Here are the helper methods in the first example:
handleSubmit- A submission handler.handleChange- A change handler to pass to each input, select, or textarea.value- The forms current values.
- Some Things to Notice (After adding First Name and Last Name)
- The exact same change handler is used for each form input.
- The
idandnameHTML attributes match the property defined ininitialValues.
- In terms of a form that is pure react, the form can be thought of like this:
const [values, setValues] = React.useState({});
const handleChange = event => {
setValues(prevValues => ({
...prevValues,
// we use the name to tell Formik which key of `values` to update
[event.target.name]: event.target.value
});
}
Validation
- The built in HTML validation does work well with Formik.
- However, this is difficult to show to the user and only works in-browser.
- Formik can keep track of the forms validations and error messages.
- To add validation, specify a validate function and produce an error object that matches the shape of
values/initialValues.
import React from "react";
import { useFormik } from "formik";
// A custom validation function. This must return an object
// which keys are symmetrical to our values/initialValues
const validate = (values) => {
const errors = {};
if (!values.firstName) {
errors.firstName = "Required";
} else if (values.firstName.length > 15) {
errors.firstName = "Must be 15 characters or less";
}
if (!values.lastName) {
errors.lastName = "Required";
} else if (values.lastName.length > 20) {
errors.lastName = "Must be 20 characters or less";
}
if (!values.email) {
errors.email = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = "Invalid email address";
}
return errors;
};
const SignupForm = () => {
// Pass the useFormik() hook initial form values, a validate function that will be called when
// form values change or fields are blurred, and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
firstName: "",
lastName: "",
email: "",
},
validate,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
value={formik.values.firstName}
/>
{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null}
<button type="submit">Submit</button>
</form>
);
};
- On default, the validation occurs after every keystroke, blur event, and before submission. The
onSubmitfunction will only trigger if there are no errors.
Visited Fields
- Showing errors on all the fields is a confusing UX for the users.
- We only want to show errors for the elements that the user's have seen.
- Formik keeps track of which fields have been visited. It stores its information in a field called
touched, which mirrors the same shape as the others.- The keys are the name of the field and the value is a boolean.
- To take advantage of touched, we can pass formik.handleBlur to each onBlur prop.
import React from "react";
import { useFormik } from "formik";
const validate = (values) => {
const errors = {};
if (!values.firstName) {
errors.firstName = "Required";
} else if (values.firstName.length > 15) {
errors.firstName = "Must be 15 characters or less";
}
if (!values.lastName) {
errors.lastName = "Required";
} else if (values.lastName.length > 20) {
errors.lastName = "Must be 20 characters or less";
}
if (!values.email) {
errors.email = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = "Invalid email address";
}
return errors;
};
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: "",
lastName: "",
email: "",
},
validate,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.errors.email ? <div>{formik.errors.email}</div> : null}
<button type="submit">Submit</button>
</form>
);
};
- Then, the error logic can be changed to check if the variable has been touched.
import React from "react";
import { useFormik } from "formik";
const validate = (values) => {
const errors = {};
if (!values.firstName) {
errors.firstName = "Required";
} else if (values.firstName.length > 15) {
errors.firstName = "Must be 15 characters or less";
}
if (!values.lastName) {
errors.lastName = "Required";
} else if (values.lastName.length > 20) {
errors.lastName = "Must be 20 characters or less";
}
if (!values.email) {
errors.email = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = "Invalid email address";
}
return errors;
};
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: "",
lastName: "",
email: "",
},
validate,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};
Schema Validation with Yup
- Lots of people use (Yup) for object schema validation.
- To install:
npm install yup --save
# or via yarn
yarn add yup
- To get it working with up, change out the custom validation and rewrite it with Yup:
import React from "react";
import { useFormik } from "formik";
import * as Yup from "yup";
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: "",
lastName: "",
email: "",
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, "Must be 15 characters or less")
.required("Required"),
lastName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Required"),
email: Yup.string().email("Invalid email address").required("Required"),
}),
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.firstName}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.lastName}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
onBlur={formik.handleBlur}
value={formik.values.email}
/>
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};
Reducing Boilerplate
Get Field Props
- To save time,
useFormik()returns a helper method calledformik.getFieldProps()to make it simpler to wire up inputs. It returns the exact group ofonChange,onBlur,value, andcheckedfor a given field.
import React from "react";
import { useFormik } from "formik";
import * as Yup from "yup";
const SignupForm = () => {
const formik = useFormik({
initialValues: {
firstName: "",
lastName: "",
email: "",
},
validationSchema: Yup.object({
firstName: Yup.string()
.max(15, "Must be 15 characters or less")
.required("Required"),
lastName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Required"),
email: Yup.string().email("Invalid email address").required("Required"),
}),
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
type="text"
{...formik.getFieldProps("firstName")}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input id="lastName" type="text" {...formik.getFieldProps("lastName")} />
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input id="email" type="email" {...formik.getFieldProps("email")} />
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
);
};
Leveraging React Context
- Formik comes with React-Context Components to make everything more verbose.
- Examples:
<Formik /><Form /><Field /><ErrorMessage />
import React from "react";
import { Formik } from "formik";
import * as Yup from "yup";
const SignupForm = () => {
return (
<Formik
initialValues={{ firstName: "", lastName: "", email: "" }}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, "Must be 15 characters or less")
.required("Required"),
lastName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Required"),
email: Yup.string().email("Invalid email address").required("Required"),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{(formik) => (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="firstName"
type="text"
{...formik.getFieldProps("firstName")}
/>
{formik.touched.firstName && formik.errors.firstName ? (
<div>{formik.errors.firstName}</div>
) : null}
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
type="text"
{...formik.getFieldProps("lastName")}
/>
{formik.touched.lastName && formik.errors.lastName ? (
<div>{formik.errors.lastName}</div>
) : null}
<label htmlFor="email">Email Address</label>
<input id="email" type="email" {...formik.getFieldProps("email")} />
{formik.touched.email && formik.errors.email ? (
<div>{formik.errors.email}</div>
) : null}
<button type="submit">Submit</button>
</form>
)}
</Formik>
);
};
- This is what is actually happening internally!
- The
<Field>component will render an<input>component that, when given anameprop, will automatically grab the respectiveonChange,onBlur, and value props and pass them to the element.
// <input className="form-input" placeHolder="Jane" />
<Field name="firstName" className="form-input" placeholder="Jane" />
// <textarea className="form-textarea"/></textarea>
<Field name="message" as="textarea" className="form-textarea" />
// <select className="my-select"/>
<Field name="colors" as="select" className="my-select">
<option value="red">Red</option>
<option value="green">Green</option>
<option value="blue">Blue</option>
</Field>
Wrapping Up
- You can even abstract more if you'd like to:
import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form, useField } from "formik";
import * as Yup from "yup";
const MyTextInput = ({ label, ...props }) => {
// useField() returns [formik.getFieldProps(), formik.getFieldMeta()]
// which we can spread on <input>. We can use field meta to show an error
// message if the field is invalid and it has been touched (i.e. visited)
const [field, meta] = useField(props);
return (
<>
<label htmlFor={props.id || props.name}>{label}</label>
<input className="text-input" {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</>
);
};
const MyCheckbox = ({ children, ...props }) => {
// React treats radios and checkbox inputs differently from other input types: select and textarea.
// Formik does this too! When you specify `type` to useField(), it will
// return the correct bag of props for you -- a `checked` prop will be included
// in `field` alongside `name`, `value`, `onChange`, and `onBlur`
const [field, meta] = useField({ ...props, type: "checkbox" });
return (
<div>
<label className="checkbox-input">
<input type="checkbox" {...field} {...props} />
{children}
</label>
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
const MySelect = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<select {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
// And now we can use these
const SignupForm = () => {
return (
<>
<h1>Subscribe!</h1>
<Formik
initialValues={{
firstName: "",
lastName: "",
email: "",
acceptedTerms: false, // added for our checkbox
jobType: "", // added for our select
}}
validationSchema={Yup.object({
firstName: Yup.string()
.max(15, "Must be 15 characters or less")
.required("Required"),
lastName: Yup.string()
.max(20, "Must be 20 characters or less")
.required("Required"),
email: Yup.string()
.email("Invalid email address")
.required("Required"),
acceptedTerms: Yup.boolean()
.required("Required")
.oneOf([true], "You must accept the terms and conditions."),
jobType: Yup.string()
.oneOf(
["designer", "development", "product", "other"],
"Invalid Job Type"
)
.required("Required"),
})}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
<Form>
<MyTextInput
label="First Name"
name="firstName"
type="text"
placeholder="Jane"
/>
<MyTextInput
label="Last Name"
name="lastName"
type="text"
placeholder="Doe"
/>
<MyTextInput
label="Email Address"
name="email"
type="email"
placeholder="jane@formik.com"
/>
<MySelect label="Job Type" name="jobType">
<option value="">Select a job type</option>
<option value="designer">Designer</option>
<option value="development">Developer</option>
<option value="product">Product Manager</option>
<option value="other">Other</option>
</MySelect>
<MyCheckbox name="acceptedTerms">
I accept the terms and conditions
</MyCheckbox>
<button type="submit">Submit</button>
</Form>
</Formik>
</>
);
};