DEV Community

Cover image for The Power of Custom React Hooks: Building a Form with Validations
Mattias Velamsson
Mattias Velamsson

Posted on

The Power of Custom React Hooks: Building a Form with Validations

As I've been self studying React.js and recently learned about Custom Hooks, I decided it was time for a post about it!

In this example, I'll be showing step-by-step how to create some simple form validation, with error handling using Custom Hooks.


I will be working with three files. App.js, Form.js and input-hook.js

Setup

Project Structure:
📦src
┣ 📂components
┃ ┣ 📜Form.js
┣ 📂hooks
┃ ┣ 📜input-hook.js
┣ 📜App.js
┣ 📜index.css
┗ 📜index.js


App.js

Here we'll just render the form, nothing else.

import Form from "./components/Form";

function App() {
  return (
    <div className="app">
      <Form />
    </div>
  );
}


export default App;
Enter fullscreen mode Exit fullscreen mode

input-hook.js

import React, { useState } from "react";

const useInput = (validateValue) => {
  const [inputValue, setInputValue] = useState("");
  const [inputTouched, setInputTouched] = useState(false);

  const isValid = validateValue(inputValue);
  const hasError = !isValid && inputTouched;

  const inputChangeHandler = (e) => {
    setInputValue(e.target.value);
  };

  const inputBlurHandler = (e) => {
    setInputTouched(true);
  };

  const reset = () => {
    setInputValue("");
    setInputTouched(false)
  };

  return {
    value: inputValue,
    isValid,
    hasError,
    inputChangeHandler,
    inputBlurHandler,
    reset,
  };
};

export default useInput;
Enter fullscreen mode Exit fullscreen mode

First, we import useState, as we will need it in our custom hook.

The useInput custom component starts by using the useState hook to create three state variables: value, isInputValid, and hasError. We also included a argument validationFn, which is where we will pass the logic for how to validate the field, from our Form.js.

value

  • Keep track of what the user is typing isInputValid
  • Checks validity of whatever is typed hasError
  • A Boolean tracking both if the user started typing, and if what they typed is valid or not.

The first one inputChangeHandler is passed to the input's onChange prop. It updates the value state by checking the input's value.

The second one inputBlurHandler is passed to the input's onBlur prop. The handler checks if the user started typing or not.

The third one reset is the function we use to reset the form to it's initial state.

Finally, we return all the properties we need to use in our Form.js as an object.

Form.js

Form Structure

  return (
    <form onSubmit={submitHandler}>
      <div className="control-group">
        <div className={nameInputClasses}>
          <label htmlFor="name">First Name</label>
          <input
            value={nameInputValue}
            onBlur={nameBlurHandler}
            onChange={nameChangeHandler}
            type="text"
            id="name"
          />
          {nameHasError && <p className="error-text">Enter a name.</p>}
        </div>
        <div className={lastNameInputClasses}>
          <label htmlFor="lastname">Last Name</label>
          <input
            value={lastNameInputValue}
            onChange={lastNameChangeHandler}
            onBlur={lastNameBlurHandler}
            type="text"
            id="lastname"
          />
          {lastNameHasError && <p className="error-text">Enter a last name.</p>}
        </div>
      </div>
      <div className={emailInputClasses}>
        <label htmlFor="email">E-Mail Address</label>
        <input
          value={emailInputValue}
          onChange={emailChangeHandler}
          onBlur={emailBlurHandler}
          type="text"
          id="email"
        />
        {emailHasError && <p className="error-text">Enter a real email.</p>}
      </div>
      <button type="submit" disabled={!formIsValid}>
        Submit
      </button>
    </form>
  );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

In this component, there will be a lot going on. I'm going to break it down piece by piece.

First, we import the necessary dependencies: useInput hook, React, and useEffect and useState.

import useInput from "../hooks/input-hook";
import React, { useEffect, useState } from "react";
Enter fullscreen mode Exit fullscreen mode

We then use the useState hook to create a state variable formIsValid which we set to false initially. It is used to check if our form is ready to be submitted or not.

const [formIsValid, setFormIsValid] = useState(false);
Enter fullscreen mode Exit fullscreen mode


`

Next we are using our useInput Custom Hook to create three new objects. One which checks name, lastName and lastly email.

In our useInput's argument, we pass a anonymous function. In this situation, we use that as an opportunity to check the input.

For the name fields, I make sure it's not empty, and for the email field I use RegEx to make sure it's a valid email format(it's not perfect, but works).

`

  const {
    value: nameInputValue,
    isInputValid: isNameValid,
    hasError: nameHasError,
    inputChangeHandler: nameChangeHandler,
    inputBlurHandler: nameBlurHandler,
    reset: nameReset,
  } = useInput((name) => name.trim() !== "");

  const {
    value: lastNameInputValue,
    isInputValid: isLastNameValid,
    hasError: lastNameHasError,
    inputChangeHandler: lastNameChangeHandler,
    inputBlurHandler: lastNameBlurHandler,
    reset: lastNameReset,
  } = useInput((lastName) => lastName.trim() !== "");

  const {
    value: emailInputValue,
    isInputValid: isEmailValid,
    hasError: emailHasError,
    inputChangeHandler: emailChangeHandler,
    inputBlurHandler: emailBlurHandler,
    reset: emailReset,
  } = useInput((email) =>
    email.trim().match(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/)
  );
Enter fullscreen mode Exit fullscreen mode

Then, we use useEffect to check if the inputs have a value, and if so set the form validity to true.

  useEffect(() => {
    if (nameInputValue && lastNameInputValue && emailInputValue) {
      setFormIsValid(true);
    } else {
      setFormIsValid(false);
    }
  }, [nameInputValue, lastNameInputValue, emailInputValue]);

Enter fullscreen mode Exit fullscreen mode

Next is the submitHandler which gets called when the form is submitted. Basically what it does is:

  1. Prevents a refresh on submit
  2. Check that the form is valid
  3. If valid, submits and resets the input fields. Otherwise just return(do nothing).
  const submitHandler = (event) => {
    event.preventDefault();

    if (!formIsValid) {
      return;
    }

    nameReset();
    lastNameReset();
    emailReset();
  };

Enter fullscreen mode Exit fullscreen mode

Now, the next step is optional, it's basically playing with CSS to add classes with colors depending on the forms validity.

  const nameInputClasses = nameHasError
    ? "form-control invalid"
    : "form-control";

  const lastNameInputClasses = lastNameHasError
    ? "form-control invalid"
    : "form-control";

  const emailInputClasses = emailHasError
    ? "form-control invalid"
    : "form-control";
Enter fullscreen mode Exit fullscreen mode

Check the objects value and if it has any error, change class to form-control invalid.

And that's it!


Here's an example of the result:

Image description

Top comments (0)