Monday, 30 March, 2020 UTC


Summary

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:
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
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.
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:
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.
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.
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.
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.
The post Building forms using Formik with the React Hooks API and TypeScript appeared first on Marcin Wanago Blog - JavaScript, both frontend and backend.