How to validate form on submit click with custom reusable hook?

Viewed 52

I'm trying to make a custom reusable useForm hook with inputs validation. I want form to be validated on Submit and clear validation when user typing something in the field. Actually the problem with validation. I do not understand how to implement it.

Hook accepts inital state for inputs which is an object with keys and values as empty strings:

{
    "name": "",
    "description": ""
}

Hooks also accept a callback function which is actually a submitHandler which fires on submit click but do nothing at the moment.

I have a submit function in useForm hook which expected to fire when a form is valid. To check the form validity I loop through inputs calling validator function on each input. If input is not valid I set an error for that field.

But its not clear what to do next. If I console.log(error) in submit function right after forEach loop after first submit button click its an empty object (on second button click it has errors but not for the first click).

May I ask for help tp deal with problem? How to make this validation work correctly?

Submit function in useForm hook:

  const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    Object.entries(input).forEach(([field, value]) => {
      validator(field, value)
    })
    console.log(error) // it's an empty object on first submit button click
    callback();
  };

Validator function:

const validator = (field: keyof Error, value: Input['key']) => {
    switch (field) {
      case 'name':
        if (value.length <= 2) {
          setErrors((prev) => ({
            ...prev,
            [field]: 'Min 2 characters for name field',
          }));
        }
        break;

      case 'description':
        if (value.length <= 2) {
          setErrors((prev) => ({
            ...prev,
            [field]: 'Min 2 characters for description field',
          }));
        }
        break;

      default:
        break;
    }
  };

Full hook code:

import { useState } from 'react';

type Input = {
  [key: string]: string;
};

type Error = {
  [key: string]: string | undefined;
};

const useForm = (initValue: Input, callback: () => void) => {
  const [input, setInput] = useState(initValue);
  const [errors, setErrors] = useState<Error>({});

  const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    const { name, value } = e.target;

    clearValidation(name);

    setInput((prevState) => ({
      ...prevState,
      [name]: value,
    }));
  };

  const resetForm = () => {
    setInput({});
  };

  const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    Object.entries(input).forEach(([field, value]) => {
      validator(field, value);
    });

    callback();
  };

  const validator = (field: keyof Error, value: Input['key']) => {
    switch (field) {
      case 'name':
        if (value.length <= 2) {
          setErrors((prev) => ({
            ...prev,
            [field]: 'Min 2 characters for name field',
          }));
        }
        break;

      case 'description':
        if (value.length <= 2) {
          setErrors((prev) => ({
            ...prev,
            [field]: 'Min 2 characters for description field',
          }));
        }
        break;

      default:
        break;
    }
  };

  const clearValidation = (field: keyof Error) => {
    if (field) {
      setErrors((prevState) => ({
        ...prevState,
        [field]: undefined,
      }));
      return;
    }
    setErrors({});
  };

  return { input, errors, setInput, handleChange, resetForm, submit };
};

export default useForm;
1 Answers
Related