150 lines
3.4 KiB
TypeScript
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,
|
|
};
|
|
}
|