Let's face it, forms are really verbose in React. To make matters worse, most form helpers do wayyyy too much magic and often have a significant performance cost associated with them. Formik is a small library that helps you with the 3 most annoying parts:
- Getting values in and out of form state
- Validation and error messages
- Handling form submission
By colocating all of the above in one place, Formik will keep things organized--making testing, refactoring, and reasoning about your forms a breeze.
I (@jaredpalmer) wrote Formik while building a large internal administrative dashboard with @eonwhite. With around ~30 unique forms, it quickly became obvious that we could benefit by standardizing not just our input components but also the way in which data flowed through our forms.
By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.
- According to our prophet Dan Abramov, form state is inherently ephemeral and local, so tracking it in Redux (or any kind of Flux library) is unnecessary
- Redux-Form calls your entire top-level Redux reducer multiple times ON EVERY SINGLE KEYSTROKE. This is fine for small apps, but as your Redux app grows, input latency will continue to increase if you use Redux-Form.
- Redux-Form is 22.5 kB minified gzipped (Formik is 7.8 kB)
My goal with Formik was to create a scalable, performant, form helper with a minimal API that does the really really annoying stuff, and leaves the rest up to you.
"I can't believe people ever put forms in redux, or did anything else other than this."
--James Long, Creator of Prettier and Actual Budget
"Formik. All day. All long."
--Ken Wheeler, Director of Open Source at Formidable Labs
"Been using @jaredpalmer's Formik lately at work β this is my all-time favorite way to handle forms"
--Brent Jackson, Creator of Rebass, styled-system, Compositor and many more
"Formik removes most of the moving parts involved in forms allowing me to move faster with more confidence."
--Kye Hohenberger, Creator of Emotion
Formik started by expanding on this little higher order component by Brent Jackson, some naming conventions from Redux-Form, and (most recently) the render props approach popularized by React-Motion and React-Router 4. Whether you have used any of the above or not, Formik only takes a few minutes to get started with.
Add Formik to your project.
npm i formik --save
You can also try before you buy with this demo of Formik on CodeSandbox.io
- Basics
- Sync Validation
- Building your own input primitives
- Working with 3rd-party inputs #1: react-select
- Working with 3rd-party inputs #2: Draft.js
- Accessing React lifecycle functions
- An Introduction to Formik by Jared Palmer @ Spotify NYC. August 15th, 2017.
- Better React Forms with Formik
- The Joy of Forms with React and Formik
- Painless React Forms with Formik
Formik keeps track of your form's state and then exposes it plus a few reusable
methods and event handlers (handleChange
, handleBlur
, and handleSubmit
) to
your form via props
. handleChange
and handleBlur
work exactly as
expected--they use a name
or id
attribute to figure out which field to
update.
There are two ways to use Formik:
withFormik()
: A Higher-order Component (HoC) that accepts a configuration object<Formik />
: A React component with arender
prop
Both do exactly the same thing and share the same internal implementation. They just differ in their respective style....
// Higher Order Component
import React from 'react';
import { withFormik } from 'formik';
// Our inner form component which receives our form's state and updater methods as props
const InnerForm = ({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<input
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{touched.password && errors.password && <div>{errors.password}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
);
// Wrap our form with the using withFormik HoC
const MyForm = withFormik({
// Transform outer props into form values
mapPropsToValues: props => ({ email: '', password: '' }),
// Add a custom validation function (this can be async too!)
validate: (values, props) => {
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;
},
// Submission handler
handleSubmit: (
values,
{
props,
setSubmitting,
setErrors /* setValues, setStatus, and other goodies */,
}
) => {
LoginToMyApp(values).then(
user => {
setSubmitting(false);
// do whatevs...
// props.updateUser(user)
},
errors => {
setSubmitting(false);
// Maybe even transform your API's errors into the same shape as Formik's!
setErrors(transformMyApiErrors(errors));
}
);
},
})(InnerForm);
// Use <MyForm /> anywhere
const Basic = () => (
<div>
<h1>My Form</h1>
<p>This can be anywhere in your application</p>
<MyForm />
</div>
);
export default Basic;
// Render Prop
import React from 'react';
import { Formik } from 'formik';
const Basic = () => (
<div>
<h1>My Form</h1>
<p>This can be anywhere in your application</p>
{/*
The benefit of the render prop approach is that you have full access to React's
state, props, and composition model. Thus there is no need to map outer props
to values...you can just set the initial values, and if they depend on props / state
then--boom--you can directly access to props / state.
The render prop accepts your inner form component, which you can define separately or inline
totally up to you:
- `<Formik render={props => <form>...</form>}>`
- `<Formik component={InnerForm}>`
- `<Formik>{props => <form>...</form>}</Formik>` (identical to as render, just written differently)
*/}
<Formik
initialValues={{
email: '',
password: '',
}}
validate={values => {
// same as above, but feel free to move this into a class method n
10000
ow.
let 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;
}}
onSubmit={(
values,
{ setSubmitting, setErrors /* setValues and other goodies */ }
) => {
LoginToMyApp(values).then(
user => {
setSubmitting(false);
// do whatevs...
// props.updateUser(user)
},
errors => {
setSubmitting(false);
// Maybe transform your API's errors into the same shape as Formik's
setErrors(transformMyApiErrors(errors));
}
);
}}
render={({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<input
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
/>
{touched.password && errors.password && <div>{errors.password}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
/>
</div>
);
export default Basic;
As you can see above, validation is left up to you. Feel free to write your own
validators or use a 3rd party library. Personally, I use
Yup for object schema validation. It has an
API that's pretty similar Joi /
React PropTypes but is small enough
for the browser and fast enough for runtime usage. Because I β€οΈ Yup sooo
much, 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
. Anyways, you
can install Yup from npm...
npm install yup --save
- Guides
- API
<Formik />
- Formik render methods
- Formik props
dirty: boolean
errors: { [field: string]: string }
handleBlur: (e: any) => void
handleChange: (e: React.ChangeEvent<any>) => void
handleReset: () => void
handleSubmit: (e: React.FormEvent<HTMLFormEvent>) => void
isSubmitting: boolean
isValid: boolean
resetForm: (nextValues?: Values) => void
setErrors: (fields: { [field: string]: string }) => void
setFieldError: (field: string, errorMsg: string) => void
setFieldTouched: (field: string, isTouched: boolean, shouldValidate?: boolean) => void
submitForm: () => void
submitCount: number
setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void
setStatus: (status?: any) => void
setSubmitting: (isSubmitting: boolean) => void
setTouched: (fields: { [field: string]: boolean }) => void
setValues: (fields: { [field: string]: any }) => void
status?: any
touched: { [field: string]: boolean }
values: { [field: string]: any }
validateForm: (values?: any) => void
component
render: (props: FormikProps<Values>) => ReactNode
children: func
enableReinitialize?: boolean
isInitialValid?: boolean
initialValues?: Values
onReset?: (values: Values, formikBag: FormikBag) => void
onSubmit: (values: Values, formikBag: FormikBag) => void
validate?: (values: Values) => FormikErrors<Values> | Promise<any>
validateOnBlur?: boolean
validateOnChange?: boolean
validationSchema?: Schema | (() => Schema)
<Field />
<FieldArray />
<Form />
withFormik(options)
options
displayName?: string
enableReinitialize?: boolean
handleSubmit: (values: Values, formikBag: FormikBag) => void
isInitialValid?: boolean | (props: Props) => boolean
mapPropsToValues?: (props: Props) => Values
validate?: (values: Values, props: Props) => FormikErrors<Values> | Promise<any>
validateOnBlur?: boolean
validateOnChange?: boolean
validationSchema?: Schema | ((props: Props) => Schema)
- Injected props and methods
- Organizations and projects using Formik
- Authors
- Contributors
Imagine you want to build a form that lets you edit user data. However, your user API has nested objects like so.
{
id: string,
email: string,
social: {
facebook: string,
twitter: string,
// ...
}
}
When we are done we want our dialog to accept just a user
, updateUser
, and
onClose
props.
// User.js
import React from 'react';
import Dialog from 'MySuperDialog';
import EditUserForm from './EditUserForm';
import { Formik } from 'formik';
const EditUserDialog = ({ user, updateUser, onClose }) => {
return (
<Dialog onClose={onClose}>
<h1>Edit User</h1>
<Formik
initialValues={user /** { email, social } */}
onSubmit={(values, actions) => {
CallMyApi(user.id, values).then(
updatedUser => {
actions.setSubmitting(false);
updateUser(updatedUser), onClose();
},
error => {
actions.setSubmitting(false);
actions.setErrors(transformMyAPIErrorToAnObject(error));
}
);
}}
render={({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
/>
{errors.email && touched.email && <div>{errors.email}</div>}
<input
type="text"
name="social.facebook"
onChange={handleChange}
onBlur={handleBlur}
value={values.social.facebook}
/>
{errors.social &&
errors.social.facebook &&
touched.facebook && <div>{errors.social.facebook}</div>}
<input
type="text"
name="social.twitter"
onChange={handleChange}
onBlur={handleBlur}
value={values.social.twitter}
/>
{errors.social &&
errors.social.twitter &&
touched.twitter && <div>{errors.social.twitter}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</form>
)}
/>
</Dialog>
);
};
To make writing forms less verbose. Formik comes with a few helpers to save you key strokes.
<Field>
<Form />
This is the exact same form as before, but written with <Form />
and
<Field />
:
// EditUserDialog.js
import React from 'react';
import Dialog from 'MySuperDialog';
import EditUserForm from './EditUserForm';
import { Formik, Field, Form } from 'formik';
const EditUserDialog = ({ user, updateUser, onClose }) => {
return (
<Dialog onClose={onClose}>
<h1>Edit User</h1>
<Formik
initialValues={user /** { email, social } */}
onSubmit={(values, actions) => {
CallMyApi(user.id, values).then(
updatedUser => {
actions.setSubmitting(false);
updateUser(updatedUser), onClose();
},
error => {
actions.setSubmitting(false);
actions.setErrors(transformMyAPIErrorToAnObject(error));
}
);
}}
render={({ errors, touched, isSubmitting }) => (
<Form>
<Field type="email" name="email" />
{errors.email && touched.social.email && <div>{errors.email}</div>}
<Field type="text" name="social.facebook" />
{errors.social.facebook &&
touched.social.facebook && <div>{errors.social.facebook}</div>}
<Field type="text" name="social.twitter" />
{errors.social.twitter &&
touched.social.twitter && <div>{errors.social.twitter}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
/>
</Dialog>
);
};
Formik is 100% compatible with React Native and React Native Web. However, because of differences between ReactDOM's and React Native's handling of forms and text input, there are two differences to be aware of. This section will walk you through them and what I consider to be best practices.
Before going any further, here's a super minimal gist of how to use Formik with React Native that demonstrates the key differences:
// Formik x React Native example
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { withFormik } from 'formik';
const enhancer = withFormik({
/*...*/
});
const MyReactNativeForm = props => (
<View>
<TextInput
onChangeText={text => props.setFieldValue('email', text)}
value={props.values.email}
/>
<Button onPress={props.handleSubmit} title="Submit" />
</View>
);
export default enhancer(MyReactNativeForm);
As you can see above, the notable differences between using Formik with React DOM and React Native are:
- Formik's
props.handleSubmit
is passed to a<Button />
instead of HTML<form />
component (since there is no<form />
element in React Native). <TextInput />
uses Formik'sprops.setFieldValue
instead ofprops.handleChange
. To understand why, see the discussion below.
'cuz handleChange
will not work in React Native...
import { Button, TextInput, View } from 'react-native';
import { Formik } from 'formik';
const MyReactNativeForm = props => (
<View>
<Formik
onSubmit={(values, actions) => {
setTimeout(() => {
console.log(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
render={formikProps => (
<View>
<TextInput
name="email"
onChangeText={formikProps.handleChange} // this WILL NOT WORK IN RN
value={formikProps.values.email}
/>
<Button onPress={formikProps.handleSubmit} />
</View>
)}
/>
</View>
);
The reason is that Formik's handleChange
function expects its first argument
to be synthetic DOM event where the event.target
is the DOM input element and
event.target.id
or event.target.name
matches the field to be updated.
Without this, handleChange
will do nothing.
In React Native, neither
<TextInput />
's
onChange
nor
onChangeText
callbacks pass such an event or one like it to its callback. Instead, they do
the following (emphasis added):
onChange?: function
Callback that is called when the text input's text changes.
onChangeText?: function
Callback that is called when the text input's text changes. Changed text is passed as an argument to the callback handler.
However, Formik works just fine if you use props.setFieldValue
!
Philosophically, just treat React Native's <TextInput />
the same way you would
any other 3rd party custom input element.
In conclusion, the following WILL work in React Native:
// ...
// this works.
export const MyReactNativeForm = props => (
<View>
<TextInput
onChangeText={text => props.setFieldValue('email', text)}
value={props.values.email}
/>
<Button onPress={props.handleSubmit} />
</View>
);
// ...
If for any reason you wish to avoid creating new functions on e
10000
ach render, I
suggest treating React Native's <TextInput />
as if it were another 3rd party
custom input element:
- Write your own class wrapper around the custom input element
- Pass the custom component
props.setFieldValue
instead ofprops.handleChange
- Use a custom change handler callback that calls whatever you passed-in
setFieldValue
as (in this case we'll match the React Native TextInput API and call itthis.props.onChangeText
for parity).
// FormikReactNativeTextInput.js
import * as React from 'react';
import { TextInput } from 'react-native';
export default class FormikReactNativeTextInput extends React.Component {
handleChange = (value: string) => {
// remember that onChangeText will be Formik's setFieldValue
this.props.onChangeText(this.props.name, value);
};
render() {
// we want to pass through all the props except for onChangeText
const { onChangeText, ...otherProps } = this.props;
return (
<TextInput
onChangeText={this.handleChange}
{...otherProps} // IRL, you should be more explicit when using TS
/>
);
}
}
Then you could just use this custom input as follows:
// MyReactNativeForm.js
import { View, Button } from 'react-native';
import TextInput from './FormikReactNativeTextInput';
import { Formik } from 'formik';
const MyReactNativeForm = props => (
<View>
<Formik
onSubmit={(values, actions) => {
setTimeout(() => {
console.log(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
render={props => (
<View>
<TextInput
name="email"
onChangeText={props.setFieldValue}
value={props.values.email}
/>
<Button title="submit" onPress={props.handleSubmit} />
</View>
)}
/>
</View>
);
export default MyReactNativeForm;
The Formik source code is written in TypeScript, so you can rest assured that
types will always be up to date. As a mental model, Formik's types are very
similar to React Router 4's <Route>
.
import * as React from 'react';
import { Formik, FormikProps, Form, Field, FieldProps } from 'formik';
interface MyFormValues {
firstName: string;
}
export const MyApp: React.SFC<{} /* whatever */> = () => {
return (
<div>
<h1>My Example</h1>
<Formik
initialValues={{ firstName: '' }}
onSubmit={(values: MyFormValues) => alert(JSON.stringify(values))}
render={(formikBag: FormikProps<MyFormValues>) => (
<Form>
<Field
name="firstName"
render={({ field, form }: FieldProps<MyFormValues>) => (
<div>
<input type="text" {...field} placeholder="First Name" />
{form.touched.firstName &&
form.errors.firstName &&
form.errors.firstName}
</div>
)}
/>
</Form>
)}
/>
</div>
);
};
import React from 'react';
import * as Yup from 'yup';
import { withFormik, FormikProps, FormikErrors, Form, Field } from 'formik';
// Shape of form values
interface FormValues {
email: string;
password: string;
}
interface OtherProps {
message: string;
}
// You may see / user InjectedFormikProps<OtherProps, FormValues> instead of what comes below. They are the same--InjectedFormikProps was artifact of when Formik only exported an HOC. It is also less flexible as it MUST wrap all props (it passes them through).
const InnerForm = (props: OtherProps & FormikProps<FormValues>) => {
const { touched, errors, isSubmitting, message } = props;
return (
<Form>
<h1>{message}</h1>
<Field type="email" name="email" />
{touched.email && errors.email && <div>{errors.email}</div>}
<Field type="password" name="password" />
{touched.password && errors.password && <div>{errors.password}</div>}
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
);
};
// The type of props MyForm receives
interface MyFormProps {
initialEmail?: string;
message: string; // if this passed all the way through you might do this or make a union type
}
// Wrap our form with the using withFormik HoC
const MyForm = withFormik<MyFormProps, FormValues>({
// Transform outer props into form values
mapPropsToValues: props => {
return {
email: props.initialEmail || '',
password: '',
};
},
// Add a custom validation function (this can be async too!)
validate: (values: FormValues) => {
let errors: FormikErrors = {};
if (!values.email) {
errors.email = 'Required';
} else if (!isValidEmail(values.email)) {
errors.email = 'Invalid email address';
}
return errors;
},
handleSubmit: values => {
// do submitting things
},
})(InnerForm);
// Use <MyForm /> anywhere
const Basic = () => (
<div>
<h1>My App</h1>
<p>This can be anywhere in your application</p>
<MyForm message="Sign up" />
</div>
);
export default Basic;
<Formik>
is a component that helps you with building forms. It uses a render
props pattern made popular by libraries like React Motion and React Router.
import React from 'react';
import { Formik } from 'formik';
const BasicExample = () => (
<div>
<h1>My Form</h1>
<Formik
initialValues={{ name: 'jared' }}
onSubmit={(values, actions) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
actions.setSubmitting(false);
}, 1000);
}}
render={props => (
<form onSubmit={props.handleSubmit}>
<input
type="text"
onChange={props.handleChange}
onBlur={props.handleBlur}
value={props.values.name}
name="name"
/>
{props.errors.name && <div id="feedback">{props.errors.name}</div>}
<button type="submit">Submit</button>
</form>
)}
/>
</div>
);
There are three ways to render things with <Formik />
<Formik component>
<Formik render>
<Formik children>
All three render methods will be passed the same props:
Returns true
if values are not deeply equal from initial values, false
otherwise.
dirty
is a readonly computed property and should not be mutated directly.
Form validation errors. Should match the shape of your form's values
defined
in initialValues
. If you are using validationSchema
(which you should be),
keys and shape will match your schema exactly. Internally, Formik transforms raw
Yup validation errors
on your behalf. If you are using validate
, then that function will determine
the errors
objects shape.
onBlur
event handler. Useful for when you need to track whether an input has
been touched
or not. This should be passed to <input ... />
DOM-only. Use setFieldTouched
in React Native.
General input change event handler. This will update the values[key]
where
key
is the event-emitting input's name
attribute. If the name
attribute is
not present, handleChange
will look for an input's id
attribute. Note:
"input" here means all HTML inputs.
DOM-only. Use setFieldValue
in React Native.
Reset handler. Will reset the form to its initial state. This should be passed
to <button >
Submit handler. This should be passed to <form >
Submitting state. Either true
or false
. Formik will set this to true
on
your behalf before calling handleSubmit
to reduce boilerplate.
Returns true
if the there are no errors
, or the result of
isInitialValid
the form if is in "pristine" condition (i.e. not dirty
)).
Imperatively reset the form. This will clear errors
and touched
, set
isSubmitting
to false
and rerun mapPropsToValues
with the current
WrappedComponent
's props
or what's passed as an argument. The latter is
useful for calling resetForm
within componentWillReceiveProps
.
Set errors
imperatively.
Set the error message of a field imperatively. field
should match the key of
errors
you wish to update. Useful for creating custom input error handlers.
Set the touched state of a field imperatively. field
should match the key of
touched
you wish to update. Useful for creating custom input blur handlers. Calling this method will trigger validation to run if validateOnBlur
is set to true
(which it is by default). You can also explicitly prevent/skip validation by passing a third argument as false
.
Trigger a form submission.
Number of times user tried to submit the form. Increases when handleSubmit
is called, resets after calling
handleReset
. submitCount
is readonly computed property and should not be mutated directly.
Set the value of a field imperatively. field
should match the key of
values
you wish to update. Useful for creating custom input change handlers. Calling this will trigger validation to run if validateOnChange
is set to true
(which it is by default). You can also explicitly prevent/skip validation by passing a third argument as false
.
Set a top-level status
to anything you want imperatively. Useful for
controlling arbitrary top-level state related to your form. For example, you can
use it to pass API responses back into your component in handleSubmit
.