- keeping track of values/errors/visited fields, orchestrating validation, and handling submission
- plain React state and props
- form state is inherently local and ephemeral
Home
Less Code, Fewer Edge Cases
Formik comes with battle-tested solutions for input validation, formatting, masking, arrays, and error handling.
Battery-pack included
Battery-pack included, security, accessibility, and user experience research.
- Form-level Validation
- Field-level Validation
- Custom Validation
- Dependent Validation
- React Native
- Error Messages
- Array Fields
- Internationalization(i18n)
- Conditional Logic
- Tree shakeable
- Wizards and multi-step forms
- API Errors
- Auto-saving forms
- Dynamic Fields
Overview
- Getting values in and out of form state
- Validation and error messages
- Handling form submission
Motivation
standardizing and data flowed
- Form state is inherently ephemeral and local
- Redux-Form calls your entire top-level Redux reducer multiple times on every single keystroke, as your Redux app grows, input latency will continue to increase
- Redux-Form is 22.5 kB minified gzipped (Formik is 12.7 kB)
Installation
NPM
npm install formik --save
yarn add formik
CDN
<script src="https://unpkg.com/formik/dist/formik.umd.production.min.js"></script>
In-browser Playgrounds
The Gist
- handleChange
- handleBlur
- handleSubmit
- name/id
import React from "react";
import { Formik } from "formik";
const Basic = () => (
<div>
<h1>Anywhere in your app!</h1>
<Formik
initialValues={{ email: "", password: "" }}
validate={(values) => {
const errors = {};
if (!values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Invalid email address";
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
/* and other goodies */
}) => (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{errors.email && touched.email && errors.email}
<input
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{errors.password && touched.password && errors.password}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
</Formik>
</div>
);
export default Basic;
Reducing boilerplate
onChange
-> handleChange
onBlur
-> handleBlur
Formik comes with a few extra components:
<Form />
, <Field />
, <ErrorMessage />
. They use React context to hook into the parent <Formik />
state/methods.
// Render Prop
import React from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
const Basic = () => (
<div>
<h1>Any place in your app!</h1>
<Formik
initialValues={{ email: "", password: "" }}
validate={(values) => {
const errors = {};
if (!values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = "Invalid email address";
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({ isSubmitting }) => (
<Form>
<Field type="email" name="email" />
<ErrorMessage name="email" component="div" />
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
</div>
);
export default Basic;
Complementary Packages
Formik has a special config option / prop for Yup called validationSchema
which will automatically transform Yup's validation errors into a pretty object whose keys match values
and touched
.
npm install yup --save
yarn add yup
Tutorial
Before we start
What are we building?
We'll build a complex newsletter signup form with React and Formik. Final Result
Prerequisites
You'll need to have familiarity with HTML, CSS, JavaScript and React to fully understand Formik and how it works.
Setup for the Tutorial
Setup Option 1: Write Code in the Browser
This is the quickest way to get started. Display an email address input, a submit button, and some React code.
Setup Option 2: Local Development Environment
formik-examples****
npm create-react-app formik-examples
yarn add formik
rm -rf ./src/*
+ src/index.js
+ src/styles.css
The Basics
A simple newsletter signup form
import React from "react";
import { useFormik } from "formik";
const SignupForm = () => {
// Pass the useFormik() hook initial form values and a submit function that will
// be called when the form is submitted
const formik = useFormik({
initialValues: {
email: "",
},
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};
- handleSubmit: A submission handler
- handleChange: A change handler to pass to each
<input>
,<select>
, or<textarea>
- values: Our form's current values
import React from "react";
import { useFormik } from "formik";
const SignupForm = () => {
// Note that we have to initialize ALL of fields with values. These
// could come from props, but since we don’t want to prefill this form,
// we just use an empty string. If we don’t do this, React will yell
// at us.
const formik = useFormik({
initialValues: {
firstName: "",
lastName: "",
email: "",
},
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}
/>
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};
- We reuse the same exact change handler function
handleChange
for each HTML input - We pass an
id
andname
HTML attribute that matches the property we defined ininitialValues
- We access the field's value using the same name (
email -> formik.values.email
)
Formik's handleChange
as working 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
HTML validation: could add a required prop to each of our inputs, specify minimum/maximum lengths (maxlength
and minlength
), and/or add a pattern
prop for regex validation for each of these inputs.
However, HTML validation has its limitations.
- First, it only works in the browser! So this clearly is not viable for React Native.
- Second, it's hard/impossible to show custom error messages to our user.
- Third, it's very janky.
To add validation with JS, let's specify a custom validation function and pass it as validate
to the useFormik()
hook. If an error
exists, this custom validation function should produce an error object with a matching shape to our 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>
);
};
Visited fields
We only want to show a field's error message after our user is done typing in that field.
Like errors
and values
, Formik keeps track of which fields have been visited. It stores this information in an object called touched
that also mirrors the shape of values/initialValues
. The keys of touched
are the field names, and the values of touched
are booleans true/false
.
To take advantage of touched
, we pass formik.handleBlur
to each input's onBlur
prop. We can now change our error message render logic to only show a given field's error message if it exists and if our user has visited that field.
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
Formik has a special configuration prop for Yup called validationSchema
which will automatically transform Yup's validation errors messages into a pretty object whose keys match values/initialValues/touched
(just like any custom validation function would have to).
yarn add 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
getFieldProps()
onChange
->handleChange
onBlur
->handleBlur
- ...
To save you time, useFormik()
returns a helper method called formik.getFieldProps() to make it faster to wire up inputs. Given some field-level info, it returns to you the exact group of onChange
, onBlur
, value
, checked
for a given field. You can then spread that on an input
, select
, or textarea
.
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-powered API/components to make life easier and code less verbose: <Formik />
, <Form />
, <Field />
, and <ErrorMessage />
. More explicitly, they use React Context implicitly to connect with the parent <Formik />
state/methods.
If you did this yourself, it would look like:
import React from "react";
import { useFormik } from "formik";
// Create empty context
const FormikContext = React.createContext({});
// Place all of what’s returned by useFormik into context
export const Formik = ({ children, ...props }) => {
const formikStateAndHelpers = useFormik(props);
return (
<FormikContext.Provider value={formikStateAndHelpers}>
{typeof children === "function"
? children(formikStateAndHelpers)
: children}
</FormikContext.Provider>
);
};
We've done this for you in a <Formik>
component that works just like this.
Let's now swap out the useFormik()
hook for Formik's <Formik>
component/render-prop.
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>
);
};
import React from "react";
import { Formik, Field, Form, ErrorMessage } 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);
}}
>
<Form>
<label htmlFor="firstName">First Name</label>
<Field name="firstName" type="text" />
<ErrorMessage name="firstName" />
<label htmlFor="lastName">Last Name</label>
<Field name="lastName" type="text" />
<ErrorMessage name="lastName" />
<label htmlFor="email">Email Address</label>
<Field name="email" type="email" />
<ErrorMessage name="email" />
<button type="submit">Submit</button>
</Form>
</Formik>
);
};
// <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>
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 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>
</>
);
};
Wrapping Up
- Has complex validation logic and rich error messages
- Properly displays errors messages to the user at the correct time (after they have blurred a field)
- Leverages your own custom input components you can use on other forms in your app
Guides
Validation
Formik supports synchronous and asynchronous form-level and field-level validation. Furthermore, it comes with baked-in support for schema-based form-level validation through Yup.
Flavors of Validation
Form-level Validation
<Formik validate>
andwithFormik({ validate: ... })
<Formik validationSchema>
andwithFormik({ validationSchema: ... })
validate
<Formik>
and withFormik()
take a prop/option called validate that accepts either a synchronous or asynchronous function.
// Synchronous validation
const validate = (values, props /* only available when using withFormik */) => {
const errors = {};
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;
};
// Async Validation
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const validate = (values, props /* only available when using withFormik */) => {
return sleep(2000).then(() => {
const errors = {};
if (["admin", "null", "god"].includes(values.username)) {
errors.username = "Nice try";
}
// ...
return errors;
});
};
validationSchema
Formik has a special config option / prop for Yup object schemas called validationSchema
which will automatically transform Yup's validation errors into a pretty object whose keys match values
and touched
.
yarn add yup
import React from "react";
import { Formik, Form, Field } from "formik";
import * as Yup from "yup";
const SignupSchema = Yup.object().shape({
firstName: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
lastName: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
email: Yup.string().email("Invalid email").required("Required"),
});
export const ValidationSchemaExample = () => (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
firstName: "",
lastName: "",
email: "",
}}
validationSchema={SignupSchema}
onSubmit={(values) => {
// same shape as initial values
console.log(values);
}}
>
{({ errors, touched }) => (
<Form>
<Field name="firstName" />
{errors.firstName && touched.firstName ? (
<div>{errors.firstName}</div>
) : null}
<Field name="lastName" />
{errors.lastName && touched.lastName ? (
<div>{errors.lastName}</div>
) : null}
<Field name="email" type="email" />
{errors.email && touched.email ? <div>{errors.email}</div> : null}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
Field-level Validation
validate
import React from "react";
import { Formik, Form, Field } from "formik";
function validateEmail(value) {
let error;
if (!value) {
error = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
error = "Invalid email address";
}
return error;
}
function validateUsername(value) {
let error;
if (value === "admin") {
error = "Nice try!";
}
return error;
}
export const FieldLevelValidationExample = () => (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
username: "",
email: "",
}}
onSubmit={(values) => {
// same shape as initial values
console.log(values);
}}
>
{({ errors, touched, isValidating }) => (
<Form>
<Field name="email" validate={validateEmail} />
{errors.email && touched.email && <div>{errors.email}</div>}
<Field name="username" validate={validateUsername} />
{errors.username && touched.username && <div>{errors.username}</div>}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
Manually Triggering Validation
You can manually trigger both form-level and field-level validation with Formik using the validateForm
and validateField
methods respectively.
import React from "react";
import { Formik, Form, Field } from "formik";
function validateEmail(value) {
let error;
if (!value) {
error = "Required";
} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
error = "Invalid email address";
}
return error;
}
function validateUsername(value) {
let error;
if (value === "admin") {
error = "Nice try!";
}
return error;
}
export const FieldLevelValidationExample = () => (
<div>
<h1>Signup</h1>
<Formik
initialValues={{
username: "",
email: "",
}}
onSubmit={(values) => {
// same shape as initial values
console.log(values);
}}
>
{({ errors, touched, validateField, validateForm }) => (
<Form>
<Field name="email" validate={validateEmail} />
{errors.email && touched.email && <div>{errors.email}</div>}
<Field name="username" validate={validateUsername} />
{errors.username && touched.username && <div>{errors.username}</div>}
{/** Trigger field-level validation
imperatively */}
<button type="button" onClick={() => validateField("username")}>
Check Username
</button>
{/** Trigger form-level validation
imperatively */}
<button
type="button"
onClick={() => validateForm().then(() => console.log("blah"))}
>
Validate All
</button>
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
When Does Validation Run
You can control when Formik runs validation by changing the values of <Formik validateOnChange>
and/or <Formik validateOnBlur>
props depending on your needs. By default, Formik will run validation methods as follows:
After "change" events/methods (things that update values
)
handleChange
setFieldValue
setValues
After "blur" events/methods (things that update touched
)
handleBlur
setTouched
setFieldTouched
Whenever submission is attempted
handleSubmit
submitForm
Displaying Error Messages
import React from "react";
import { Formik, Form, Field } from "formik";
import * as Yup from "yup";
const DisplayingErrorMessagesSchema = Yup.object().shape({
username: Yup.string()
.min(2, "Too Short!")
.max(50, "Too Long!")
.required("Required"),
email: Yup.string().email("Invalid email").required("Required"),
});
export const DisplayingErrorMessagesExample = () => (
<div>
<h1>Displaying Error Messages</h1>
<Formik
initialValues={{
username: "",
email: "",
}}
validationSchema={DisplayingErrorMessagesSchema}
onSubmit={(values) => {
// same shape as initial values
console.log(values);
}}
>
{({ errors, touched }) => (
<Form>
<Field name="username" />
{/* If this field has been touched, and it contains an error, display it
*/}
{touched.username && errors.username && <div>{errors.username}</div>}
<Field name="email" />
{/* If this field has been touched, and it contains an error, display
it */}
{touched.email && errors.email && <div>{errors.email}</div>}
<button type="submit">Submit</button>
</Form>
)}
</Formik>
</div>
);
Arrays and Nested Objects
Nested Objects
import React from "react";
import { Formik, Form, Field } from "formik";
export const NestedExample = () => (
<div>
<h1>Social Profiles</h1>
<Formik
initialValues={{
social: {
facebook: "",
twitter: "",
},
}}
onSubmit={(values) => {
// same shape as initial values
console.log(values);
}}
>
<Form>
<Field name="social.facebook" />
<Field name="social.twitter" />
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
);
Arrays
import React from "react";
import { Formik, Form, Field } from "formik";
export const BasicArrayExample = () => (
<div>
<h1>Friends</h1>
<Formik
initialValues={{
friends: ["jared", "ian"],
}}
onSubmit={(values) => {
// same shape as initial values
console.log(values);
}}
>
<Form>
<Field name="friends[0]" />
<Field name="friends[1]" />
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
);
Avoid nesting
import React from "react";
import { Formik, Form, Field } from "formik";
export const NestedExample = () => (
<div>
<h1>Social Profiles</h1>
<Formik
initialValues={{
"owner.fullname": "",
}}
onSubmit={(values) => {
// same shape as initial values
console.log(values);
}}
>
<Form>
<Field name="['owner.fullname']" />
<button type="submit">Submit</button>
</Form>
</Formik>
</div>
);
Form Submission
Submission Phases
To submit a form in Formik, you need to somehow fire off the provided handleSubmit(e)
or submitForm
prop.
Pre-submit
- Touch all fields. initialValues are required and should always be specified
- Set isSubmitting to true
- Increment submitCount + 1
Validation
- Set isValidating to true
- Run all field-level validations, validate, and validationSchema asynchronously and deeply merge results
- Are there any errors?
- Yes: Abort submission. Set isValidating to false, set errors, set isSubmitting to false
- No: Set isValidating to false, proceed to "Submission
Submission
- Proceed with running your submission handler (i.e.onSubmit or handleSubmit)
- you call setSubmitting(false) in your handler to finish the cycle