The Formik library keeps on growing and attempts to keep up with the React community. Formik, since version 2.0, has a built-in set of hooks. In this article, we go through all of them. We also look into how to use Formik with TypeScript.
The basics of Formik with TypeScript
Form handling in React might require quite a bit of code. Therefore, there are quite a few approaches out there to help us with that. Formik deals with three essential tasks:
- Handling the values inside of a form
- Validating the data
- Dealing with the form submission
We’ve dealt with the basics of Formik in the past. If you are interested, check out Writing React forms with Formik. Validating data using Yup. Therefore, in this article, we focus on TypeScript support and the Hooks API.
The Formik library is built with TypeScript. Thanks to that, we always have the newest typings. The first thing to look into is the <Formik /> component. It acts as an initializer for our form. When discussing the TypeScript integrations, we need to look into the initialValues and the onSubmit props.
initialValues & onSubmit
The initialValues prop represents the initial values of our fields. Even if our form starts as empty, we need to initialize all fields with initial values. Otherwise, React complains in the console that we’ve changed an input from uncontrolled to controlled.
The onSubmit prop gets called as soon as we submit our form. Inside of it, we have access to the form values, among other things.
The important part is that the <Formik /> component is generic. We’ve looked into generic React components previously. If you are interested, check out Functional React components with generic props in TypeScript
In the typings, we can see that initialValues and the values inside of the onSubmit have the same type. It means that when we submit the form, we can expect the same set of properties that we’ve provided as initialValues.
Let’s look a bit closer into the typings of the onSubmit prop:
1 |
onSubmit: (values: Values, formikHelpers: FormikHelpers<Values>) => void | Promise<any>; |
The interesting thing is that it can return a promise.
When we start submitting our form, the internal isSubmitting value is set to true. If we return a promise from our onSubmit handler, the isSubmitting value changes to false when the promise resolves or rejects.
useContactFormManagement.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import { useCallback } from 'react'; import submitContactForm from './submitContactForm'; interface ContactFormFields { name: string; email: string; content: string; } function useContactFormManagement() { const handleSubmit = useCallback( (contactFormFields: ContactFormFields) => { return submitContactForm(contactFormFields) .then(() => { console.log('Form submitted!'); }); }, [], ); const initialValues: ContactFormFields = { name: '', email: '', content: '', }; return { handleSubmit, initialValues, }; } export default useContactFormManagement; |
If you would like to get more insight on how to design React hooks such as the one above, check out JavaScript design patterns #3. The Facade pattern and applying it to React Hooks
With our useContactFormManagement hook, we now have everything we need to create a basic contact form.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import React from 'react'; import { Field, Form, Formik } from 'formik'; import useContactFormManagement from './useContactFormManagement'; const ContactForm = () => { const { initialValues, handleSubmit } = useContactFormManagement(); return ( <div> <Formik onSubmit={handleSubmit} initialValues={initialValues} > {({ isSubmitting }) => ( <Form> <Field name="name" type="text" placeholder="Name" /> <Field name="email" type="email" placeholder="Email" /> <Field name="content" type="text" as="textarea" placeholder="Content" /> <button type="submit" disabled={isSubmitting} > Submit </button> </Form> )} </Formik> </div> ); }; |
The useField() hook
Above, we use the <Field /> component in a very basic way. It automatically hooks up inputs to Formik. It has quite a few possibilities of customization, but we can use the useField() hook instead.
With useField() hook, we can create more advanced inputs that look like we want them to. The most straightforward way of using the above hook is to provide it with the name of the input. It returns three properties packed in an array:
1 |
const [field, meta, helpers] = useField('email'); |
We can also provide the useField with the same arguments as the Field component. For a full list, check out the documentation.
The field property is designed in a way that allows us to pass it straight to a <input /> tag. It holds values such as name, value, and onChange.
The meta property holds relevant metadata about the field. An example of such is an error or touched.
The helpers contain helper functions that allow us to interfere with the field directly: setValue, setTouched, and setError.
A full description of the above arguments can be found in the documentation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import React, { FunctionComponent } from 'react'; import { useField } from 'formik'; import styles from './styles.module.scss'; interface Props { name: string; type: string; placeholder: string; } const InputField: FunctionComponent<Props> = (props) => { const [ field, { error, touched }, ] = useField({ name: props.name, type: props.name, }); return ( <div> <input {...field} {...props} /> {error && touched && <div className={styles.error}>{error}</div>} </div> ); }; export default InputField; |
The above example is quite a basic custom Formik component. Please note that the useField() function is also generic so that we can pass it the type of the value. This way, we can improve the types of applications even more.
We can also use the useField() hook to integrate libraries such as Material-UI and Ant Design with Formik. We can also use one of the 3rd party bindings to do the integration for us.
The useFormikContext() hook
The useFormikContext() can be very useful when we need to add some more advanced functionalities to our form. Therefore we can have access to the state of our form and all of its helpers through React Context.
To use the useFormikContext() hook, we need to have the <Formik /> component higher in the component tree, or use the withFormik higher-order component
An example of a use-case is creating conditional fields.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import React from 'react'; import { useFormikContext } from 'formik'; import FormFields from '../FormFields'; import InputField from '../InputField'; const TelephoneField = () => { const { values } = useFormikContext<ContactFormFields>(); return ( <> {values.withTelephone && ( <InputField name="telephone" type="text" placeholder="Telephone number" /> )} </> ); }; export default TelephoneField; |
In the above component, we show the TelephoneField only if the withTelephone checkbox is checked. Since the useFormikContext() is also generic, we can supply it with the interface describing all of the form fields.
The Formik props carry all of the props of the <Formik /> component. The official documentation also has a useful use-case mimicking the Stripe’s 2-factor verification form, so it is worth checking out.
The useFormik() hook
Formik uses the useFormik() hook internally along with React Context to create the <Formik /> component. Even though that’s the case, Formik exports it for advanced use-cases. For example, we can use it if we want to avoid the usage of React Context in our application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import React from 'react'; import useContactFormManagement from './useContactFormManagement'; import { useFormik } from 'formik'; const ContactForm = () => { const { initialValues, handleSubmit } = useContactFormManagement(); const formik = useFormik({ initialValues, onSubmit: handleSubmit, }); return ( <div> <form onSubmit={formik.handleSubmit} > <input id="name" name="name" type="text" onChange={formik.handleChange} value={formik.values.name} /> <input id="email" name="email" type="text" onChange={formik.handleChange} value={formik.values.email} /> <textarea id="content" name="content" onChange={formik.handleChange} value={formik.values.content} /> <button type="submit" disabled={formik.isSubmitting} > Submit </button> </form> </div> ); }; export default ContactForm; |
Summary
In this article, we’ve gone a bit deeper into Formik works and how to utilize that using the Hooks API. We’ve also talked a bit about how to use Formik with TypeScript. By using useField, useFormikContext, and useFormik hooks, we can create quite advanced forms. We can also build field components that we can reuse across our application.
Thank you! Just found your articles and I’ve to say that these are amongst the best ones. I got answers to many of my questions like “how do all these different formik hooks differ from each other”? Great job!
Thank you, I’m glad you like it 🙂
For a more complete use case, please also provide submitContactForm.tsx
For a more complete use case, please also provide submitContactForm.tsx
Great article!
I was just wondering: should I use the Formik-Component or the useFormik-Hook for normal forms? I don’t quite understand the advantages of using the Component!
Thank you Marcin for posting this. The official docs don’t go deep enough, you saved me a lot of headache.