Angular to React.js - Forms (Part 5 of 6)

Written by Cristian Marquez

Welcome back to the fifth part of my six-part guide for Angular developers looking to learn React. At this point, our app allows users to view a product catalog, click a product name, navigate to a product’s detail page, add the product to a cart, and view the cart. Our next step is to collect information from users so they can purchase products from our store.


To get started, fork the project



Creating Forms in React


React on its own does not include specific functionality for managing forms. Form elements need to be managed using javascript without a framework, also known as Vanilla javascript.


Let’s start by looking at an input element.


  
<input type="text" value={name} onChange={(e) => handleChange(e, setName)} />
  
  

We need to find a way to manually update the prop value whenever the onChange event is called.


To control the value props, we will create a local state and function.

  
const ReactForm = () => {
  const [name, setName] = useState("");
  const handleChange = event => {
      setName(event.target.value);
  };
  return <input type="text" value={name} onChange={handleChange} />
}
  
  

React requires a separate state for each input element and a handleChange method to update the value prop. Our simple example above only handles one input element, but real-world forms can contain many input elements. Manually implementing a large form with many inputs can be tedious without the help of React libraries created specifically for form management.


For this project, we will use one of the most popular React form libraries, Formik. To learn more about some of the other popular libraries, take a look at this article.



Setting Up Formik


  1. Following the instructions in the Formik documentation, add Formik to the project.
  2. Create UserInformation.js inside the Components folder.
  3. Copy and paste the code below into the file you just created.
        
    import React from "react";
    import { Formik } from "formik";
    import * as Yup from "yup";
      
    const UserSchema = Yup.object().shape({
      name: Yup.string().required("Name is required"),
      address: Yup.string().required("Address is required")
    });
      
    const UserInformation = ({ clearCart }) => (
      <Formik
        initialValues={{ name: "", address: "" }}
        validationSchema={UserSchema}
        onSubmit={(values, { setSubmitting, resetForm }) => {
          setTimeout(() => {
            console.warn(
              "Your order has been submitted",
              JSON.stringify(values, null, 2)
            );
            setSubmitting(false);
            clearCart();
            resetForm();
          }, 400);
        }}
      >
        {({
          values,
          errors,
          touched,
          handleChange,
          handleBlur,
          handleSubmit,
          isSubmitting
        }) => (
          <form onSubmit={handleSubmit}>
            <div>
              <label htmlFor="name">Name</label>
              <input
                type="text"
                name="name"
                onChange={handleChange}
                onBlur={handleBlur}
                value={values.name}
              />
              {errors.name && touched.name && <p className="error">{errors.name}</p>}
            </div>
            <div>
              <label htmlFor="address">Address</label>
              <input
                type="text"
                name="address"
                onChange={handleChange}
                onBlur={handleBlur}
                value={values.address}
              />
              {errors.address && touched.address && <p className="error">{errors.address}</p>}
            </div>
      
            <button type="submit" disabled={isSubmitting}>
              Purchase
            </button>
          </form>
        )}
      </Formik>
    );
      
    export default UserInformation;
      
    
  4. Import the UserInformation component into the Cart.js component, and insert the new UserInformation component below the products list.
      
    import UserInformation from './UserInformation';
     
    ...
    {products.map(item => (
      <div key={item.name} className="cart-item">
        <span>{item.name}</span>
        <span>{utils.currency(item.price)}</span>
      </div>
    ))}
    <UserInformation clearCart={clearCart} />
    ...
    Your cart now includes a form with name and address fields.

Managing Forms with Formik


When we created the UserInformation file above, we wrapped the HTML form element in a function instead of providing it directly as a child of the Formik component. This is called a render props pattern. This pattern allows Formik to provide a set of methods and variables to the HTML form elements - like handleChange and handleBlur - that make form management more efficient.


  
<Formik
...
>
  {formStatesAndSharedMethods => (
    {/* You can access the shared functionality here */}	
  )}
</Formik>

To control the HTML form submit event, we will use the handleSubmit function.


<form onSubmit={handleSubmit}>

To manage the HTML input elements we will use handleChange, handleBlur, and the values object.



<input
  type="name"
  name="name"
  onChange={handleChange}
  onBlur={handleBlur}
  value={values.name}
/>

To control form errors, we will use Formik’s errors object. For example, errors.address will display the errors of the form element with the name address.


If a user has not yet touched an input, we don’t want the error to display. Luckily, we can access the touched object to identify user interaction.


{errors.address && touched.address && <p className="error">errors.address</p>}

Our form should be disabled after a user clicks the submit button to prevent the system from sending multiple requests to the server before it can respond. To disable the button after the first submission, we can add the isSubmitting variable to the disabled prop.


<button type="submit" disabled={isSubmitting}>
  

For a list of all available Formik methods and variables, visit here.



Improving the Code


Formik provides Form, Field and ErrorMessage components to make the current implementation more concise.


  1. Create a new component called UserInformationRefactor.js inside the /components folder.
  2. Copy and paste the following code:
    
    import React from "react";
    import { Formik, Form, Field, ErrorMessage } from "formik";
      
    const UserInformation = ({ clearCart }) => (
      <Formik
        initialValues={{ name: "", address: "" }}
        onSubmit={(values, { setSubmitting, resetForm }) => {
          setTimeout(() => {
            console.warn(
              "Your order has been submitted",
              JSON.stringify(values, null, 2)
            );
            setSubmitting(false);
            clearCart();
            resetForm();
          }, 400);
        }}
      >
        {({ isSubmitting }) => (
          <Form>
            <div>
              <label htmlFor="name">Name</label>
              <Field name="name" />
              <ErrorMessage name="name" component="p" />
            </div>
            <div>
              <label htmlFor="address">Address</label>
              <Field name="address" />
              <ErrorMessage name="address" component="p" />
            </div>
      
            <button type="submit" disabled={isSubmitting}>
              Purchase
            </button>
          </Form>
        )}
      </Formik>
    );
      
    export default UserInformation;
    

The code above replaces the default HTML elements with corresponding Formik components: form becomes Form, input becomes Field, and errors are shown with ErrorMessage. These Formik components automatically handle much of the functionality we implemented manually.


The Form component sets up the onSubmit={handleSubmit} prop so you don’t have to provide it.


The Field component renders an <input /> element with the onChange={handleChange}, onBlur={handleBlur}, and value={values[name]} props based on the name prop provided.


The ErrorMessage component conditionally renders the error associated with the corresponding Field component’s name.



Implementing Validation


Although we set up the form error display above, we have not yet configured the list of possible errors for each component.

  1. Install the library yup.
  2. Import the package.
    
    import * as Yup from "yup";
    
  3. Create a Yup object with the following configuration:
    
    const UserSchema = Yup.object().shape({
      name: Yup.string().required("Name is required"),
      address: Yup.string().required("Address is required")
    });     
    
  4. Add the prop below to the Formik component.
    
    <Formik
      validationSchema={UserSchema}
    
  5. Add the following styles into the style.css file.
    
    .error {
      color: #FA4048;
      margin: 0 0 16px;
      font-size: 13px;
    }    
    

Now, the associated error will display if the field has been touched and the user clicks away.



To learn more about yup, browse the yup documentation.



Solutions



What's next?


So far, we have covered setup, routing, data management, async actions, and form management. The final article of this series will cover the deployment process. See you there.


---
At FullStack Labs, we pride ourselves on our ability to push the capabilities of cutting-edge frameworks like React. Interested in learning more about speeding up development time on your next project? Contact us.

Let’s Talk!

We’d love to learn more about your project. Contact us below for a free consultation with our CEO.
Projects Start at $25,000.

FullStack Labs
This field is required
This field is required
Type of project
Reason for contact:
How did you hear about us? This field is required