# Frontend — Patterns : Forms > Extrait de la base de connaissance Lead_tech. Voir `knowledge/frontend/patterns/README.md` pour l'index complet. --- ## Pattern : Formulaire robuste avec validation et erreurs explicites ### Synthèse - **Objectif** : garantir des formulaires fiables, compréhensibles et maintenables. - **Contexte** : toute interface avec saisie utilisateur et règles métier. - **Quand l'utiliser** : dès qu'un formulaire dépasse un simple champ isolé. - **Quand l'éviter** : formulaires ultra-simples sans validation réelle. ### Analyse - **Avantages** : - UX claire (l'utilisateur sait quoi corriger) - Moins d'erreurs silencieuses - Base saine pour tests et accessibilité - **Limites / vigilance** : - Peut sembler verbeux sans discipline - Risque de duplication si mal factorisé ### Validation - Validé le : 25-01-2026 - Contexte technique : Front-end agnostique, API HTTP ### Implémentation (exemple minimal) ```txt - Validation côté client (format, champs requis) - Validation côté serveur (règles métier) - Mapping explicite des erreurs serveur → champs UI - Aucun submit silencieux ``` ### Checklist - [ ] Messages d'erreur compréhensibles et localisés - [ ] Validation client + serveur cohérente - [ ] Focus automatique sur le champ en erreur - [ ] États loading / disabled gérés - [ ] Tests sur cas valides et invalides --- ## Pattern : Toggle optimiste avec rollback (React Server Action) ### Synthèse - **Objectif** : masquer la latence serveur sur un toggle boolean en mettant à jour l'UI immédiatement, avec rollback en cas d'erreur. - **Contexte** : toggles boolean (visibilité, disponibilité, settings) où la latence doit être masquée. - **Quand l'utiliser** : toggles sans besoin de re-fetcher l'entité entière après mutation. - **Quand l'éviter** : mutations qui retournent des données complexes → préférer le pattern "Server Action retournant l'entité". ### Validation - Validé le : 21-03-2026 - Contexte technique : React / Next.js App Router — app-template-resto ### Implémentation ```tsx const [optimistic, setOptimistic] = useState(initialValue); async function handleToggle() { const prev = optimistic; setOptimistic(!prev); // update immédiat try { await toggleAction(!prev); router.refresh(); // synchronise le Server Component parent } catch { setOptimistic(prev); // rollback si erreur } } ``` --- ## Pattern : Server Action retournant l'entité — élimination de `router.refresh()` sur create/edit ### Synthèse - **Objectif** : mettre à jour l'état local directement avec les données réelles retournées par le serveur, sans round-trip SSR supplémentaire. - **Contexte** : liste d'items managée côté client (`useState`) avec création et modification via Server Actions. - **Quand l'utiliser** : create et edit d'entités dans une liste. Plus performant que toggle optimiste + `router.refresh()`. - **Quand l'éviter** : simples toggles boolean → le pattern optimiste avec rollback suffit. ### Analyse - **Avantages vs toggle optimiste + `router.refresh()` :** - Zéro aller-retour SSR supplémentaire (~500ms–2s économisés sur mobile) - État local garanti cohérent avec la DB (données réelles, pas calculées localement) - Pas de flash de rechargement - **Limites / vigilance** : - `revalidatePath` reste nécessaire pour invalider le cache des pages publiques ### Validation - Validé le : 22-03-2026 - Contexte technique : React / Next.js App Router — app-template-resto story 3.8 ### Implémentation ```typescript // Repository — retourne l'entité complète export async function createItem(tenantId: string, data: Input): Promise { return prisma.item.create({ data: { tenantId, ...data }, select: { ...fullSelect } }); } // Action — retourne la donnée au client export async function createItemAction(formData: FormData): Promise { const actor = await requireOwner(); const item = await createItem(actor.tenantId, input); revalidatePath("/dashboard/..."); // invalider cache pages publiques return item; // ← clé : retourner l'entité } // Client — mise à jour locale sans round-trip SSR const created = await createItemAction(formData); setItems((prev) => [...prev, created]); // pas de router.refresh() ``` **Pour les entités avec relations :** utiliser un helper `findItemById(tenantId, id)` appelé après la mutation pour retourner la forme complète avec les relations résolues.