There is no doubt that web forms play an integral role in our web site or applications. By default, they provide a useful set of elements and features — from legends and fieldsets to native validation and states — but they only get us so far when we start to consider the peculiarities of using them. For example, how can we manipulate the state of a form? How about different forms of validation? Even hooking a form up to post submissions is a daunting effort at times.
Component-driven front-end libraries, like React, can ease the task of wiring web forms but can also get verbose and redundant. That’s why I want to introduce you to Formik, a small library that solves the three most annoying parts of writing forms in React:
- State manipulation
- Form validation (and error messages)
- Form submission
We’re going to build a form together in this post. We’ll start with a React component then integrate Formik while demonstrating the way it handles state, validation, and submissions.
Creating a form as a React component
Components live and breathe through their state and prop. What HTML form elements have in common with React components is that they naturally keep some internal state. Their values are also automatically stored in their value attribute.
Allowing form elements to manage their own state in React makes them uncontrolled components. That’s just a fancy way of saying the DOM handles the state instead of React. And while that works, it is often easier to use controlled components, where React handles the state and serves as the single source of truth rather than the DOM.
The markup for a straightforward HTML form might look something like this:
<form>
<div className="formRow">
<label htmlFor="email">Email address</label>
<input type="email" name="email" className="email" />
</div>
<div className="formRow">
<label htmlFor="password">Password</label>
<input type="password" name="password" className="password" />
</div>
<button type="submit">Submit</button>
</form>
We can convert that into a controlled React component like so:
function HTMLForm() {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
return (
<form>
<div className="formRow">
<label htmlFor="email">Email address</label>
<input
type="email"
name="email"
className="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
</div>
<div className="formRow">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
className="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}
This is a bit verbose but it comes with some benefits:
- We get a single source of truth for form values in the state.
- We can validate the form when and how we want.
- We get performance perks by loading what we need and when we need it.
OK, so why Formik again?
As it is with anything JavaScript, there’s already a bevy of form management libraries out there, like React Hook Form and Redux Form, that we can use. But there are several things that make Formik stand out from the pack:
- It’s declarative: Formik eliminates redundancy through abstraction and taking responsibility for state, validation and submissions.
- It offers an Escape Hatch: Abstraction is good, but forms are peculiar to certain patterns. Formik abstracts for you but also let’s you control it should you need to.
- It co-locates form states: Formik keeps everything that has to do with your form within your form components.
- It’s adaptable: Formik doesn’t enforce any rules on you. You can use as less or as much Formik as you need.
- Easy to use: Formik just works.
Sound good? Let’s implement Formik into our form component.
Going Formik
We will be building a basic login form to get our beaks wet with the fundamentals. We’ll be touching on three different ways to work with Formik:
- Using the
useFormik
hook - Using
Formik
with React context - Using
withFormik
as a higher-order component
I’ve created a demo with the packages we need, Formik and Yup.
Method 1: Using the useFormik hook
As it is right now, our form does nothing tangible. To start using Formik, we need to import the useFormik
hook. When we use the hook, it returns all of the Formik functions and variables that help us manage the form. If we were to log the returned values to the console, we get this:
We’ll call useFormik
and pass it initialValues
to start. Then, an onSubmit
handler fires when a form submission happens. Here’s how that looks:
// This is a React component
function BaseFormik() {
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
onSubmit(values) {
// This will run when the form is submitted
}
});
// If you're curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
return (
// Your actual form
)
}
Then we’ll bind Formik to our form elements:
// This is a React component
function BaseFormik() {
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
onSubmit(values) {
// This will run when the form is submitted
}
});
// If you're curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
return (
// We bind "onSubmit" to "formik.handleSubmit"
<form className="baseForm" onSubmit={formik.handleSubmit} noValidate>
<input
type="email"
name="email"
id="email"
className="email formField"
value={formik.values.email} // We also bind our email value
onChange={formik.handleChange} // And, we bind our "onChange" event.
/>
</form>
)
}
This is how the binding works:
- It handles form submission with
onSubmit={formik.handleSubmit}
. - It handles the state of inputs with
value={formik.values.email}
andonChange={formik.handleChange}
.
If you take a closer look, we didn’t have to set up our state, nor handle the onChange
or onSubmit
events as we’d typically do with React.
However as you might have noticed, our form contains some redundancy. We had to drill down formik and manually bind the form input’s value
and onChange
event. That means we should de-structure the returned value and immediately bind the necessary props to a dependent field, like this:
// This is a React component
function BaseFormik() {
const {getFieldProps, handleSubmit} = useFormik({
initialValues: {
email: "",
password: ""
},
onSubmit(values) {
// This will run when the form is submitted
}
});
// If you're curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
return (
<form className="baseForm" onSubmit={handleSubmit} noValidate>
<input
type="email"
id="email"
className="email formField"
{...getFieldProps("email")} // We pass the name of the dependent field
/>
</form>
)
}
Let’s take things even further with the included <Formik/>
component.
Method 2: Using Formik with React context
The <Formik/>
component exposes various other components that adds more abstraction and sensible defaults. For example, components like <Form/
>, <Field/>
, and <ErrorMessage/>
are ready to go right out of the box.
Keep in mind, you don’t have to use these components when working with <Formik/>
but they do require <Formik/>
(or withFormik
) when using them.
Using <Formik/>
requires an overhaul because it uses the render props pattern as opposed to hooks with useFormik
. The render props pattern isn’t something new in React. It is a pattern that enables code re-usability between components — something hooks solve better. Nevertheless, <Formik/>
has a bagful of custom components that make working with forms much easier.
import { Formik } from "formik";
function FormikRenderProps() {
const initialValues = {
email: "",
password: ""
};
function onSubmit(values) {
// Do stuff here...
alert(JSON.stringify(values, null, 2));
}
return (
<Formik {...{ initialValues, onSubmit }}>
{({ getFieldProps, handleSubmit }) => (
<form className="baseForm" onSubmit={handleSubmit} noValidate>
<input
type="email"
id="email"
className="email formField"
{...getFieldProps("email")}
/>
</form>
)}
</Formik>
);
}
Notice that initialValues
and onSubmit
have been completely detached from useFormik
. This means we are able to pass the props that <Formik/>
needs, specifically initialValues
and useFormik
.
<Formik/>
returns a value that’s been de-structured into getFieldProps
and handleSubmit
. Everything else basically remains the same as the first method using useFormik
.
Here’s a refresher on React render props if you’re feeling a little rusty.
We haven’t actually put any <Formik/>
components to use just yet. I’ve done this intentionally to demonstrate Formik’s adaptability. We certainly do want to use those components for our form fields, so let’s rewrite the component so it uses the <Form/>
component.
import { Formik, Field, Form } from "formik";
function FormikRenderProps() {
const initialValues = {
email: "",
password: ""
};
function onSubmit(values) {
// Do stuff here...
alert(JSON.stringify(values, null, 2));
}
return (
<Formik {...{ initialValues, onSubmit }}>
{() => (
<Form className="baseForm" noValidate>
<Field
type="email"
id="email"
className="email formField"
name="email"
/>
</Form>
)}
</Formik>
);
}
We replaced <form/>
with <Form/>
and removed the onSubmit
handler since Formik handles that for us. Remember, it takes on all the responsibilities for handling forms.
We also replaced <input/>
with <Field/>
and removed the bindings. Again, Formik handles that.
There’s also no need to bother with the returned value from <Formik/>
anymore. You guessed it, Formik handles that as well.
Formik handles everything for us. We can now focus more on the business logic of our forms rather than things that can essentially be abstracted.
We’re pretty much set to go and guess what? We’ve haven’t been concerned with state managements or form submissions!
“What about validation?” you may ask. We haven’t touched on that because it’s a whole new level on its own. Let’s touch on that before jumping to the last method.
Form validation with Formik
If you’ve ever worked with forms (and I bet you have), then you’re aware that validation isn’t something to neglect.
We want to take control of when and how to validate so new opportunities open up to create better user experiences. Gmail, for example, will not let you input a password unless the email address input is validated and authenticated. We could also do something where we validate on the spot and display messaging without additional interactions or page refreshes.
Here are three ways that Formik is able to handle validation:
- At the form level
- At the field level
- With manual triggers
Validation at the form level means validating the form as a whole. Since we have immediate access to form values, we can validate the entire form at once by either:
- using
validate
, or - using a third-party library with
validationSchema
.
Both validate
and validationSchema
are functions that return an errors
object with key/value pairings that those of initialValues
. We can pass those to useFormik
, <Formik/>
or withFormik
.
While validate
is used for custom validations, validationSchema
is used with a third-party library like Yup.
Here’s an example using validate
:
// Pass the `onSubmit` function that gets called when the form is submitted.
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
// We've added a validate function
validate() {
const errors = {};
// Add the touched to avoid the validator validating all fields at once
if (formik.touched.email && !formik.values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(formik.values.email)
) {
errors.email = "Invalid email address";
}
if (formik.touched.password && !formik.values.password) {
errors.password = "Required";
} else if (formik.values.password.length <= 8) {
errors.password = "Must be more than 8 characters";
}
return errors;
},
onSubmit(values) {
// Do stuff here...
}
});
// ...
And here we go with an example using validationSchema
instead:
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
// We used Yup here.
validationSchema: Yup.object().shape({
email: Yup.string()
.email("Invalid email address")
.required("Required"),
password: Yup.string()
.min(8, "Must be more than 8 characters")
.required("Required")
}),
onSubmit(values) {
// Do stuff here...
}
});
Validating at the field level or using manual triggers are fairly simple to understand. Albeit, you’ll likely use form level validation most of the time. It’s also worth checking out the docs to see other use cases.
Method 3: Using withFormik as a higher-order component
withFormik
is a higher-order component and be used that way if that’s your thing. Write the form, then expose it through Formik.
A couple of practical examples
So far, we’ve become acquainted with Formik, covered the benefits of using it for creating forms in React, and covered a few methods to implement it as a React component while demonstrating various ways we can use it for validation. What we haven’t done is looked at examples of those key concepts.
So, let’s look at a couple of practical applications: displaying error messages and generating a username based on what’s entered in the email input.
Displaying error messages
We’ve built our form and validated it. And we’ve caught some errors that can be found in our errors
object. But it’s no use if we aren’t actually displaying those errors.
Formik makes this a pretty trivial task. All we need to do is check the errors
object returned by any of the methods we’ve looked at — <Formik/>
, useFormik
or withFormik
— and display them:
<label className="formFieldLabel" htmlFor="email">
Email address
<span className="errorMessage">
{touched["email"] && errors["email"]}
</span>
</label>
<div className="formFieldWrapInner">
<input
type="email"
id="email"
className="email formField"
{...getFieldProps("email")}
/>
</div>
If there’s an error during validation, {touched["email"] && errors["email"]}
will display it to the user.
We could do the same with <ErrorMessage/>
. With this, we only need to tell it the name of the dependent field to watch:
<ErrorMessage name="email">
{errMsg => <span className="errorMessage">{errMsg}</span>}
</ErrorMessage>
Generating a username from an email address
Imagine a form that automatically generates a username for your users based on their email address. In other words, whatever the user types into the email input gets pulled out, stripped of @
and everything after it, and leaves us with a username with what’s left.
For example: [email protected]
produces @jane
.
Formik exposes helpers that can “intercept” its functionality and lets us perform some effects.In the case of auto-generating a username, one way will be through Formik’s setValues
:
onSubmit(values) {
// We added a `username` value for the user which is everything before @ in their email address.
setValues({
...values,
username: `@${values.email.split("@")[0]}`
});
}
Type in an email address and password, then submit the form to see your new username!
Wrapping up
Wow, we covered a lot of ground in a short amount of space. While this is merely the tip of the iceberg as far as covering all the needs of a form and what Formik is capable of doing, I hope this gives you a new tool to reach for the next time you find yourself tackling forms in a React application.
If you’re ready to take Formik to the next level, I’d suggest looking through their resources as a starting point. There are so many goodies in there and it’s a good archive of what Formik can do as well as more tutorials that get into deeper use cases.
Good luck with your forms!