Files
_Assistant_Lead_Tech/10_frontend_patterns_valides.md
2026-03-20 13:59:42 +01:00

637 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Patterns front-end validés
Ce fichier contient **uniquement** des patterns front-end :
- testés,
- validés,
- utilisés dans des projets réels (ou des apps complètes, pas des snippets isolés).
Il sert de **mémoire durable** pour éviter :
- de refaire les mêmes erreurs,
- de redélibérer éternellement sur des sujets déjà tranchés,
- de propager des “bonnes pratiques” théoriques non éprouvées.
Dernière mise à jour : 20-03-2026
---
## Index
- [Gestion explicite des états UI (loading / empty / error)](#pattern-etats-ui-loading-empty-error)
- [Séparation claire server state / client state](#pattern-separation-server-state-client-state)
- [Formulaire robuste avec validation et erreurs explicites](#pattern-formulaire-robuste)
- [Navigation réactive post-action async (React / Expo Router)](#pattern-navigation-reactive-post-action-async)
- [Refresh idempotent sur store de liste paginée](#pattern-refresh-idempotent-liste-paginee)
- [UI admin légère sur domaine existant](#pattern-ui-admin-legere-domaine-existant)
- [Intégration tierce en mode link-out — préférer une page locale canonique](#pattern-link-out-page-locale-canonique)
- [Design Tokens natifs TypeScript (Expo / React Native)](#pattern-design-tokens-expo-rn)
- [Tests de styles React Native sans renderer JSX](#pattern-tests-styles-sans-renderer)
- [Export des styles de composant pour réutilisation partielle](#pattern-export-styles-composant)
- [Token typography par usage sémantique (React Native)](#pattern-token-typography-semantique)
---
## Règle dor
Si ce nest pas **confirmé comme fonctionnel et utile**,
**ça na rien à faire ici**.
- Pas de conseils vagues
- Pas de patterns “à la mode”
- Pas de dépendance implicite à un framework ou une version non précisée
---
## Périmètre couvert
- SPA et webapps
- UX technique (forms, erreurs, loading, feedback)
- State management (client / server)
- Architecture front-end
- Performance et accessibilité
- Sécurité front (au niveau applicatif)
- DX et maintenabilité
Ce fichier traite le **front-end comme un logiciel en production**,
au même niveau dexigence que le backend.
---
## Format standard dun pattern (obligatoire)
## Pattern : <Nom clair et précis>
- Objectif : ce que le pattern résout
- Contexte : type dapplication / contraintes
- Quand lutiliser : cas pertinents
- Quand léviter : contre-exemples
- Avantages : bénéfices concrets
- Limites / vigilance : pièges, dette potentielle
- Validé le : DD-MM-YYYY
- Contexte technique : framework + version + tooling principal
### Implémentation (exemple minimal)
```txt
(contenu volontairement minimal, lisible, non-magique)
```
---
<a id="pattern-etats-ui-loading-empty-error"></a>
## Pattern : Gestion explicite des états UI (loading / empty / error)
### Synthèse
- **Objectif** : éviter les interfaces ambiguës ou incohérentes en rendant explicites tous les états possibles dune vue.
- **Contexte** : SPA ou webapp consommant des données asynchrones (API, backend, cache).
- **Quand lutiliser** : dès quune vue dépend de données externes ou dun traitement async.
- **Quand léviter** : vues purement statiques ou synchrones sans dépendance externe.
### Analyse
- **Avantages** :
- UX plus prévisible et compréhensible
- Debug facilité (état visible = problème identifiable)
- Base saine pour tests et accessibilité
- **Limites / vigilance** :
- Peut sembler verbeux sur des écrans simples
- Nécessite une discipline pour ne pas “court-circuiter” les états
### Validation
- Validé le : 25-01-2026
- Contexte technique : SPA (React / Vue / Svelte agnostique), API HTTP
### Implémentation (exemple minimal)
```txt
if (loading) {
afficher un skeleton ou spinner
} else if (error) {
afficher un message clair + action possible
} else if (data est vide) {
afficher un état empty explicite
} else {
afficher la vue nominale
}
```
### Checklist
- [ ] Aucun écran blanc ou silencieux
- [ ] Message derreur compréhensible pour lutilisateur
- [ ] États testables individuellement
- [ ] Accessibilité respectée (focus, lecture écran)
- [ ] Pas de logique métier cachée dans le rendu
---
<a id="pattern-separation-server-state-client-state"></a>
## Pattern : Séparation claire server state / client state
### Synthèse
- **Objectif** : éviter le mélange des responsabilités entre données serveur et état local UI.
- **Contexte** : SPA ou webapp consommant une API avec interactions utilisateur.
- **Quand lutiliser** : dès que lapplication affiche des données distantes modifiables ou synchronisées.
- **Quand léviter** : applications très simples ou purement statiques.
### Analyse
- **Avantages** :
- Logique plus lisible et testable
- Réduction des bugs liés aux états incohérents
- Évolutivité facilitée quand lapp grossit
- **Limites / vigilance** :
- Demande de la rigueur dans le découpage
- Peut sembler abstrait au début pour des petits projets
### Validation
- Validé le : 25-01-2026
- Contexte technique : SPA agnostique (React / Vue / Svelte), API HTTP
### Implémentation (exemple minimal)
```txt
serverState = données venant du backend (fetch, cache, sync)
clientState = état local UI (filtres, onglets, modales, formulaires)
Ne jamais :
- stocker du state UI dans le cache serveur
- dériver la logique UI directement des réponses API sans adaptation
```
### Checklist
- [ ] Les données serveur peuvent être invalidées / rechargées
- [ ] Létat UI est local et réinitialisable
- [ ] Les responsabilités sont lisibles dans le code
- [ ] Les tests peuvent cibler chaque type détat
- [ ] Pas de dépendance implicite entre UI et API
---
<a id="pattern-formulaire-robuste"></a>
## 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 lutiliser** : dès quun formulaire dépasse un simple champ isolé.
- **Quand léviter** : formulaires ultra-simples sans validation réelle.
### Analyse
- **Avantages** :
- UX claire (lutilisateur sait quoi corriger)
- Moins derreurs 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 derreur 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
---
<a id="pattern-navigation-reactive-post-action-async"></a>
## Pattern : Navigation réactive post-action async (React / Expo Router)
### Synthèse
- **Objectif** : déclencher la navigation après une action asynchrone (login, register, submit) de façon idiomatique et sans bypasser la réactivité React.
- **Contexte** : SPA ou app mobile React avec state management (Zustand, Redux, Context) et router déclaratif (React Router, Expo Router, Next.js App Router).
- **Quand l'utiliser** : dès qu'une navigation dépend du résultat d'une action async.
- **Quand l'éviter** : navigations synchrones sans état async impliqué.
### Analyse
- **Avantages** :
- Respecte le cycle de vie React (pas de lecture de state hors cycle)
- Re-render automatique si l'état change entre-temps
- Testable : on peut assert sur l'état, pas sur des effets de bord
- **Limites / vigilance** :
- Ne pas oublier les dépendances du `useEffect` (ESLint react-hooks/exhaustive-deps)
- Gérer le cas "composant démonté" si la navigation peut être annulée
### Validation
- Validé le : 07-03-2026
- Contexte technique : React 18+ / Zustand / Expo Router — pattern applicable sur React Router, Next.js App Router
### Implémentation (exemple minimal)
```typescript
// ❌ Anti-pattern : lecture de state hors cycle React
const handleSubmit = async () => {
await login(email, password);
const { accessToken } = useAuthStore.getState(); // bypasse la réactivité
if (accessToken) router.replace('/(tabs)');
};
// ✅ Pattern correct : useEffect réactif sur le state
const { accessToken, isLoading, error } = useAuthStore();
useEffect(() => {
if (accessToken && !isLoading && !error) {
router.replace('/(tabs)');
}
}, [accessToken, isLoading, error]);
const handleSubmit = async () => {
await login(email, password);
// la navigation se déclenche via useEffect quand le store se met à jour
};
```
### Pour les callbacks OAuth (ref nécessaire)
```typescript
// Quand un callback externe déclenche la navigation
const pendingOAuth = useRef(false);
useEffect(() => {
if (pendingOAuth.current && accessToken) {
pendingOAuth.current = false;
router.replace('/(tabs)');
}
}, [accessToken]);
const handleOAuth = async () => {
pendingOAuth.current = true;
await exchangeWithIdp(token);
};
```
### Checklist
- [ ] Aucun `store.getState()` utilisé pour lire l'état post-action dans un handler
- [ ] `useEffect` avec dépendances explicites (state pertinent + isLoading + error)
- [ ] Cas d'erreur géré (ne pas naviguer si error est défini)
- [ ] `useRef` si le trigger vient d'un callback externe (OAuth, deep link)
- [ ] Convention documentée dans la story foundations / project-context avant les premiers écrans
---
<a id="pattern-refresh-idempotent-liste-paginee"></a>
## Pattern : Refresh idempotent sur store de liste paginée
### Synthèse
- **Objectif** : garantir quun pull-to-refresh recharge une liste paginée sans doublons, sans courses réseau et sans état intermédiaire incohérent.
- **Contexte** : app mobile ou SPA avec store de domaine (ex. Zustand) et pagination incrémentale.
- **Quand lutiliser** : dès quune même liste supporte à la fois `loadMore` et `refresh`.
- **Quand léviter** : listes purement statiques ou données entièrement remplacées sans pagination.
### Analyse
- **Avantages** :
- évite les doublons lors des refresh concurrents
- garde une transition atomique entre ancien et nouvel état
- rend le comportement async testable côté store
- **Limites / vigilance** :
- impose une discipline claire entre `refresh` et `loadMore`
- demande une clé didentité stable pour dédupliquer les items
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo / Zustand / listes paginées
### Implémentation (exemple minimal)
```txt
- conserver une promesse de refresh partagée tant quun refresh est en vol
- refuser ou réutiliser tout refresh concurrent au lieu den lancer un second
- remplacer atomiquement la liste à la fin du refresh
- dédupliquer les items par identifiant au merge des pages suivantes
- empêcher `loadMore` de fusionner sur un snapshot devenu obsolète
```
### Checklist
- [ ] Une seule promesse de refresh en vol à la fois
- [ ] `refresh` et `loadMore` ont des garde-fous explicites
- [ ] La liste est remplacée atomiquement après refresh
- [ ] Les pages suivantes sont dédupliquées par identifiant stable
- [ ] Tests sur refresh concurrent + refresh suivi de pagination
---
<a id="pattern-ui-admin-legere-domaine-existant"></a>
## Pattern : UI admin légère sur domaine existant
### Synthèse
- **Objectif** : ajouter une capacité interne simple sans ouvrir trop tôt un back-office séparé ni dupliquer la logique métier.
- **Contexte** : app mobile ou SPA avec un domaine métier déjà structuré et quelques actions internes ponctuelles.
- **Quand lutiliser** : publication, activation, modération légère, bascule de statut, action opérateur simple.
- **Quand léviter** : permissions complexes, workflows multiples, audit riche ou volume dactions qui justifie un vrai espace dadministration.
### Analyse
- **Avantages** :
- réutilise le service et le store métier existants
- limite le coût de structure pour une capacité admin mince
- garde les mutations testables et lisibles
- **Limites / vigilance** :
- ne pas laisser cette approche dériver vers un pseudo back-office implicite
- le refresh après mutation doit être explicite sur les vues impactées
### Validation
- Validé le : 10-03-2026
- Contexte technique : React Native / Expo Router / store de domaine
### Implémentation (exemple minimal)
```txt
- ajouter une route dédiée minimale pour laction interne
- réutiliser le service/store métier existant au lieu de créer une couche parallèle
- afficher le statut courant avant action
- bloquer les actions concurrentes avec un flag explicite (`isUpdating*`)
- déclencher un refresh explicite des vues impactées après succès
- éviter les mutations en fire-and-forget
```
### Checklist
- [ ] Route dédiée minimale, pas de mini back-office générique
- [ ] Réutilisation du domaine métier existant
- [ ] Garde-fou explicite contre les doubles actions
- [ ] Refresh explicite après mutation réussie
- [ ] Tests sur succès, erreur et action concurrente
---
---
<a id="pattern-link-out-page-locale-canonique"></a>
## Pattern : Intégration tierce en mode link-out — préférer une page locale canonique
### Synthèse
- **Objectif** : éviter les parcours concurrents et centraliser les garde-fous UX quand une fonctionnalité publique dépend dun service tiers externe.
- **Contexte** : site ou webapp avec CTA publics menant vers un tiers de réservation, paiement, prise de rendez-vous ou formulaire externe.
- **Quand lutiliser** : dès quune fonctionnalité externe dispose dune page locale dédiée côté produit (`/reservation`, `/booking`, etc.).
- **Quand léviter** : si le produit assume volontairement une sortie directe unique vers le tiers, sans page locale intermédiaire ni besoin de contextualisation.
### Analyse
- **Avantages** :
- UX plus cohérente entre home, navigation et pages dédiées
- garde-fous, wording et fallbacks centralisés au même endroit
- facilite lévolution future vers embed, click-to-load ou variantes de parcours
- **Limites / vigilance** :
- ajoute une étape intermédiaire si la page locale napporte aucune valeur
- demande de maintenir lalignement entre les CTA internes et le parcours canonique
### Validation
- Validé le : 19-03-2026
- Contexte technique : site web public / intégration tierce en mode lien externe
### Implémentation (exemple minimal)
```txt
- faire pointer les CTA internes (home, nav, landing) vers une page locale dédiée
- faire de cette page locale le point canonique vers le service tiers externe
- centraliser sur cette page le contexte utile, les garde-fous et les fallbacks
- éviter les sorties directes concurrentes vers le tiers depuis plusieurs endroits du site
```
### Checklist
- [ ] Un parcours canonique unique est défini
- [ ] Les CTA internes convergent vers la page locale dédiée
- [ ] Les garde-fous et fallbacks sont centralisés
- [ ] Les sorties directes concurrentes vers le tiers sont évitées ou justifiées
---
<a id="pattern-design-tokens-expo-rn"></a>
## Pattern : Design Tokens natifs TypeScript (Expo / React Native)
### Synthèse
- **Objectif** : centraliser les tokens de design sans librairie externe (NativeBase, Tamagui), typés et barrel-exportés.
- **Contexte** : app Expo / React Native avec un système de design à maintenir.
- **Quand lutiliser** : dès le début dun projet mobile, avant les premiers composants.
- **Quand léviter** : si une librairie UI opinionée est déjà choisie et gère ses propres tokens.
### Analyse
- **Avantages** :
- aucune dépendance externe, zéro configuration magique
- autocomplétion TypeScript exacte via `as const` + types dérivés
- facile à migrer vers un design system plus élaboré ultérieurement
- **Limites / vigilance** :
- les fichiers TTF doivent être présents dans `assets/fonts/` — Google Fonts ne peut pas être téléchargé automatiquement, documenter comme pré-requis dans la story
- ne pas réutiliser les tokens `spacing` pour les dimensions de composants (voir risques)
### Validation
- Validé le : 19-03-2026
- Contexte technique : Expo SDK 52+ / React Native / TypeScript — app-alexandrie story 0.1
### Implémentation (exemple minimal)
```typescript
// apps/mobile/src/theme/colors.ts
export const colors = {
primary: #2563EB,
error: #DC2626,
// ...
} as const;
export type ColorToken = keyof typeof colors;
// apps/mobile/src/theme/spacing.ts
export const spacing = { xs: 4, sm: 8, md: 12, base: 16, lg: 24 } as const;
export type SpacingToken = keyof typeof spacing;
// apps/mobile/src/theme/index.ts (barrel export)
export * from ./colors;
export * from ./spacing;
export * from ./typography;
export * from ./shadows;
```
### Checklist
- [ ] Tous les tokens `as const` pour inférence exacte
- [ ] Pas de Context React — constantes TypeScript pures
- [ ] Types dérivés (`ColorToken = keyof typeof colors`) pour lautocomplétion
- [ ] `useFonts` dans `_layout.tsx` avec guard `!fontsLoaded`
- [ ] Fichiers TTF présents dans `assets/fonts/` et documentés dans la story
---
<a id="pattern-tests-styles-sans-renderer"></a>
## Pattern : Tests de styles React Native sans renderer JSX
### Synthèse
- **Objectif** : tester les tokens et styles de composants React Native dans un environnement Jest `testEnvironment: node` sans renderer JSX.
- **Contexte** : config Jest avec `transform: { ^.+\\.ts$: ts-jest }` — les `.tsx` ne sont pas transformés.
- **Quand lutiliser** : tokens de thème, logique pure, valeurs de style exportées.
- **Quand léviter** : rendu conditionnel (styles dynamiques inline) — nécessite `@testing-library/react-native`.
### Analyse
- **Avantages** :
- teste que le composant utilise les bons tokens, pas seulement que les tokens ont des valeurs
- détecte les régressions de style sans renderer
- rapide, aucune config Jest supplémentaire
- **Limites / vigilance** :
- ne teste pas le style calculé au runtime (style conditionnel dynamique)
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / Jest / ts-jest — app-alexandrie story 0.2
### Implémentation
```typescript
// Button.tsx — exporter le StyleSheet avec un nom préfixé
export const buttonStyles = StyleSheet.create({
base: { borderRadius: 20, height: 57 },
primary: { backgroundColor: colors.primary },
});
export function Button(...) { ... }
// ui-components.spec.ts — importer et vérifier les tokens
import { buttonStyles } from ./Button;
import { colors } from @/theme;
it(variante primary utilise colors.primary, () => {
expect(buttonStyles.primary.backgroundColor).toBe(colors.primary);
});
```
### Deux niveaux de tests UI recommandés
1. `.spec.ts` (node) : tokens, valeurs, logique pure
2. `.spec.tsx` (config séparée avec renderer) : rendu visuel, interactions
---
<a id="pattern-export-styles-composant"></a>
## Pattern : Export des styles de composant pour réutilisation partielle (React Native)
### Synthèse
- **Objectif** : partager les dimensions et formes dun composant UI vers des éléments custom qui en dérivent, sans dupliquer les valeurs.
- **Contexte** : app React Native où des screens construisent des éléments qui doivent être "au gabarit" dun composant existant.
- **Quand lutiliser** : bouton custom OAuth, container calqué sur un composant de base, etc.
- **Quand léviter** : si lécart visuel est intentionnel — dans ce cas, une constante locale est plus claire.
### Analyse
- **Avantages** :
- zéro drift silencieux : si les dimensions du composant changent, tous les éléments dérivés suivent
- tests de styles possibles en dehors du composant
- **Limites / vigilance** :
- à nutiliser que pour des éléments vraiment dérivés, pas comme contournement de design system
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / StyleSheet — app-alexandrie story 0.3
### Implémentation
```typescript
// Button.tsx
export const buttonStyles = StyleSheet.create({
base: { borderRadius: 20, height: 57 },
primary: { backgroundColor: colors.primary },
});
export function Button(...) { ... }
// login.tsx — bouton OAuth au gabarit du Button
import { buttonStyles } from @/components/ui/Button;
<TouchableOpacity style={[buttonStyles.base, styles.facebookButton]} />
```
---
<a id="pattern-token-typography-semantique"></a>
## Pattern : Token typography par usage sémantique (React Native)
### Synthèse
- **Objectif** : éviter les mauvais usages de tokens typography visuellement proches mais sémantiquement distincts.
- **Contexte** : fichier `typography.ts` dans un design system React Native.
- **Quand lutiliser** : dès que deux tokens partagent la même taille mais un poids différent.
- **Quand léviter** : jamais — les tokens typography doivent toujours refléter lusage, pas lapparence.
### Analyse
- **Avantages** :
- prévient les "approximations" de tokens en code review
- changement de style dusage spécifique sans régression globale
- **Limites / vigilance** :
- en review : chercher les usages sans `fontWeight` explicite — cest souvent le signe que le mauvais token a été choisi
### Validation
- Validé le : 19-03-2026
- Contexte technique : React Native / TypeScript — app-alexandrie story 0.4
### Implémentation
```typescript
// Bon : nommé par usage sémantique
listItemTitle: { fontSize: 12, fontWeight: 600 }, // titre dun item de liste
caption: { fontSize: 12, fontWeight: 500 }, // info secondaire, hints
// Mauvais : nommé par apparence
mediumText12: { fontSize: 12, fontWeight: 500 }, // ambigu, réutilisé à tort
```
**Règle** : `caption` (Medium) ≠ `listItemTitle` (SemiBold) même si la taille est identique. Ne jamais piocher un token "par approximation".
---
### Principes transverses
- Un pattern = une responsabilité claire
- On privilégie la simplicité locale avant la généricité globale
- Le code doit rester compréhensible 6 mois plus tard
- Si un pattern devient central → il mérite une décision darchitecture dédiée
## Notes importantes
- 3 bons patterns > 30 moyens
- Si un pattern évolue :
- on met à jour la date
- on précise le nouveau contexte
- En cas de doute → le pattern nentre pas encore ici