Creating a Contact Form with Validation with React and Material UI
Tags: react, materialui, typescript
Published: 2021-03-09 19:59:59



In this article I'll be going through how we can build our own simple contact form component with validation in React, Typescript and Material UI. Scroll down to the end of the page to see the CodeSandbox url for this.

##Form Skeleton 💀

First, we'll create the react component, let's call it *ContactForm*
```react
const ContactForm = () => {}
```
Then we'll add an empty form element
```react
export const ContactForm = () => 
{
  return (
    <form>
    </form>
  )
}
```
This form will do nothing at the moment and won't display anything on the page. So we'll start adding the form elements using Material UI components. This will build up the basic skeleton of the contact form. The elements we're adding are:
* Three text fields for the user to input their name, email and message.
* One button used to submit the form.
```react
export const ContactForm = () => 
{
  return (
    <form>
      <TextField label="Full Name" />
      <TextField label="Email"/>
      <TextField label="Message"/>
      <Button type="submit">Submit</Button>
    </form>
  )
}
```
The form now should look like this:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbtymxwfe62l5jfh4s2n.png)
 
We'll make some adjustments to make the form look nicer so we'll add `fullWidth` to the `TextField` components and add `multiline` and `rows={5}` to the message text field:
_If fullWidth is set, the input will take up the full width of its container._
```react
export const ContactForm = () => 
{
  return (
    <form>
      <TextField label="Full Name" fullWidth autocomplete="none"/>
      <TextField label="Email" fullWidth autocomplete="none"/>
      <TextField label="Message" fullWidth multiline rows={5} autocomplete="none"/>
      <Button type="submit">Submit</Button>
    </form>
  )
}
```

##Form Validation ✅
Now that our form is looking a bit better we'll start looking at the validation side of things.
Let's create a new function in a separate file to handle our validation and we'll add and expose the functions that we need to validate the form's input values.
```react

const initialFormValues = {
  fullName: "",
  email: "",
  message:"",
  formSubmitted: false,
  success: false
}

export const useFormControls = () => {
  // We'll update "values" as the form updates
  const [values, setValues] = useState(initialFormValues);
  // "errors" is used to check the form for errors
  const [errors, setErrors] = useState({} as any);
  const validate: any = (fieldValues = values) => {
    // this function will check if the form values are valid
  }
  const handleInputValue: any = (fieldValues = values) => {
    // this function will be triggered by the text field's onBlur and onChange events
  }
  const handleFormSubmit = async (e: any) => {
    // this function will be triggered by the submit event
  }
  const formIsValid: any = () => {
    // this function will check if the form values and return a boolean value
  }
 return {
    handleInputValue,
    handleFormSubmit,
    formIsValid,
    errors
  };
}
```
Now we have the functions in place we'll set up the event handling. We'll also need to get access to the functions in the `useFormControls` component so we'll create an object that will contain the initial form values
```react
export const ContactForm = () => {
  const {
    handleInputValue,
    handleFormSubmit,
    formIsValid,
    errors
  } = useFormControls();
  return (
    <form onSubmit={handleFormSubmit}>	
      <TextField name="fullName" onBlur={handleInputValue} onChange={handleInputValue} label="Full Name" fullWidth autoComplete="none" {...(errors["fullName"] && { error: true, helperText: errors["fullName"] })}/>	
      <TextField name="email" onBlur={handleInputValue} onChange={handleInputValue} label="Email" fullWidth autoComplete="none" {...(errors["email"] && { error: true, helperText: errors["email"]  })}/>	
      <TextField name="message" onBlur={handleInputValue} onChange={handleInputValue} label="Message" fullWidth multiline rows={5} autoComplete="none" {...(errors["message"] && { error: true, helperText: errors["message"] })}/>	
      <Button type="submit" disabled={!formIsValid()}>Submit</Button>	
    </form>	
  )
}
```

Our input fields have shared properties and values so to make the code DRY, we'll create an array with our text fields' properties' values and add it to the top of the file and loop through it:

```react
const inputFieldValues = [
  {
    name: "fullName",
    label: "Full Name",
    id: "my-name"
  },
  {
    name: "email",
    label: "Email",
    id: "my-email"
  },
  {
    name: "message",
    label: "Message",
    id: "my-message",
    multiline: true,
    rows: 10
  }
];
export const ContactForm = () => {
  const {
    handleInputValue,
    handleFormSubmit,
    formIsValid,
    errors
  } = useFormControls();
  return (
    <form onSubmit={handleFormSubmit}>
      {inputFieldValues.map((inputFieldValue, index) => {
        return (
          <TextField
            key={index}
            onBlur={handleInputValue}
	    onChange={handleInputValue}
            name={inputFieldValue.name}
            label={inputFieldValue.label}
            multiline={inputFieldValue.multiline ?? false}
            rows={inputFieldValue.rows ?? 1}
	    autoComplete="none"
	    {...(errors[inputFieldValue.name] && { error: true, helperText: errors[inputFieldValue.name] })}

          />
        );
      })}
      <Button
        type="submit"
        disabled={!formIsValid()}
      >
        Send Message
      </Button>
    </form>
  )
}
```
 
That's all set up then .. Now we just need to start filling in the values in the `useFormControls` component. 

We'll start with the onBlur and onChange events. We need this to show an error message if the user clicks in the input box and clicks out without typing anything. The onChange event will be triggered when the value in the text field is changed and this will trigger the same function `handleInputValue`

```react
const handleInputValue = (e: any) => {
    const { name, value } = e.target;
    setValues({
      ...values,
      [name]: value
    });
    validate({ [name]: value });
  };
```

This 👆🏼 will update the state variable `values` for a specific element (e.g. When the "email" text field is updated where the name is "email", the value of "email is updated). 

This function will call the `validate` function which validates the value of the text field that was changed and sets the appropriate error message. A regex will be used to validate against the email value to ensure the correct format hhas been entered. The state variable `errors` gets updated with the relevent message

```react
const validate: any = (fieldValues = values) => {
    let temp: any = { ...errors }

    if ("fullName" in fieldValues)
      temp.fullName = fieldValues.fullName ? "" : "This field is required."

    if ("email" in fieldValues) {
      temp.email = fieldValues.email ? "" : "This field is required."
      if (fieldValues.email)
        temp.email = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(fieldValues.email)
          ? ""
          : "Email is not valid."
    }

    if ("message" in fieldValues)
      temp.message =
        fieldValues.message ? "" : "This field is required."

    setErrors({
      ...temp
    });
  }

```
  
Next we update the `formIsValid` function
> The errors state variable would have been updated in the `validate` function.
> We check if the properties are set, to cover the first time the contact form loads when the errors state variable is empty.

```react
 const formIsValid = (fieldValues = values) => {
    const isValid =
      fieldValues.fullName &&
      fieldValues.email &&
      fieldValues.message &&
      Object.values(errors).every((x) => x === "");

    return isValid;
  };
```

And last but not least we have the function that actually submits the form to be sent. The functionality to send the contact form by email `postContactForm` isn't covered as part of this tutorial but I will be covering it in a later tutorial.

```react
const handleFormSubmit = async (e: any) => {
    e.preventDefault();
    if (formIsValid()) {
      await postContactForm(values);
      alert("You've posted your form!")
    }
  };
```

At the end of this, you will have a functioning contact form (minus the sending email part 😊).


-----

I hope this helps. You can find the full working code here:

{% codesandbox contact-form-fbg1v %}

 In a later post, I will be going through sending an email to a .NET Core backend and displaying a message on the screen.