DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Cover image for Handling Forms in React
sairam
sairam

Posted on • Updated on

Handling Forms in React

Form is an essential and basic part of any Modern Web Application. It allows user to interact with the application as well as give their data. Forms can be used to perform various actions as per the nature of business requirement and It can be used anywhere like user authentication, adding/updating user, filtering, ordering etc. It has various type of elements to collect the information based on the type like, input, textarea, select, button etc.

Well, there are many front end frameworks each have it own way of handling forms. In Angular, There are two ways of handling like Template Driven and Reactive Forms.

When it comes to React, there are two ways of handling in react too as following,

  1. Uncontrolled Components We depend on DOM elements by attaching refs to it. Data fetching from the elements and validation becomes difficult and hard to manage.
  2. Controlled Components Unlike Uncontrolled Components, We maintain complete data as/in state variable. It is easy to maintain and most followed practice and recommended one in React Community.

As React is not complete framework like angular. For handling forms React, either we need to write our own logic or depend on any third party package like React Hook Form, Formik.

Here is an example of basic React form written with minimal bootstrap CSS and no other libraries were included.

React Form Example

In the example shown above, first we initialize required fields - email and password with necessary properties - valid, touched, error and touched in the state using useState hook .

const [formData, setFormData] = useState({
    email: { valid: true, value: "", error: "",  touched: false },
    password: { valid: true, value: "", error: "", touched: false }
  });
Enter fullscreen mode Exit fullscreen mode

Next, we return JSX with form wrapped around with some elements, elements are bound with some events to handle the changes in the form.

 <div className="row" style={{ marginTop: "100px" }}>
      <div className="col-md-12">
        <h4 className="text-center">Sign In</h4>
        <form className="form-horizontal" onSubmit={onSubmit}>
          <div className="form-group">
            <label htmlFor="email" className="col-sm-2 control-label"> Email </label>
            <div className="col-sm-10">
              <input type="email" className="form-control" id="email" placeholder="Email" value={email.value} onChange={handleChange} name="email" autoComplete="off" />
              <label className="label label-danger" style={{ display: email.touched && email.error ? "inline" : "none" }}> {email.error} </label>
            </div>
          </div>
          <div className="form-group">
            <label htmlFor="password" className="col-sm-2 control-label"> Password</label>
            <div className="col-sm-10">
              <input type="password" className="form-control" id="password" placeholder="Password" value={password.value} onChange={handleChange} name="password" />
              <label className="label label-danger" style={{ display: password.touched && password.error ? "inline" : "none" }} > {password.error} </label>
            </div>
          </div>
          <div className="form-group">
            <div className="col-sm-offset-2 col-sm-10">
              <button type="submit" className="btn btn-default">Sign in</button>
            </div>
          </div>
        </form>
      </div>
    </div>
Enter fullscreen mode Exit fullscreen mode

Now, we write handleChange method to handle the changes in the state with the user input.

  const handleChange = (e) => {
    const target = e.target;
    const { name, value } = target;
    let prevFormData = { ...formData };
    let error = validateFormField(name, value);
    prevFormData[name] = {
      ...prevFormData[name],
      value: value.trim(),
      touched: true,
      error
    };
    setFormData(prevFormData);
  };
Enter fullscreen mode Exit fullscreen mode

Anytime values in the state updates, we run the validations against the input - using validateFormField method. Here in this method, we check for multiple conditions and returns a respective error message.

const validateFormField = (name, value) => {
    let error = "";
    if (name === "email") {
      if (!value) {
        error = "email is mandatory";
      }
      if (value) {
        if (!/^\w+([\\.-]?\w+)*@\w+([\\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
          error = "give a valid email";
        }
      }
    }
    if (name === "password") {
      if (!value) {
        error = "password is mandatory";
      }
      if (value) {
        if (value.length < 3) {
          error = "password should be atleast 3 characters";
        }
        if (value.length > 10) {
          error = "password should not exceed 10 characters";
        }
      }
    }
    return error;
  };
Enter fullscreen mode Exit fullscreen mode

And, we left with the final step i.e., handling submit. We are handling form submission process with onSubmit method. We need to check for the validity of the form, so as to do this, we need call validateFormField looping through the state.

  const onSubmit = (event) => {
    event.preventDefault();
    let prevFormData = { ...formData };
    let error;
    let isValid = true;
    for (let field in prevFormData) {
      error = validateFormField(field, prevFormData[field]["value"]);
      if (isValid && error) {
        isValid = false;
      }
      prevFormData[field] = {
        ...prevFormData[field],
        touched: true,
        error
      };
    }
    if (!isValid) {
      setFormData(prevFormData);
    } else {
      alert(JSON.stringify(prevFormData));
    }
  };
Enter fullscreen mode Exit fullscreen mode

Wow, finally we made it. Form is now ready to use. But, it is not that funny right? Yes, creating forms in React is not much funny. It need a lot of code base. So far we have written for only two text boxes, imagine if the form has at most 10 fields.

It is weird right? creating form, handling state management and validation status of 10 fields will become nightmare, boring as well. If we create a form in plain "React", we would do the following, at a minimum,

  1. Set up the state for the form values, form errors and validation status.
  2. Handling user input and state changes.
  3. Method for validating the inputs.
  4. Handling form submission method.

What is Formik?

Formik is the library that standardize the input component and controls the flow of form submission. It comes with state management and handling user input changes. Formik helps you to write the following parts of building the form:

  1. Validation and error messages
  2. Handling form submission process.
  3. Get values in and out of the form state

Here again the same example, but with Formik -

React Formik Example

Formik uses a design pattern - Render Callback. We pass an anonymous function as child element for Fomik, It will get called at some point of time from the Formik.

<Formik>
    {(props)=>{
       return (
         /* form comes here */
       )
    }}
</Formik>
Enter fullscreen mode Exit fullscreen mode

Let's see how Formik will make build React form easier.

Configuring Formik

Formik will need basic configuration as -

  1. Initial values
  2. Validation handler
  3. Submit handler
<Formik 
      initialValues={{ email: "", password: "" }}
      onSubmit={(values) => { /* submit handler */ }}
      validate={(values)=>{/* validation handler */}} >
          {(props) => {
            return (
              <form className="form-horizontal" onSubmit={props.handleSubmit}>
                /* form elements come here */
              </form>
            );
     }}
</Formik>
Enter fullscreen mode Exit fullscreen mode

With the above minimal configuration, Formik will setup its own state, so we no need to maintain state and handle it. It will take care of. 😎

Validation and Error messages

Validation in Formik will be executed automatically at 3 phases - input change, focus out of an particular input field and when user submits the form. Don't worry about them. All wee need to do is, simply pass a plain JavaScript function to Fomik's validate prop.

Compare validation in vanilla React vs Formik

// vanilla React validation code
const handleChange = (e) => {
    const target = e.target;
    const {
        name,
        value
    } = target;
    let prevFormData = {
        ...formData
    };
    let error = validateFormField(name, value);
    prevFormData[name] = {
        ...prevFormData[name],
        value: value.trim(),
        touched: true,
        error
    };
    setFormData(prevFormData);
};
const validateFormField = (name, value) => {
    let error = "";
    if (name === "email") {
        if (!value) {
            error = "email is mandatory";
        }
        if (value) {
            if (!/^\w+([\\.-]?\w+)*@\w+([\\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
                error = "give a valid email";
            }
        }
    }
    if (name === "password") {
        if (!value) {
            error = "password is mandatory";
        }
        if (value) {
            if (value.length < 3) {
                error = "password should be atleast 3 characters";
            }
            if (value.length > 10) {
                error = "password should not exceed 10 characters";
            }
        }
    }
    return error;
};

// Formik Validation
validate = {values => {
        let errors = {};
        if (!values.hasOwnProperty('email')) {
            error = "email is mandatory";
        } else {
            if (!/^\w+([\\.-]?\w+)*@\w+([\\.-]?\w+)*(\.\w{2,3})+$/.test(values.email)) {
                error = "give a valid email";
            }
        }
        if (!values.hasOwnProperty('password')) {
            error = "password is mandatory";
        }else {
            if (values.email < 3) {
                error = "password should be atleast 3 characters";
            }
            if (values.email > 10) {
                error = "password should not exceed 10 characters";
            }
        }
        return errors;
    }
}

Enter fullscreen mode Exit fullscreen mode

With the validation in place, now we need to output the error messages as our own way.

Creating a React hook for validation.

Here, In the above example we have used one React hook - useValidator. We extracted the validation logic and put it in one hook 😎

const useValidator = (validationsInfo) => {
  return function (values) {
    let errors = {};
    for (let key in validationsInfo) {
      let fieldValitions = validationsInfo[key];
      let fieldValue = values[key];
      for (let validationObejct of fieldValitions) {
        let { type, value, message } = validationObejct;
        let isValid = true;
        switch (type) {
          case "required":
            if (value === true) {
              if (!fieldValue && fieldValue !== 0) {
                isValid = false;
              }
            }
            break;
          case "regex":
            try {
              const validationRegex = new RegExp(value, "g");
              isValid = validationRegex.test(fieldValue) === true;
            } catch (error) {}
            break;
          case "minlength":
            if (fieldValue.length < value) {
              isValid = false;
            }
            break;
          case "maxlength":
            if (fieldValue.length > value) {
              isValid = false;
            }
            break;
          case "exactlength":
            if (fieldValue.length !== value) {
              isValid = false;
              break;
            }
            break;
          case "minvalue":
            fieldValue = +fieldValue;
            if (fieldValue < value) {
              isValid = false;
            }
            break;
          case "maxvalue":
            fieldValue = +fieldValue;
            if (fieldValue > value) {
              isValid = false;
            }
            break;
          case "custom":
            try {
              if (typeof value === "function") {
                isValid = value(fieldValue) === true;
              }
            } catch (error) {}
            break;
          default:
            break;
        }
        if (!isValid) {
          errors[key] = message;
          break;
        }
      }
    }
    return errors;
  };
};

export default useValidator;

Enter fullscreen mode Exit fullscreen mode

How to use useValidator

We need to pass JavaScript object with the validations -Regex, minlength, maxlength etc. Even we can execute a function using custom.

const validator = useValidator({
    email: [
      {
        type: "required",
        value: true,
        message: "email is required"
      },
      {
        type: "regex",
        value:
          "^([a-zA-Z0-9_\\.\\-])+\\@(([a-zA-Z0-9\\-])+\\.)+([a-zA-Z0-9]{2,4})+$",
        message: "enter valid email"
      }
    ],
    password: [
      { type: "required", value: true, message: "password is required" },
      {
        type: "minlength",
        value: 2,
        message: "password should be minimum two length"
      },
      {
        type: "custom",
        value: function (val) {
          if (typeof val !== "string") {
            val = String(val);
          }
          if (val.length > 10) {
            return false;
          }
          return true;
        },
        message: "password should not exceed 10 characters"
      }
    ]
  });
Enter fullscreen mode Exit fullscreen mode

The useValidator hook will return a function, that we need to pass it to Formik with validate prop.

Conclusion

Building forms is one of those things that React isn’t good at. Luckily, React has a community of developers that help each other and make the process of writing code easier.

Formik is definitely one of those open source libraries that’s a must-have if you are writing many forms in your React application.

That is it. Keep Learning.

Top comments (0)

Classic DEV Post:

Visualizing Promises and Async/Await 🀯

async await