DEV Community

Cover image for Creating a Contact Form with Validation with React and Material UI
Hiba E
Hiba E

Posted on

Creating a Contact Form with Validation with React and Material UI

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

const ContactForm = () => {}
Enter fullscreen mode Exit fullscreen mode

Then we'll add an empty form element

export const ContactForm = () => 
{
  return (
    <form>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

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.
export const ContactForm = () => 
{
  return (
    <form>
      <TextField label="Full Name" />
      <TextField label="Email"/>
      <TextField label="Message"/>
      <Button type="submit">Submit</Button>
    </form>
  )
}
Enter fullscreen mode Exit fullscreen mode

The form now should look like this:
image

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.

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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.


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
  };
}
Enter fullscreen mode Exit fullscreen mode

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

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> 
  )
}
Enter fullscreen mode Exit fullscreen mode

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:

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>
  )
}
Enter fullscreen mode Exit fullscreen mode

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

const handleInputValue = (e: any) => {
    const { name, value } = e.target;
    setValues({
      ...values,
      [name]: value
    });
    validate({ [name]: value });
  };
Enter fullscreen mode Exit fullscreen mode

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

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
    });
  }

Enter fullscreen mode Exit fullscreen mode

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.

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

    return isValid;
  };
Enter fullscreen mode Exit fullscreen mode

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.

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

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:

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

Top comments (3)

Collapse
 
paultrimor profile image
paul • Edited

Good idea for creating a Form Controls hook. However, your design doesn't keep the state of the form at the high-level component. I am not sure this is the "React" way of doing things.

Collapse
 
hibaeldursi profile image
Hiba E

Fair point 😃 This is quite an old post from when I first started learning react. Looking at the code I would probably do it differently now 😅

Collapse
 
humbertojr profile image
Humberto Jr

Thanks very much Hiba your post, help me much in my project, because i need start fast