# Front-end — Risques & vigilance Ce fichier recense des risques front-end susceptibles de provoquer : - bugs subtils, - comportements inattendus, - dette technique rapide, - régressions UX/perf/a11y. Dernière mise à jour : 24-03-2026 --- ## Règles d’utilisation - Chaque entrée doit dire : - ce qui peut mal se passer, - comment on le voit (symptômes), - comment on le maîtrise (mitigation). - Si c’est lié à une stack / version : on note le contexte. --- ## Index - [Auth côté client](#risque-auth-cote-client) - [Erreurs silencieuses / écrans blancs](#risque-erreurs-silencieuses) - [Mélange server state / client state](#risque-melange-server-client-state) - [Appels API en state local d’écran](#risque-api-state-local-ecran) - [Performances : sur-renders + bundle](#risque-performances-sur-renders) - [Accessibilité oubliée (a11y)](#risque-accessibilite-oubliee) - [Catch silencieux — erreur inconnue sans feedback utilisateur](#risque-catch-silencieux) - [Auto-reset d’un état dégradé sur toute réponse 2xx](#risque-auto-reset-etat-degrade) - [Refresh store en fire-and-forget après mutation](#risque-refresh-store-fire-and-forget) - [Loading infini sur écran gated par droits distants](#risque-loading-infini-ecran-gated) - [Jest React Native — config node bloque les composants `.tsx`](#risque-jest-rn-config-node) - [Bouton OAuth présent mais handler vide après refacto UI](#risque-oauth-handler-vide) - [Double système d'espacement dans un monorepo Expo](#risque-double-systeme-espacement) - [Dimensions d'image via tokens `spacing` (React Native)](#risque-dimensions-image-via-spacing) - [Écran détail Expo Router — store vide en deep link / reload](#risque-store-vide-deep-link) - [`useEffect` fetch — guard incomplet sur les états terminaux](#risque-useeffect-guard-incomplet) - [Store Zustand : collections sans clé de contexte (navigation inter-contexte)](#risque-zustand-collection-sans-cle-contexte) - [`useSearchParams()` sans `Suspense` casse le build Next.js App Router](#risque-usesearchparams-sans-suspense) - [Type `ViewData` dupliqué entre couche serveur et composant UI (Next.js)](#risque-type-viewdata-duplique) - [Composant React dans un fichier `.ts` — `React.createElement` workaround](#risque-composant-react-fichier-ts) - [Double validation de segment dynamique App Router (layout + page)](#risque-double-validation-segment-app-router) - [Faux test négatif — tester le helper au lieu de tester l'exclusion](#risque-faux-test-negatif) - [État booléen UI dérivé hardcodé au lieu d'être calculé depuis le store](#risque-boolean-ui-hardcode-store) - [Flag `isLoading` unique pour des opérations de natures différentes](#risque-flag-isloading-unique-nature-differente) - [Consent state : `false` ambigu entre "pas de décision" et "refus explicite"](#risque-consent-state-false-ambigu) - [Script inline : interpolation directe au lieu de `JSON.stringify`](#risque-script-inline-interpolation-directe) - [Next.js App Router : `window.location.reload()` au lieu de `router.refresh()`](#risque-window-location-reload-nextjs) - [`useTransition` + optimistic update : snapshot capturé après `setState`](#risque-usetransition-snapshot-apres-setstate) - [`window.confirm()` dans une app React/Next.js](#risque-window-confirm-react) - [`import type` depuis `src/server/**` dans un composant client](#risque-import-type-server-composant-client) - [Inline styles dans les composants dashboard](#risque-inline-styles-dashboard) - [Classes Tailwind invalides courantes (bugs silencieux)](#risque-tailwind-classes-invalides) - [Next.js : `` natif interdit dans les composants](#risque-img-natif-nextjs) - [`useTransition` global pour des listes d'items interactifs](#risque-usetransition-global-liste-items) - [`useCallback` inutile quand le callback est wrappé en inline au render](#risque-usecallback-inutile-inline) - [Formulaire React avec `defaultValue` sans `key` prop](#risque-formulaire-defaultvalue-sans-key) - [Zustand : optimistic update sur item absent de la liste principale](#risque-zustand-optimistic-update-sous-listes) - [Guard de rôle via return conditionnel dans le render](#risque-guard-role-return-conditionnel) - [Méthodes Zustand qui avalent les erreurs (sans rethrow)](#risque-zustand-sans-rethrow) - [Regex globale singleton (/g) partagée au niveau module](#risque-regex-globale-singleton) --- ## Auth côté client (mauvaise séparation des responsabilités) ### Risques - Le front “décide” des permissions au lieu d’appliquer un contrat backend - Affichage d’actions interdites / fuite d’informations dans l’UI - Tokens stockés de façon dangereuse (XSS) ### Symptômes - Différences entre “ce que l’UI permet” et “ce que l’API accepte” - Bugs “ça marche chez moi” selon sessions/rôles - Incohérences sur refresh / multi-tab ### Bonnes pratiques / mitigations - Le backend reste source de vérité (authz) - Cacher l’UI ≠ sécuriser : toujours sécuriser côté API - Stockage tokens : privilégier cookies httpOnly si modèle adapté - Gérer proprement expiration/refresh + révocation ### Contexte technique - Observé : (à compléter) - Stack : (à préciser) --- ## Erreurs silencieuses / écrans blancs ### Risques - Exceptions non gérées → app inutilisable - États async mal gérés → UI incohérente (loading infini, vide incompris) ### Symptômes - Écran blanc après une action - Toast générique “Une erreur est survenue” sans corrélation - Pas de moyen de reproduire / diagnostiquer ### Bonnes pratiques / mitigations - Pattern “états UI explicites” (loading/empty/error) - Boundary d’erreur UI + fallback - Logging minimal côté client avec requestId/traceId quand possible --- ## Mélange server state / client state ### Risques - Cache pollué par des états UI (onglets, filtres) - UI qui reflète une donnée périmée sans le savoir - Re-renders et bugs de synchronisation ### Symptômes - “Ça revient tout seul” après refresh - Données affichées ≠ données du backend - Debug très long car état implicite ### Bonnes pratiques / mitigations - Séparer explicitement server state vs client state - Invalidation/reload explicite du server state - État UI local réinitialisable --- ## Appels API gérés en state local d’écran (refactor coûteux) ### Risques - Server state non partageable entre écrans (liste/detail, wizard, tabs) → duplication et incohérences - Pas de cache / invalidation standard → bugs subtils et re-fetchs inutiles - Refactor tardif quand l’epic s’étend (mutations, cache, offline, pagination) ### Symptômes - Même appel API recopié dans plusieurs écrans - Un écran “A” modifie une ressource mais l’écran “B” n’est jamais rafraîchi - Code review qui force un refactor vers un store/cache au milieu d’un epic ### Bonnes pratiques / mitigations - Par défaut : créer un store de domaine (ex : Zustand) ou un cache de server state pour tout domaine susceptible d’être réutilisé - Centraliser `isLoading`/`error`/`data` et la stratégie de refresh/invalidation - Exception acceptable : état purement UI, local et jetable (ex : input de recherche, filtres temporaires non persistés) --- ## Performances : sur-renders + bundle non maîtrisé ### Risques - App lente sur mobile - Bundle qui grossit sans contrôle - Chargements inutiles (images, libs) ### Symptômes - Input lag - Temps de chargement qui dérive à chaque feature - Requêtes réseaux inutiles ### Bonnes pratiques / mitigations - Lazy loading routes/features - Mesurer (au minimum) : temps de chargement + re-renders critiques - Politique images (formats, tailles, lazy) - Audit régulier des dépendances --- ## Accessibilité oubliée (a11y) ### Risques - App inutilisable au clavier/lecteur d’écran - Régressions silencieuses sur focus/labels ### Symptômes - Modales impossibles à fermer au clavier - Inputs sans labels/erreurs non annoncées - Focus “perdu” ### Bonnes pratiques / mitigations - Checklist a11y minimale sur chaque écran clé - Gestion de focus (modales, erreurs formulaire) - Labels/aria cohérents + tests simples --- ## Catch silencieux — erreur inconnue sans feedback utilisateur ### Risques - Un `catch` qui ne traite que les cas connus laisse l'utilisateur face à un spinner qui disparaît sans message - L'état d'erreur reste implicite → impossible de diagnostiquer ou de reproduire ### Symptômes - Bouton spinner qui s'arrête, rien ne se passe - Pas de toast / message d'erreur affiché - Erreur "avalée" silencieusement dans les logs ### Bonnes pratiques / mitigations ```typescript } catch (err: unknown) { const code = (err as { code?: string }).code; if (code === 'SUBSCRIPTION_REQUIRED') { setSubscriptionRequired(true); } else { setError('Une erreur est survenue. Veuillez réessayer.'); // toujours un fallback } } ``` - **Règle** : tout `catch` doit avoir une branche `else` (ou `default`) qui affiche un feedback utilisateur explicite. - Contexte technique : React Native / Expo — 09-03-2026 --- ## Auto-reset d’un état dégradé sur toute réponse 2xx ### Risques - Le client sort trop tôt d’un mode dégradé alors que la cause serveur est toujours présente - Le bandeau ou l’état read-only clignote puis disparaît à tort - Les utilisateurs retentent une action d’écriture qui va encore échouer ### Symptômes - Un GET réussi réinitialise `isReadOnly` ou `isDegraded` - L’UI redevient “normale” alors que Redis ou un service critique est toujours indisponible - Les erreurs reviennent immédiatement à la prochaine mutation ### Bonnes pratiques / mitigations - Ne réinitialiser l’état dégradé qu’après une requête d’écriture réussie - Exclure `GET` et `HEAD` de la logique de reset - Conserver le mode dégradé tant qu’aucune mutation n’a prouvé le retour à la normale - Contexte technique : React Native / Expo — 10-03-2026 --- ## Refresh store en fire-and-forget après mutation ### Risques - L’UI affiche un succès alors que la resynchronisation a échoué - État local incohérent avec l’état serveur - Erreurs silencieuses impossibles à diagnostiquer ### Symptômes - Mutation réussie puis store jamais rafraîchi - Spinner coupé avant que l’écran soit réellement à jour - Données anciennes qui persistent jusqu’au prochain reload ### Bonnes pratiques / mitigations - `await` explicite du refresh si l’UI dépend du résultat - Gestion d’erreur dédiée sur la phase de resynchronisation - N’utiliser le fire-and-forget que pour un effet secondaire réellement non bloquant - Contexte technique : React Native / Expo — 10-03-2026 --- ## Loading infini sur écran gated par droits distants ### Risques - Un écran protégé reste bloqué dans un faux `loading` après une erreur de chargement des entitlements - Un effet relance automatiquement la récupération en boucle sans action utilisateur - L’utilisateur ne voit ni état d’erreur ni issue de sortie claire ### Symptômes - Spinner infini sur un écran soumis à permissions distantes - `entitlements` ou autorisations laissés à `null` après erreur - `useEffect` ou logique d’entrée qui retrigger le fetch à chaque rendu ### Bonnes pratiques / mitigations - Distinguer explicitement `loading`, `error`, `ready` - Ne pas réutiliser `null` comme état ambigu "pas encore chargé" et "chargement en erreur" - Bloquer les retries automatiques en boucle après erreur - Réautoriser un retry seulement via action utilisateur explicite ou nouvelle condition d’entrée - Contexte technique : React Native / Expo / store d’entitlements — 10-03-2026 --- ## Jest React Native — config node bloque les composants `.tsx` ### Risques - `SyntaxError: Cannot use import statement outside a module` lors de l’import d’un barrel `.ts` qui réexporte des `.tsx` - Impossible d’importer des composants React Native dans les tests — JSX non transformé ### Symptômes - Erreur de syntaxe inattendue au run des tests sur un fichier `.ts` qui importe un `.tsx` - Les tests de tokens passent mais tout test touchant un composant échoue ### Bonnes pratiques / mitigations - `transform: { ‘^.+\\.ts$’: ‘ts-jest’ }` ne transforme que `.ts` — pas `.tsx` - **Pattern recommandé** : tester la logique pure (tokens, valeurs de style) dans `.spec.ts`, le rendu visuel dans `.spec.tsx` avec une config séparée (`@testing-library/react-native` + `babel-jest`) - Exporter le `StyleSheet` de chaque composant pour le tester sans JSX (voir pattern dédié dans `10_frontend_patterns_valides.md`) - Contexte technique : React Native / Jest / ts-jest — app-alexandrie 19-03-2026 --- ## Bouton OAuth présent mais handler vide après refacto UI ### Risques - L’OAuth est silencieusement cassé sur le nouvel écran — zéro erreur au démarrage, zéro crash - L’AC "toutes les fonctionnalités préservées" peut être coché alors que le bouton est mort ### Symptômes - `