Source

hooks/useForm.js

import { useState } from "react";

/**
 * Custom hook to manage form state and validation
 * @param {Object} options - options object
 *
 * @category Hooks
 * @returns An object with the data, errors and functions: handleChange, handleSubmit, handleValidation.
 */
export const useForm = (options) => {
	const [data, setData] = useState(options?.initialValues || {});
	const [errors, setErrors] = useState({});

	/**
	 * It takes a key and a sanitize function as arguments and returns a function that takes an event as an
	 * argument and sets the data state with the event value
	 */
	const handleChange = (key, sanitizeFn) => (event) => {
		if (event !== undefined && key) {
			// If event as an target value, value is the target value else value is the event value.
			const value = event.target ? event.target.value : event;
			// If the sanitize function is passed, then sanitize the value.
			const sanitizeValue = sanitizeFn ? sanitizeFn(value) : value;
			setData({
				...data,
				[key]: sanitizeValue,
			});

			//

			// Creating a new object with the same properties as the errors, then deleting the property
			// with the key that was passed in, and then set the errors state. */
			const newErrors = { ...errors };
			delete newErrors[key];
			setErrors(newErrors);
		}
	};

	/**
	 * It takes an event and a key, and if the event is not undefined and the key is valid, it will check
	 * if the value of the event is valid based on the validations object
	 * @returns An object with the key of the field that has an error and the error message.
	 */
	const handleValidation = (event, key) => {
		if (event !== undefined && key) {
			const value = event.target ? event.target.value : event;
			const validations = options?.validations;
			if (validations) {
				const validation = validations[key];
				const pattern = validation?.pattern;
				const custom = validation?.custom;
				const newErrors = { ...errors };

				if (validation?.required?.value && !value) {
					newErrors[key] = validation?.required?.message;
				} else if (pattern?.value && !RegExp(pattern.value).test(value)) {
					newErrors[key] = pattern.message;
				} else if (custom?.isValid && !custom.isValid(value)) {
					newErrors[key] = custom.message;
				} else delete newErrors[key];

				setErrors(newErrors);
				return newErrors;
			}
		}
	};

	/**
	 * Checks if there are validations, if true, it loops through the validations and calls the handleValidation function,
	 * Then sets the errors state if there are errors,
	 * Otherwise it sets the errors state to an empty object and calls the onSubmit function if it exists
	 */
	const handleSubmit = (event) => {
		if (event) {
			event.preventDefault();
		}
		const validations = options?.validations;
		if (validations) {
			let newErrors = {};
			for (const key in validations) {
				const error = handleValidation(data[key], key);
				Object.assign(newErrors, error);
			}

			if (Object.keys(newErrors).length > 0) {
				setErrors(newErrors);
				return;
			}
		}

		setErrors({});

		if (options?.onSubmit) {
			options.onSubmit();
		}
	};

	return {
		data,
		handleChange,
		handleSubmit,
		handleValidation,
		errors,
	};
};