Getting Forms Right in React

Thank you to Alexander Karan for this article! You can read more of his work on Medium. Forms used to be the bane of my life. They needed to include validation, handle errors and give the user a great experience. I get frustrated when I find filling in…


This content originally appeared on JavaScript January and was authored by Emily Freeman

Thank you to Alexander Karan for this article! You can read more of his work on Medium.

Forms used to be the bane of my life. They needed to include validation, handle errors and give the user a great experience. I get frustrated when I find filling in data in an app difficult. I don't want this for my users. I want to make sure I create the best and most comfortable experience for them. Pulling off amazing forms that meet these standards used to mean that a lot of boilerplate code was needed until I discovered Formik. Using Formik, we can make sure all data collected by a form is correct, and the user experience is top-notch. So let's dive into it and create our first Formik form.

We are going to cover integrating Formik into essential components, validation of data using Yup and creating a custom component to collect data in Formik.

First, download the starter project from Github:

Starter project

To set up the project run:

npm install

Then CD into the project folder and type the command:

npm start

Let's check out the starter project I created for you. There are three components in the components folder CustomButton, CustomLabel and CustomInput. All three are the same as their primary react counterparts button, label and input. I have just added some custom styling via their CSS files.

In the App file, you will find a basic form that captures a user's name and email. Pressing submit will present the info in an alert. This form has no error handling and no data validation; we need to add all this in. Traditionally adding in the above, would mean we would be adding a lot more code to get this form working well. The form is only collecting a small amount of info, but as an app grows, the form will probably need to collect more data, meaning we would have even more code to write. Also, what about when we start adding in more forms across the app? We need to try and make all our error handling and data validation re-usable and more generic if we are going to adhere to the DRY principle of programming. Now I am having a panic attack thinking about all the tedious grunt work I have to do to get the form working. I want to focus on building better user experiences, not making forms. So let's nip it in the bud right now and add Formix.

Use npm to install Formik:

npm install --save formik

Let's prep our app file for Formik. We can remove the useState hook, value change function and the form component; don't delete the contents, comment it out. Formik manages all the state and value changes for us internally, so there is no need for us to handle it ourselves anymore. Instead, we can focus on building the form and excellent user experiences. Your app file should now look like this.

import React from "react";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomLabel from "./components/CustomLabel";
import CustomButton from "./components/CustomButton";

import "./App.css";

function App() {
  return (
    <div className="App">
      <div>
        {/* <CustomLabel htmlFor="userFirstName">First Name</CustomLabel>
          <CustomInput
            id="userFirstName"
            name="firstName"
            value=
            onChange=
          />
          <CustomLabel htmlFor="userLastName">Last Name</CustomLabel>
          <CustomInput
            id="userLastName"
            name="lastName"
            value=
            onChange=
          />
          <CustomLabel htmlFor="userEmail">Email</CustomLabel>
          <CustomInput
            id="userEmail"
            name="email"
            value=
            onChange=
          />
          <CustomButton type="submit">Submit</CustomButton> */}
      </div>
    </div>
  );
}

export default App;

Now we can add Formik into the file at the top with an import statement. To initialise Formik, we need to pass in an initial state for the data we are collecting as an object. I like to pass this in through props to the component because then I can initialise a default state with default props and also pass in a saved state from Redux or somewhere else in the app if there is one. Update your app file to look like my example below.

import React from "react";
import PropTypes from "prop-types";
import { Formik, Form } from "formik";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomLabel from "./components/CustomLabel";
import CustomButton from "./components/CustomButton";

import "./App.css";

function App({ savedUser }) {
  return (
    <div className="App">
      <div>
        <Formik initialValues={savedUser}>
          <Form></Form>
        </Formik>

        {/* <CustomLabel htmlFor="userFirstName">First Name</CustomLabel>
          <CustomInput
            id="userFirstName"
            name="firstName"
            value=
            onChange=
          />
          <CustomLabel htmlFor="userLastName">Last Name</CustomLabel>
          <CustomInput
            id="userLastName"
            name="lastName"
            value=
            onChange=
          />
          <CustomLabel htmlFor="userEmail">Email</CustomLabel>
          <CustomInput
            id="userEmail"
            name="email"
            value=
            onChange=
          />
          <CustomButton type="submit">Submit</CustomButton> */}
      </div>
    </div>
  );
}

App.defaultProps = {
  savedUser: {
    firstName: "",
    lastName: "",
    email: ""
  }
};

App.propTypes = {
  savedUser: PropTypes.object
};

export default App;

Every form needs validation and Formik allows you to build custom validation logic of your own; however, it also has a fantastic partnership with Yup. If you have not encountered Yup before, in a nutshell, it’s the lightweight version of Joi, so perfect for using on the front end. Let’s install Yup using npm.

npm install --save yup

Using Yup, I can now enforce that users enter a valid email address, the first and last name are required fields and must be at least three characters long. To achieve this, make the following changes to your app file.

import React from "react";
import PropTypes from "prop-types";
import { Formik, Form } from "formik";
import * as Yup from "yup";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomLabel from "./components/CustomLabel";
import CustomButton from "./components/CustomButton";

import "./App.css";

function App({ savedUser }) {
  return (
    <div className="App">
      <div>
        <Formik
          initialValues={savedUser}
          validationSchema={Yup.object({
            fName: Yup.string()
              .min(2, "First name must me more than two characters")
              .required("First name is required"),
            lName: Yup.string()
              .min(2, "Last name must me more than two characters")
              .required("Last name is required"),
            email: Yup.string()
              .email("Invalid email address")
              .required("Email is required")
          })}
        >
          <Form></Form>
        </Formik>

        {/* <CustomLabel htmlFor="userFirstName">First Name</CustomLabel>
          <CustomInput
            id="userFirstName"
            name="firstName"
            value=
            onChange=
          />
          <CustomLabel htmlFor="userLastName">Last Name</CustomLabel>
          <CustomInput
            id="userLastName"
            name="lastName"
            value=
            onChange=
          />
          <CustomLabel htmlFor="userEmail">Email</CustomLabel>
          <CustomInput
            id="userEmail"
            name="email"
            value=
            onChange=
          />
          <CustomButton type="submit">Submit</CustomButton> */}
      </div>
    </div>
  );
}

App.defaultProps = {
  savedUser: {
    firstName: "",
    lastName: "",
    email: ""
  }
};

App.propTypes = {
  savedUser: PropTypes.object
};

export default App;

As you can see, Yup allows me to write a custom error message for validation failure. Later on, we will hook these up to our inputs for giving a user instant feedback even before they press submit. I would highly recommend going and checking Yup’s full documentation so you can see what’s possible.

Yup

Let move on now and put our inputs back in, as they need to go inside the Form component. Remove the value and onChange props from each component, then wrap all the form components in a div and give it the class name “form-container” to re-apply the styling. The code in the app file should be looking similar to my example below.

import React from "react";
import PropTypes from "prop-types";
import { Formik, Form } from "formik";
import * as Yup from "yup";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomLabel from "./components/CustomLabel";
import CustomButton from "./components/CustomButton";

import "./App.css";

function App({ savedUser }) {
  return (
    <div className="App">
      <div>
        <Formik
          initialValues={savedUser}
          validationSchema={Yup.object({
            fName: Yup.string()
              .min(2, "First name must me more than two characters")
              .required("First name is required"),
            lName: Yup.string()
              .min(2, "Last name must me more than two characters")
              .required("Last name is required"),
            email: Yup.string()
              .email("Invalid email address")
              .required("Email is required")
          })}
        >
          <Form>
            <div className="form-container">
              <CustomLabel htmlFor="userFirstName">First Name</CustomLabel>
              <CustomInput id="userFirstName" name="firstName" />

              <CustomLabel htmlFor="userLastName">Last Name</CustomLabel>
              <CustomInput id="userLastName" name="lastName" />

              <CustomLabel htmlFor="userEmail">Email</CustomLabel>
              <CustomInput id="userEmail" name="email" />
              <CustomButton type="submit">Submit</CustomButton>
            </div>
          </Form>
        </Formik>
      </div>
    </div>
  );
}

App.defaultProps = {
  savedUser: {
    firstName: "",
    lastName: "",
    email: ""
  }
};

App.propTypes = {
  savedUser: PropTypes.object
};

export default App; 
form1.png

The form is now displaying again, but it’s not working, and we have no error message showing. So let’s update our input component and make a few adjustments, so it works with Formik and shows error messages. Go to CustomInput in the components folder and make the following changes in the index file and then in the CSS file:

import React from "react";
import { useField } from "formik";

import CustomLabel from "../CustomLabel";

import "./CustomInput.css";

const CustomInput = ({ label, ...props }) => {
  const [field, meta] = useField(props);

  return (
    <React.Fragment>
      <CustomLabel htmlFor={props.id}>{label}</CustomLabel>
      <input className="custom-input" {...field} {...props} />
      {meta.touched && meta.error ? (
        <span className="custom-input-error">{meta.error}</span>
      ) : null}
    </React.Fragment>
  );
};

export default CustomInput;
.custom-input {
  padding: 0.375rem 15px;
  min-height: 20px;
  border-radius: 5px;
  border: 1px solid grey;
  font-style: normal;
  font-weight: normal;
  font-size: 16px;
  line-height: 130%;
}

.custom-input-error {
  margin-top: 5px;
  font-style: normal;
  font-weight: normal;
  font-size: 12px;
  line-height: 100%;
  color: darkred;
}

So what’s happening here? Using the useField hook from Formik, we can access properties such as onChange, onBlur and metadata that comes from the state now stored inside the Formik form. When data in the form triggers an error by failing the validation done in Yup, the error message will now show underneath the input. Dive a little deeper, and you can see Formik is tracking onChange and dealing with the value of each field. The inputs touched status is also monitored by Formik, allowing it to check and validate the data inside when a user leaves the input, meaning users can get an error about the data instantly rather than waiting until they submit the form.

Now we need to make a few changes to the app file to get the form working. So let’s go ahead and make these changes.

import React from "react";
import PropTypes from "prop-types";
import { Formik, Form } from "formik";
import * as Yup from "yup";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomButton from "./components/CustomButton";

import "./App.css";

function App({ savedUser }) {
  return (
    <div className="App">
      <div>
        <Formik
          initialValues={savedUser}
          validationSchema={Yup.object({
            firstName: Yup.string()
              .min(2, "First name must me more than two characters")
              .required("First name is required"),
            lastName: Yup.string()
              .min(2, "Last name must me more than two characters")
              .required("Last name is required"),
            email: Yup.string()
              .email("Invalid email address")
              .required("Email is required")
          })}
          onSubmit={values => {
            console.log(values);
          }}
        >
          {props => (
            <Form>
              <div className="form-container">
                <CustomInput
                  label="First Name"
                  id="userFirstName"
                  name="firstName"
                  type="text"
                />

                <CustomInput
                  label="Last Name"
                  id="userLastName"
                  name="lastName"
                  type="text"
                />

                <CustomInput
                  label="Email"
                  id="userEmail"
                  name="email"
                  type="email"
                />

                <CustomButton disabled={props.isSubmitting} type="submit">
                  Submit
                </CustomButton>
              </div>
            </Form>
          )}
        </Formik>
      </div>
    </div>
  );
}

App.defaultProps = {
  savedUser: {
    firstName: "",
    lastName: "",
    email: ""
  }
};

App.propTypes = {
  savedUser: PropTypes.object
};

export default App;

Let’s see if validation works. Click into a field and then click out. You should see our error messages appearing below the input about the information missing. Before we work on the submit function, I’ll explain a few other things we are doing, so it’s easier for you to build your forms. The name field you pass in via props to each input is used by Formix to track the value, onChange and other functions. Formik uses the name field to identify changes, values, initial values, and which Yup validation is needed from your Yup schema for each field. A worthy mention is an id. I have seen too many forms with the id missing. The id binds the label and input together. Passing in the id to our CustomInput adds it to the input and the htmlFor props in the label, letting screen readers know which input belongs to what label and if users click the label, it selects the input.

Right, enough learning. Let’s make the form even better and make it submit. Using Formik, we can also make sure that while data is being validated and submitted, the submit button is disabled, stopping trigger happy users from causing issues. Update your app file to the following code:

import React from "react";
import PropTypes from "prop-types";
import { Formik, Form } from "formik";
import * as Yup from "yup";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomButton from "./components/CustomButton";

import "./App.css";

function App({ savedUser }) {
  return (
    <div className="App">
      <div>
        <Formik
          initialValues=
          validationSchema={Yup.object({
            firstName: Yup.string()
              .min(2, "First name must me more than two characters")
              .required("First name is required"),
            lastName: Yup.string()
              .min(2, "Last name must me more than two characters")
              .required("Last name is required"),
            email: Yup.string()
              .email("Invalid email address")
              .required("Email is required")
          })}
          onSubmit={values => {
            alert(`
            Submitted User:
            User: $ $
            Email: $
            `);
          }}
        >
          {props => (
            <Form>
              <div className="form-container">
                <CustomInput
                  label="First Name"
                  id="userFirstName"
                  name="firstName"
                  type="text"
                />

                <CustomInput
                  label="Last Name"
                  id="userLastName"
                  name="lastName"
                  type="text"
                />

                <CustomInput
                  label="Email"
                  id="userEmail"
                  name="email"
                  type="email"
                />

                <CustomButton disabled= type="submit">
                  Submit
                </CustomButton>
              </div>
            </Form>
          )}
        </Formik>
      </div>
    </div>
  );
}

App.defaultProps = {
  savedUser: {
    firstName: "",
    lastName: "",
    email: ""
  }
};

App.propTypes = {
  savedUser: PropTypes.object
};

export default App;

Brilliant. We now have a form that works nicely, gives instant feedback and should stop users from entering incorrect data. So, start using Formik right now. But wait! What if you have a custom input? How can you plug Formix into that? No need to panic. Formix has even more goodness up its sleeve to help you out. Let’s go ahead and install React-Select; a drop-down component for React.

React Select

npm install --save react-select

Go ahead and make another folder in the components folder called CustomDropdown. Add the following index.js file; we won’t bother with styling. If you want to learn more about React-Select, check out their documentation.

import React from "react";
import Select from "react-select";
import { useField } from "formik";

import CustomLabel from "../CustomLabel";

const CustomDropdown = ({ options, label, ...props }) => {
  const [field, meta] = useField(props);

  return (
    <React.Fragment>
      <CustomLabel htmlFor={props.id}>{label}</CustomLabel>
      <Select options={options} {...field} {...props} />
      {meta.touched && meta.error ? (
        <span className="custom-input-error">{meta.error}</span>
      ) : null}
    </React.Fragment>
  );
};

export default CustomDropdown:

Side note here: for the error span we are using the same class name as the CustomInput, don’t do this in your project due to potential styling issues. Move the CSS to a more global position if it’s shared. If it’s going to be different, add CSS to the drop-down file or use styling objects. We are just taking a few short cuts.

Another change to your app file is needed to add the drop-down.

import React from "react";
import PropTypes from "prop-types";
import { Formik, Form } from "formik";
import * as Yup from "yup";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomButton from "./components/CustomButton";
import CustomDropdown from "./components/CustomDropdown";

import "./App.css";

function App({ savedUser }) {
  return (
    <div className="App">
      <div>
        <Formik
          initialValues=
          validationSchema={Yup.object({
            firstName: Yup.string()
              .min(2, "First name must me more than two characters")
              .required("First name is required"),
            lastName: Yup.string()
              .min(2, "Last name must me more than two characters")
              .required("Last name is required"),
            email: Yup.string()
              .email("Invalid email address")
              .required("Email is required"),
            permission: Yup.string().required("Permission type is required")
          })}
          onSubmit={values => {
            alert(`
            Submitted User:
            User: $ $
            Email: $
            `);
          }}
        >
          {props => (
            <Form>
              <div className="form-container">
                <CustomInput
                  label="First Name"
                  id="userFirstName"
                  name="firstName"
                  type="text"
                />

                <CustomInput
                  label="Last Name"
                  id="userLastName"
                  name="lastName"
                  type="text"
                />

                <CustomInput
                  label="Email"
                  id="userEmail"
                  name="email"
                  type="email"
                />

                <CustomDropdown
                  label="User Permissions"
                  id="userPermission"
                  name="permission"
                  type="text"
                  options={[
                    { value: "admin", label: "Admin Access" },
                    { value: "normal", label: "Normal Access" }
                  ]}
                />

                <CustomButton disabled= type="submit">
                  Submit
                </CustomButton>
              </div>
            </Form>
          )}
        </Formik>
      </div>
    </div>
  );
}

App.defaultProps = {
  savedUser: {
    firstName: "",
    lastName: "",
    email: ""
  }
};

App.propTypes = {
  savedUser: PropTypes.object
};

export default App;
TypeError: Cannot read property ‘type’ of undefined

TypeError: Cannot read property ‘type’ of undefined

The reason is React-Select is a custom component and doesn’t behave like native components. Thankfully, Formik is fantastic at working with third-party or custom-built components. The workaround is to use Formik’s setFieldValue function for changes. Let updates the code in our drop-down and then talk through what’s happening.

import React from "react";
import Select from "react-select";
import { useFormikContext, useField } from "formik";

import CustomLabel from "../CustomLabel";

const CustomDropdown = ({ options, label, ...props }) => {
  const { setFieldValue, setFieldTouched } = useFormikContext();
  const [field, meta] = useField(props);

  /**
   * Will manually set the value belong to the name props in the Formik form using setField
   */
  function handleOptionChange(selection) {
    setFieldValue(props.name, selection);
  }

  /**
   * Manually updated the touched property for the field in Formik
   */
  function updateBlur() {
    setFieldTouched(props.name, true);
  }

  return (
    <React.Fragment>
      <CustomLabel htmlFor={props.id}>{label}</CustomLabel>
      <Select
        options={options}
        {...field}
        {...props}
        onBlur={updateBlur}
        onChange={handleOptionChange}
      />
      {meta.touched && meta.error ? (
        <span className="custom-input-error">{meta.error.value}</span>
      ) : null}
    </React.Fragment>
  );
};

export default CustomDropdown;

The drop-down set up is like our CustomInput; however, we use a new hook called useFormikContext that allows us to grab Formik’s setFieldValue and setFieldTouched function. We need to override these functions and call them with custom implementations. React-Select returns the selected object, so we need to add the props name value to setFieldValue for it to work correctly, and the same goes for our onBlur. Just a few small changes required to the app file now, and we are all done.

import React from "react";
import PropTypes from "prop-types";
import { Formik, Form } from "formik";
import * as Yup from "yup";

// Custom Inputs
import CustomInput from "./components/CustomInput";
import CustomButton from "./components/CustomButton";
import CustomDropdown from "./components/CustomDropdown";

import "./App.css";

function App({ savedUser }) {
  return (
    <div className="App">
      <div>
        <Formik
          initialValues=
          validationSchema={Yup.object({
            firstName: Yup.string()
              .min(2, "First name must me more than two characters")
              .required("First name is required"),
            lastName: Yup.string()
              .min(2, "Last name must me more than two characters")
              .required("Last name is required"),
            email: Yup.string()
              .email("Invalid email address")
              .required("Email is required"),
            permission: Yup.object().shape({
              label: Yup.string().required(),
              value: Yup.string().required("Permission type is required")
            })
          })}
          onSubmit={values => {
            alert(`
            Submitted User:
            User: $ $
            Email: $
            Permission: $
            `);
          }}
        >
          {props => (
            <Form>
              <div className="form-container">
                <CustomInput
                  label="First Name"
                  id="userFirstName"
                  name="firstName"
                  type="text"
                />

                <CustomInput
                  label="Last Name"
                  id="userLastName"
                  name="lastName"
                  type="text"
                />

                <CustomInput
                  label="Email"
                  id="userEmail"
                  name="email"
                  type="email"
                />

                <CustomDropdown
                  label="User Permissions"
                  id="userPermission"
                  name="permission"
                  type="text"
                  options={[
                    { value: "admin", label: "Admin Access" },
                    { value: "normal", label: "Normal Access" }
                  ]}
                />

                <CustomButton disabled= type="submit">
                  Submit
                </CustomButton>
              </div>
            </Form>
          )}
        </Formik>
      </div>
    </div>
  );
}

App.defaultProps = {
  savedUser: {
    firstName: "",
    lastName: "",
    email: "",
    permission: { value: "", label: "" }
  }
};

App.propTypes = {
  savedUser: PropTypes.object
};

export default App;

The form now validates all data, makes sure all required data is there, reports errors back to the users and stops multiple submits. Top this all off with the fact that we didn’t have to write a lot of code; you should start using Formik in React today. Go forward and make it easier for yourself to build forms so you can make it easier and more fun for your users. One important side note. Always makes sure to set an initial value for your fields. Otherwise, you might find yourself having some issues with validation.

I highly recommend checking out the full documentation for Formik. I am currently using this across twenty-plus custom forms in one app, and I am happy to say, user errors have dropped, my frustration with making and maintaining forms has gone, and lines of code has shrunk. As always, feel free to reach out if you have questions and switch to the finished-project branch to see the final project.

Formik


This content originally appeared on JavaScript January and was authored by Emily Freeman


Print Share Comment Cite Upload Translate Updates
APA

Emily Freeman | Sciencx (2020-01-09T17:07:06+00:00) Getting Forms Right in React. Retrieved from https://www.scien.cx/2020/01/09/getting-forms-right-in-react/

MLA
" » Getting Forms Right in React." Emily Freeman | Sciencx - Thursday January 9, 2020, https://www.scien.cx/2020/01/09/getting-forms-right-in-react/
HARVARD
Emily Freeman | Sciencx Thursday January 9, 2020 » Getting Forms Right in React., viewed ,<https://www.scien.cx/2020/01/09/getting-forms-right-in-react/>
VANCOUVER
Emily Freeman | Sciencx - » Getting Forms Right in React. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2020/01/09/getting-forms-right-in-react/
CHICAGO
" » Getting Forms Right in React." Emily Freeman | Sciencx - Accessed . https://www.scien.cx/2020/01/09/getting-forms-right-in-react/
IEEE
" » Getting Forms Right in React." Emily Freeman | Sciencx [Online]. Available: https://www.scien.cx/2020/01/09/getting-forms-right-in-react/. [Accessed: ]
rf:citation
» Getting Forms Right in React | Emily Freeman | Sciencx | https://www.scien.cx/2020/01/09/getting-forms-right-in-react/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.