Formulaires
-
Utilisez React Hook Form avec Zod pour la validation des formulaires, en ajoutant d’abord
react-hook-form,@hookform/resolversetzodaux dépendances du package si le module ne les déclare pas déjà. Cette combinaison offre une validation type-safe et d’excellentes performances :import { useForm, Controller } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; // Définir le schéma de validation const formSchema = z.object({ name: z.string({ required_error: t('nameRequired', 'Le nom est requis'), }).min(1, t('nameRequired', 'Le nom est requis')), email: z.string().email(t('invalidEmail', 'Adresse e-mail invalide')), age: z.number().min(18, t('ageMustBe18', 'L'âge doit être d'au moins 18 ans')), password: z.string().min(8, t('passwordTooShort', 'Le mot de passe doit contenir au moins 8 caractères')), confirmPassword: z.string(), }).refine((data) => data.password === data.confirmPassword, { message: t('passwordsDoNotMatch', 'Les mots de passe ne correspondent pas'), path: ['confirmPassword'], }); // Utiliser dans le composant const { handleSubmit, control, formState: { errors }, } = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: '', email: '', age: 18, password: '', confirmPassword: '', }, }); const onSubmit = async (data: z.infer<typeof formSchema>) => { try { await saveData(data); showSnackbar({ title: t('saved', 'Enregistré avec succès') }); } catch (error) { showSnackbar({ kind: 'error', title: t('errorSaving', 'Erreur lors de l'enregistrement'), subtitle: error instanceof Error ? error.message : t('unknownError', 'Erreur inconnue'), }); } }; -
Utilisez
Controllerde React Hook Form lors de l’intégration avec les composants Carbon qui ne suivent pas les modèles d’entrée HTML standard :<Controller name="status" control={control} render={({ field: { onBlur, onChange, value } }) => ( <ComboBox id="status" items={statusOptions} itemToString={(item) => item?.label ?? ''} onBlur={onBlur} onChange={({ selectedItem }) => onChange(selectedItem)} selectedItem={value} titleText={t('status', 'Statut')} /> )} /> -
Gérez les erreurs de soumission de formulaire de manière appropriée. Affichez les erreurs de validation inline et montrez des snackbars pour les erreurs serveur :
const onSubmit = async (data: FormData) => { try { setIsSubmitting(true); await saveFormData(data); await closeWorkspace({ discardUnsavedChanges: true }); showSnackbar({ title: t('formSaved', 'Formulaire enregistré avec succès') }); } catch (error) { // Erreurs serveur affichées via snackbar showSnackbar({ kind: 'error', title: t('errorSavingForm', 'Erreur lors de l'enregistrement du formulaire'), subtitle: error instanceof Error ? error.message : t('unknownError', 'Erreur inconnue'), }); } finally { setIsSubmitting(false); } }; -
Utilisez les états de chargement pendant la soumission du formulaire pour éviter les soumissions en double :
const [isSubmitting, setIsSubmitting] = useState(false); <Button type="submit" disabled={isSubmitting} > {isSubmitting ? <InlineLoading description={t('saving', 'Enregistrement...')} /> : t('save', 'Enregistrer')} </Button> -
Lors de la création de schémas Zod qui incluent des messages d’erreur traduits, créez le schéma en utilisant une fonction qui accepte la fonction de traduction comme paramètre. Cela garantit que les schémas sont correctement localisés et peuvent être mémorisés :
import type { TFunction } from 'i18next'; // Bon - fonction factory de schéma const createFormSchema = (t: TFunction) => z.object({ name: z.string().min(1, t('nameRequired', 'Le nom est requis')), email: z.string().email(t('invalidEmail', 'Adresse e-mail invalide')), }); // Utiliser dans le composant avec useMemo const { t } = useTranslation(); const formSchema = useMemo(() => createFormSchema(t), [t]); const { control, handleSubmit } = useForm({ resolver: zodResolver(formSchema), }); -
Pour les formulaires Workspace v2 dans le dossier patient, utilisez
PatientWorkspace2DefinitionPropsde@openmrs/esm-patient-common-libet enveloppez le contenu du formulaire dansWorkspace2:import { Workspace2 } from '@openmrs/esm-framework'; import { type PatientWorkspace2DefinitionProps } from '@openmrs/esm-patient-common-lib'; interface MyFormProps { encounterUuid?: string; } export default function MyForm({ closeWorkspace, workspaceProps, groupProps, }: PatientWorkspace2DefinitionProps<MyFormProps, object>) { const patientUuid = groupProps?.patientUuid; const encounterUuid = workspaceProps?.encounterUuid; const { formState: { isDirty }, } = useForm(); return ( <Workspace2 title={t('myForm', 'Mon formulaire')} hasUnsavedChanges={isDirty}> {/* Utilisez patientUuid et encounterUuid pour afficher le formulaire */} </Workspace2> ); } -
Utilisez la prop
hasUnsavedChangesdeWorkspace2pour avertir les utilisateurs lorsqu’ils tentent de fermer un formulaire d’espace de travail avec des modifications non enregistrées :const { formState: { isDirty }, } = useForm(...); return ( <Workspace2 title={t('editItem', "Modifier l'élément")} hasUnsavedChanges={isDirty}> {/* Contenu du formulaire */} </Workspace2> ); -
Utilisez
closeWorkspace({ discardUnsavedChanges: true })après une soumission réussie, etcloseWorkspace()lorsque l’utilisateur annule :const onSubmit = async (data: FormData) => { try { await saveData(data); await mutate(); // Mettre à jour le cache SWR await closeWorkspace({ discardUnsavedChanges: true }); showSnackbar({ title: t('saved', 'Enregistré avec succès') }); } catch (error) { showSnackbar({ kind: 'error', title: t('errorSaving', 'Erreur lors de l'enregistrement'), subtitle: error instanceof Error ? error.message : t('unknownError', 'Erreur inconnue'), }); } }; // Bouton d'annulation <Button kind="secondary" onClick={() => closeWorkspace()}> {t('cancel', 'Annuler')} </Button> -
Affichez les erreurs au niveau du formulaire en utilisant le composant
InlineNotificationde Carbon. Envisagez des états d’erreur séparés pour les opérations de création vs mise à jour :const [errorCreating, setErrorCreating] = useState<Error | null>(null); const [errorUpdating, setErrorUpdating] = useState<Error | null>(null); {errorCreating && ( <InlineNotification role="alert" kind="error" lowContrast title={t('errorCreating', 'Erreur lors de la création')} subtitle={errorCreating.message} /> )} {errorUpdating && ( <InlineNotification role="alert" kind="error" lowContrast title={t('errorUpdating', 'Erreur lors de la mise à jour')} subtitle={errorUpdating.message} /> )}