Home > Software engineering >  How to validate form on submit click with custom reusable hook?
How to validate form on submit click with custom reusable hook?

Time:07-24

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;

CodePudding user response:

The reason the console.log is empty is because react state setting is asynchronous. It's probably setting it fine, it just happens shortly after the console.log because of how React batches state updates and runs them later seamlessly.

I would suggest logging it in the render method or something.

See here for more details: https://beta.reactjs.org/apis/usestate#ive-updated-the-state-but-logging-gives-me-the-old-value

  • Related