- Published on
If you are working on any sized React app, you will quickly find a need for working with forms.
Simple forms are easy to manage with things like useState()
. But once you have forms with multiple fields (of different types), validation rules, on submit logic and so on, then it becomes hard to manage.
Formik is one solution to these problems, and this is a guide on what you need to know to start using it.
Also check out my guide on React Hook Form - which is my recommended library for handling complex forms nowadays. Formik is a little more complex to use, but still very popular with many React applications built around Formik.
Ok, onto the guide!
Table of Contents
- Installing formik and basic setup/usage
- Using Formik fields
- Validation
- Notes on validation
- Use of Formik with class based components/HoC
- Submit function
- Working with arrays of fields or nested objects
- Array functions in FieldArray
- How to render (children/inner component) in FieldArray
- Validation in FieldArray
- Submitting
- useField hook
- FastField
- Using Formik with other libraries
- FormikBag
- Checkboxes
- Radio inputs
- Dependant Fields (derived values)
- ErrorMessage
Installing formik and basic setup/usage
yarn add formik
For local demo you can play with Formik at https://codesandbox.io/s/zKrK5YLDZ
- wrap your forms in
<Formik>
. - In that
<Formik>
component, pass ininitialValues
. You will probably also want to add a function foronSubmit
, and maybe some validation rules. - Inside
<Formik>
, pass in a function as the child, which gives you props likevalues
,handleChange
etc.
Here is a set up (note: there are easier ways to set this up - explained in next section).
import React from 'react';
import { Formik } from 'formik';
const BasicFormik = () => (
<div>
<Formik
initialValues={{ name: '' }}
validate={values => {
const validationErrors = {}
if(!values.name.trim()) {
validationErrors.name = 'You must add a name'
}
if(values.name.length > 30) {
validationErrors.name = 'Your name must be 30 or fewer characters'
}
return validationErrors;
}}
onSubmit={(values, { setSubmitting }) => {
fetch(
'/api/save-name',
{
method: 'POST',
body: JSON.stringify({name: values.name})
})
.then(() => alert('saved'))
}}
>
{({
values,
errors,
touched,
handleChange,
handleSubmit,
}) => (
<form onSubmit={handleSubmit}>
<input
type="name"
name="name"
onChange={handleChange}
value={values.name}
/>
{errors.name && touched.name && <p>Validation error: {errors.name}</p>}
<button type="submit">
Save your name
</button>
</form>
)}
</Formik>
</div>
);
The handleChange
function will look at event.target.name
(from the <input name="email">
), and will update values.name
based on that.
There are other helpful functions you can add like handleBlur
Using Formik fields
In the previous example I manually added the event listeners, set the value of the input, and manually displayed error messages.
You can also use Formik's <Field>
and <ErrorMessage>
to be a bit cleaner.
import React from 'react';
import { ErrorMessage, Field, Formik, Form, } from 'formik';
const WithHelperComponents = () => (
<div>
<Formik
initialValues={{ name: '' }}
validate={values => {
const validationErrors = {}
if(!values.name.trim()) {
validationErrors.name = 'You must add a name'
}
if(values.name.length > 30) {
validationErrors.name = 'Your name must be 30 or fewer characters'
}
return validationErrors;
}}
onSubmit={(values, { setSubmitting }) => {
fetch(
'/api/save-name',
{
method: 'POST',
body: JSON.stringify({name: values.name})
})
.then(() => alert('saved'))
}}
>
{() => (
<Form>
<Field
type="name"
name="name"
/>
<ErrorMessage name="email" component="div" />
<button type="submit">
Save your name
</button>
</form>
)}
</Formik>
</div>
);
This time I used the <Field>
and <ErrorMessage>
, which uses the name
prop to set everything up (handleChange
, handleBlur
, etc on the input, and checking for validation error message)
Validation
In the previous examples I set up my own custom validation function, that returned an object like {name: "You must enter a name"}
if there were validation errors.
The usual way of validation with Formik is to use a library such as Yup
Here is a demo:
import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as Yup from 'yup';
const nameSchema = Yup.object().shape({
name: Yup.string()
.min(2, 'Name must be at least 2 characters')
.max(30, 'Name must be at most 30 characters')
.required('Name is required'),
});
function ComponentWithValidationSchema() {
return <Formik initialValues={{ name: ''}} validationSchema={nameSchema}>
<Form>
<Field name="name" />
<ErrorMessage name="name" />
<button type="submit">Save name</button>
</Form>
</Formik>
}
Notes on validation
- If you have your own custom validation function, return
undefined
if there is no error. Do not returnnull
- Validation on
<Field>
or<FastField>
will run afteronChange
(handleChange
,setFieldValue
,setFieldValues
) andonBlur
(handleBlur
,setTouched
,setFieldTouched
)- you can change this - the main (root)
<Formik>
component accepts props likevalidationOnChange
,validateOnBlur
so you can disable if needed
- you can change this - the main (root)
- Validation also runs on form submission (
handleSubmit
,submitForm
) (any validation messages are merged with the top level validation error messages) - Validation on
<Field>
or<FastField>
components runs only if the component is mounted. So if you have some fields which are not mounted (hidden on some multi stage wizard for example), they do not get validated - If you need to know if the form is running validation, use the
isValidating
prop (passed in to the child of<Formik>
. - You can also manually run validation, by using the
validateField()
prop that is passed in to the child of<Formik>
function validateName(name) {
if(!name) return 'Name is required';
if(name.length < 2) return 'Name must be at least 2 characters';
}
function ManualValidation() {
return (
<div>
<Formik
initialValues={{ name: '', }}
onSubmit={console.log}
>
{({ errors, touched, validateField, validateForm }) => (
<Form>
<Field name="name" validate={validateName} />
{errors.name && touched.name && <div className="alert">{errors.name}</div>}
<button type="button" onClick={() => validateField('name')}>
Validate the name field
</button>
<button
type="button"
onClick={async () => {
await validateForm()
}}
>
Validate entire form
</button>
<button type="submit">Save name</button>
</Form>
)}
</Formik>
</div>
)
}
Use of Formik with class based components/HoC
All examples on my guide deal with functional components, as that is the more modern way that the majority of new apps are using. But Formik of course works with class based components.
You can also use withFormik
to pass in props/form handlers into a component (class based or functional).
Example:
import React from 'react';
import { withFormik } from 'formik';
const YourFormComponent = props => {
const {
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
} = props;
return (
<form onSubmit={handleSubmit}>
<input
name="name"
onBlur={handleBlur}
onChange={handleChange}
type="text"
value={values.name}
/>
{errors.name && touched.name && <div className="alert">{errors.name}</div>}
<button type="submit">Save name</button>
</form>
);
};
const YourFormWithFormikComponent = withFormik({
mapPropsToValues: () => ({ name: '' }),
validate: values => {
const validationErrors = {}
if(!values.name.trim()) {
validationErrors.name = 'You must add a name'
}
if(values.name.length > 30) {
validationErrors.name = 'Your name must be 30 or fewer characters'
}
return validationErrors;
},
handleSubmit: (values) =>
fetch(
'/api/save-name',
{
method: 'POST',
body: JSON.stringify({name: values.name})
})
.then(() => {
alert('saved');
})
,
})(YourFormComponent);
Submit function
You can pass in a handleSubmit
function.
This has a couple of arguments - the first is the values
from the user input for your form.
The second is the FormikBag
, which includes things such as:
- setValues - you can use this to set new form values
- setErrors - if you need to set some errors for specific fields
- setSubmitting - use this to set submitting as false. Note: if your
handleSubmit
function is async, thensetSubmitting(false)
is automatically called after the promise resolves
and more - see section on FormikBag
Working with arrays of fields or nested objects
The examples so far have been very trivial, and would be easy to implement without Formik. But I think one area that Formik is very useful is when your fields are more complex, such asi if you have nested fields or arrays of fields (which may change is length of the array).
You can set up nested objects within initialValues
exactly how you would with any normal plain old JS object:
<Formik
initialValues={{
profile: {
firstName: '',
lastName: '',
},
votes: 0,
}}
>...</Formik>
You can then reference them in <Field>
, <FastField>
and <ErrorMessage>
with dot notation:
<Formik
initialValues={{
profile: {
firstName: '',
lastName: '',
},
votes: 0,
}}
>
<Form>
<Field name="profile.firstName" />
<ErrorMessage name="profile.firstName" />
</Form>
</Formik>
If you have an array of values, it is quite simlar. Instead of referencing them with dot notation, you write it out like name="names[0]"
<Formik
initialValues={{
names: ['bart', 'lisa', 'marge'],
}}
>
<Form>
<Field name="names[0]" />
<Field name="names[1]" />
<Field name="names[2]" />
</Form>
</Formik>
If you need to manipulate the array, then you can use the <FieldArray>
component. You pass in a render
prop, which can be a function that takes an argument of helper functions (such as remove(atIndex)
to remove an item, or insert(atIndex, newValue)
)
import React from 'react';
import { Formik, Form, Field, FieldArray } from 'formik';
export const NamesList = () => (
<Formik
initialValues={{ names: ['bart', 'lisa', 'marge'] }}
onSubmit={console.log}
render={({ values }) => (
<Form>
<FieldArray
name="names"
render={arrayHelpers => (
<div>
{values.names && values.names.length > 0 && (
values.names.map((friend, index) => (
<div key={index}>
<Field name={`names.${index}`} />
<button
type="button"
onClick={() => arrayHelpers.remove(index)}
>
remove
</button>
</div>
))
)}
<button type="button" onClick={() => arrayHelpers.push('')}>
Add new name
</button>
<button type="submit">Submit</button>
</div>
)}
/>
</Form>
)}
/>
);
You can pass in a couple of props to <FieldArray>
: name
(the field name, from your values
object), and an optional boolean validateOnChange
. Set it to false to not run validation after values in the array change.
If your values
is an array of objects, you can reference them like this:
<Formik
initialValues={{people: [{name: 'Bart', age: 10}, {name: 'marge', age: 40}]}}>
<Form>
<FieldArray
name="people"
render={arrayHelpers => (
<div>
<p>There are {values.people.length} people you can edit:</p>
{values.people.map((friend, idx) => (
<div key={index}>
<p>Person at index: {idx}</p>
<Field name={`people[${index}].name`} />
<Field name={`people[${index}].age`} />
<button type="button" onClick={() => arrayHelpers.remove(idx)}>
Remove this person
</button>
</div>
))}
<button
type="button"
onClick={() => arrayHelpers.push({ name: '', age: '' })}
>
Add new person
</button>
</div>
)}
/>
</Form>
</Formik>
Array functions in FieldArray
You have access to a bunch of typical array functions
insert: (insertAtIndex: number, value: any) => void
inset a new value at a specific indexmove: (fromIndex: number, toIndex: number) => void
move the value from an old index to a new indexpop<T>(): T | undefined
Pop an item off the end of the array (removing it from the array), and return itpush: (newObject: any) => void
push a new value to the end of the arrayremove<T>(atIndex: number): T | undefined
Remove an item at index (and return it)replace: (atIndex: number, updatedValue: any) => void
Replace a value at indexswap: (indexA: number, indexB: number) => void
swap the values of 2 items at certain indexesunshift: (newValue: any) => number
add a new item to the array at the start (and return the new length of the array)
How to render (children/inner component) in FieldArray
There are 3 ways to render your content inside FieldArray. With component=
, render=
, or with children
function YourCustomComponent({insert, pop}) { return <Form>todo</Form>; }
<FieldArray
name="friends"
component={YourCustomComponent}
/>
<FieldArray
name="people"
render={({ move, swap, push, insert, unshift, pop }) => (
<Form>
// ...
</Form>
)}
/>
<FieldArray name="friends">
{({ move, push, insert, pop, form }) =>
(
<Form>
{/*... use these however you want */}
</Form>
)
}}
</FieldArray>
Validation in FieldArray
Validation for nested objects is a bit more tricky (with yup).
const validationSchema = Yup.object().shape({
people: Yup.array()
.of(
Yup.object().shape({
name: Yup.string().min(2, 'too short').required('Required'),
age: Yup.string().min(0, 'must have been born').required('Required'),
})
)
.required('Must have some people')
.min(3, 'Minimum of 3 people'),
});
But there is an issue with that: the of()
part of validation will run (and will return a nested object with those errors). But if they pass ok (no errors in the of()
block, then the other rules (the required()
and min()
) will run. These will return a string.
So if you want to show error messages for entire form (must have min of 3 people), you have to check the error object is a string.
// won't work: it can be an object if one of the names is under 2 characters
const thisWontWork = !!errors.people && <div>Error (you have to update # of people): {errors.people}</div>;
const thisWillWork = typeof errors.people === 'string' && <div>Error (you have to update # of people): {errors.people}</div>;
Submitting
There are a few ways to handle a submit, but the typical way is to pass in a handleSubmit={(values) => console.log(values)}
sort of function to the parent <Formik>
.
When submit runs:
- Formik will touch all fields
- why? so that if you have logic that checks/shows errors only after a field was touched, it will now be visible to users. Often there is logic like
errors.name && touched.name && <div>Error with name field: {errors.name}</div>
- why? so that if you have logic that checks/shows errors only after a field was touched, it will now be visible to users. Often there is logic like
- set
isSubmitting = true
- increment
submitCount
by 1 - Runs validation:
- set
isValidating = true
- Runs the validate function on fields
- If any errors: stop validation. Sets
isValidating
andisSubmitting
to false, yourerrors
will be set - If no errors:
isValidating
to false, and continues
- set
- Then it runs your
onSubmit
orhandleSubmit
function - Once done, if your submit handler was async it will set
setSubmitting(false)
. You can also call this yourself in your submit handler.
You should disable submitting when isSubmitting
is true. Either in your submit handler, or via disabled
on form / submit buttons.
useField hook
FastField
Using Formik with other libraries
FormikBag
On a lot of the handler functions (such as onReset
, onSubmit
, etc) you often get 2 arguments: the first is an object of your field values
, and the second is a FormikBag
one.
This object has the following in it:
-
props
(props passed to the wrapped component) -
resetForm
-
setErrors
-
setFieldError
-
setFieldTouched
-
setFieldValue
-
setStatus
-
setSubmitting
-
setTouched
-
setValues
-
For validation there is extremely good support for Yup
-
UIs such as MaterialUI and Ant have great support too.
Checkboxes
Checkboxes are easy to use in Formik. Use their <Field>
component like this:
<Field type="checkbox" name="published" value="true" />
If you have multiple checkboxes with the same name, they get combined into an array
<Field type="checkbox" name="animal" value="cat" />
<Field type="checkbox" name="animal" value="bird" />
Radio inputs
Very similar to checkboxes.
<Field type="radio" name="tshirtSize" value="small" />
<Field type="radio" name="tshirtSize" value="medium" />
<Field type="radio" name="tshirtSize" value="large" />
Dependant Fields (derived values)
I find dependant fields the one thing that is a bit awkward at times to work with, as you can easily get to infinite re-render states.
On paper its quite easy - use useEffect()
with the values you want to use as dependancies, and call setFieldValue
in there.
Their docs have a good and easy to understand demo here
ErrorMessage
You can manually access the errors
object, which can contain the error messages. But you often want to only show error messages once the user has interacted with a field (when touched[fieldName] === true
).
There is a nice helper component called ErrorMessage
that handles this (as long as the error messages are strings).
This will show error message from the 'name' field:
<ErrorMessage name="name" />
If you want to customize the error message, pass in a function as the children:
<ErrorMessage name="name">{errorMessage => <div className="alert">{errorMessage}</div>}</ErrorMessage>
Or you can pass in a component:
<ErrorMessage name="name" component="li" />
<ErrorMessage name="name" component={SomeCustomComponent} />