the-tip-top-frontend/hooks/useForm.ts
2025-11-17 23:38:02 +01:00

150 lines
3.4 KiB
TypeScript

import { useState, ChangeEvent, FormEvent } from 'react';
interface UseFormOptions<T> {
initialValues: T;
onSubmit: (values: T) => void | Promise<void>;
validate?: (values: T) => Partial<Record<keyof T, string>>;
}
/**
* Hook personnalisé pour gérer les formulaires
* @param options Configuration du formulaire (initialValues, onSubmit, validate)
* @returns Méthodes et états pour gérer le formulaire
*/
export function useForm<T extends Record<string, any>>({
initialValues,
onSubmit,
validate,
}: UseFormOptions<T>) {
const [values, setValues] = useState<T>(initialValues);
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
/**
* Gère le changement de valeur d'un champ
*/
const handleChange = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => {
const { name, value, type } = e.target;
const fieldValue =
type === 'checkbox' ? (e.target as HTMLInputElement).checked : value;
setValues((prev) => ({
...prev,
[name]: fieldValue,
}));
// Valider le champ si une fonction de validation est fournie
if (validate && touched[name as keyof T]) {
const validationErrors = validate({
...values,
[name]: fieldValue,
});
setErrors((prev) => ({
...prev,
[name]: validationErrors[name as keyof T],
}));
}
};
/**
* Marque un champ comme touché (pour afficher les erreurs)
*/
const handleBlur = (
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
) => {
const { name } = e.target;
setTouched((prev) => ({
...prev,
[name]: true,
}));
// Valider le champ au blur
if (validate) {
const validationErrors = validate(values);
setErrors((prev) => ({
...prev,
[name]: validationErrors[name as keyof T],
}));
}
};
/**
* Gère la soumission du formulaire
*/
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Marquer tous les champs comme touchés
const allTouched = Object.keys(values).reduce(
(acc, key) => ({ ...acc, [key]: true }),
{}
);
setTouched(allTouched);
// Valider tous les champs
if (validate) {
const validationErrors = validate(values);
setErrors(validationErrors);
// Ne pas soumettre si des erreurs existent
if (Object.keys(validationErrors).length > 0) {
return;
}
}
// Soumettre le formulaire
setIsSubmitting(true);
try {
await onSubmit(values);
} finally {
setIsSubmitting(false);
}
};
/**
* Réinitialise le formulaire
*/
const reset = () => {
setValues(initialValues);
setErrors({});
setTouched({});
setIsSubmitting(false);
};
/**
* Définit manuellement une valeur
*/
const setValue = (name: keyof T, value: any) => {
setValues((prev) => ({
...prev,
[name]: value,
}));
};
/**
* Définit manuellement une erreur
*/
const setError = (name: keyof T, error: string) => {
setErrors((prev) => ({
...prev,
[name]: error,
}));
};
return {
values,
errors,
touched,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
reset,
setValue,
setError,
};
}