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 manipulationForm 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:
We can convert that into a controlled React component like so:
function HTMLForm() {
const [email, setEmail] = React.useState();
const [password, setPassword] = React.useState();
?
return (
);
}
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 hookUsing Formik with React contextUsing 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 youre 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 youre curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
?
return (
// We bind onSubmit to formik.handleSubmit
)
}
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} and onChange={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 youre curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
?
return (
)
}
Let’s take things even further with the included
Method 2: Using Formik with React context
The
Keep in mind, you don’t have to use these components when working with
Using
import { Formik } from formik;
?
function FormikRenderProps() {
const initialValues = {
email: ,
password:
};
function onSubmit(values) {
// Do stuff here…
alert(JSON.stringify(values, null, 2));
}
return (
)}
}
Notice that initialValues and onSubmit have been completely detached from useFormik. This means we are able to pass the props that
Here’s a refresher on React render props if you’re feeling a little rusty.
We haven’t actually put any
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 (
)}
}
We replaced with and removed the onSubmit handler since Formik handles that for us. Remember, it takes on all the responsibilities for handling forms.
We also replaced with
There’s also no need to bother with the returned value from
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 levelAt the field levelWith 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, orusing 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,
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:
},
// Weve 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._%+-][email protected][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 —
If there’s an error during validation, {touched[email] && errors[email]} will display it to the user.
We could do the same with
{errMsg => {errMsg}}
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!