Formulaire
Composant composite formé de LhForm + LhFormItem, offrant la validation des données, l'affichage des erreurs et l'alignement des libellés.
Utilisation de base
import { useRef, useState } from 'react'
import { LhButton, LhForm, LhFormItem, LhInput, LhSelect } from '@lh-ui/react'
import type { LhFormInstance, LhFormRule } from '@lh-ui/react'
interface FormData {
name: string
style: string
}
const rules: Record<string, LhFormRule[]> = {
name: [{ required: true, message: 'Veuillez saisir le nom de l’œuvre', trigger: 'blur' }],
style: [{ required: true, message: 'Veuillez choisir un style', trigger: 'change' }],
}
export default function Demo() {
const [formData, setFormData] = useState<FormData>({ name: '', style: '' })
const formRef = useRef<LhFormInstance>(null)
const handleSubmit = async () => {
const valid = await formRef.current?.validate()
if (valid) alert(`Envoi réussi : ${formData.name} / ${formData.style}`)
}
const handleReset = () => {
formRef.current?.resetFields()
setFormData({ name: '', style: '' })
}
return (
<LhForm ref={formRef} model={formData} rules={rules} labelWidth="80px">
<LhFormItem label="Nom de l'œuvre" prop="name">
<LhInput
value={formData.name}
onChange={(val) => setFormData((prev) => ({ ...prev, name: val }))}
/>
</LhFormItem>
<LhFormItem label="Style" prop="style">
<LhSelect
value={formData.style}
onChange={(val) => setFormData((prev) => ({ ...prev, style: val as string }))}
options={[{ label: 'Encre', value: 'ink' }]}
/>
</LhFormItem>
<LhFormItem>
<LhButton type="primary" onClick={handleSubmit}>Envoyer</LhButton>
<LhButton onClick={handleReset}>Réinitialiser</LhButton>
</LhFormItem>
</LhForm>
)
}
Form Props
| Prop | Description | Type | Valeur par défaut |
|---|---|---|---|
| model | Objet de données du formulaire (requis) | Record<string, unknown> | — |
| rules | Règles de validation, indexées par nom de champ | Record<string, LhFormRule[]> | — |
| labelWidth | Largeur des libellés | string | number | — |
| labelPosition | Position des libellés | 'left' | 'right' | 'top' | 'right' |
| inline | Mise en page en ligne | boolean | false |
| disabled | Désactive tous les enfants | boolean | false |
| size | Taille des enfants | 'large' | 'middle' | 'small' | Selon ConfigProvider |
| onSubmit | Événement natif de soumission (preventDefault déjà appliqué) | (event: FormEvent) => void | — |
FormItem Props
| Prop | Description | Type | Valeur par défaut |
|---|---|---|---|
| label | Texte du libellé | string | — |
| prop | Nom du champ (chemins pointillés pris en charge, en correspondance avec une clé de model) | string | — |
| rules | Règles de niveau item (priment sur les règles de niveau form de même nom) | LhFormRule[] | — |
| required | Astérisque requis (ne rend que le marqueur ; la sémantique de validation vient de rules) | boolean | false |
| labelWidth | Prime sur la largeur des libellés de niveau form | string | number | — |
| errorMessage | Message d'erreur personnalisé | string | — |
| showMessage | Afficher ou non le message d'erreur | boolean | true |
LhFormRule
| Prop | Description | Type |
|---|---|---|
| required | Champ requis ou non | boolean |
| message | Message en cas d'échec de la validation | string |
| trigger | Mode de déclenchement | 'blur' | 'change' |
| validator | Fonction de validation personnalisée (peut renvoyer une Promise ; la validation asynchrone est correctement attendue) | (value, model) => string | undefined | Promise<string | undefined> |
| min / max | Valeur minimale / maximale (nombre ou longueur) | number |
| pattern | Validation par expression régulière | RegExp |
Méthodes exposées par le ref de Form
LhFormInstance (exposé via forwardRef + useImperativeHandle) :
| Méthode | Description | Valeur de retour |
|---|---|---|
| validate | Valide tous les champs | Promise<boolean> |
| validateField | Valide un seul champ | Promise<boolean> |
| resetFields | Réinitialise tous les champs à leurs valeurs initiales | void |
| resetField | Réinitialise un seul champ | void |
const formRef = useRef<LhFormInstance>(null)
const ok = await formRef.current?.validate()
Cette bibliothèque corrige le défaut de validation asynchrone de plus : le validateField de plus utilise Promise.resolve(!message) sans attendre la Promise du validateur asynchrone, laquelle renvoie donc toujours true. La version React attend explicitement la thenable avant de conclure. Voir le guide de migration.
LhFormItem enregistre son validateur auprès du formulaire au sein de useEffect et se désenregistre automatiquement au démontage. Les champs n'ont pas besoin d'être déclarés à l'avance dans rules — un champ présent dans model mais absent de rules voit sa valeur initiale récupérée au moment de l'enregistrement.
La fonction validateFormField de share n'interprète pas trigger ; le moment du déclenchement est câblé par cette couche : trigger: 'blur' (par défaut)
valide lorsque le FormItem perd le focus, et trigger: 'change' valide lorsque la zone de contenu émet un change. Une règle sans trigger
ne s'exécute qu'à la perte de focus et lors de validate(), afin d'éviter l'apparition répétée des erreurs pendant la saisie.
La validation sur change lit la nouvelle valeur de la cible de l'événement plutôt qu'un instantané de model — les mises à jour d'état de React et la distribution des événements
ne tombent pas dans le même cycle, et lire le modèle en retour serait en retard d'un cycle. Les événements change des contrôles natifs (input / textarea / select natif)
sont capturés à la remontée vers la zone de contenu ; les contrôles personnalisés n'ont pas d'événement change natif (un clic sur une option de LhSelect ne produit qu'un click),
et déclenchent donc la validation au moyen de validateChange(value) du contexte du FormItem, en transmettant la nouvelle valeur.
Outre la restauration de model à l'instantané des valeurs initiales, resetFields() incrémente un resetVersion interne ; chaque FormItem
observe ce changement de version et efface son propre état d'erreur. L'état contrôlé externe doit toujours être synchronisé manuellement (comme
setFormData(initial) dans la démo) — React ne peut pas écrire le modèle à votre place, à la différence de la chaîne réactive de Vue.
Styles
import '@lh-ui/react/style/form'