Files
_Assistant_Lead_Tech/knowledge/frontend/risques/state.md
MaksTinyWorkshop 9b7af9f1b0 Refonte Structure
2026-03-25 08:34:19 +01:00

9.8 KiB

Frontend — Risques & vigilance : State

Extrait de la base de connaissance Lead_tech. Voir knowledge/frontend/risques/README.md pour l'index complet.


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)

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

} 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

État booléen UI dérivé hardcodé au lieu d'être calculé depuis le store

Risques

  • Un état toggle (isBookmarked, isLiked, isFollowed) initialisé à false en dur ne reflète jamais l'état réel
  • Le bouton est toujours en mode "ajouter" sans jamais passer en mode "supprimer"

Symptômes

  • const isBookmarked = false; // état local géré ci-dessous via state
  • Bouton bookmark/like toujours dans le même état visuel peu importe l'état réel

Bonnes pratiques / mitigations

// ❌ Anti-pattern — état hardcodé
const isBookmarked = false;

// ✅ Pattern correct — dérivé du store au rendu
const { bookmarks } = useCommunityStore();
const isBookmarked = bookmarks.some((b) => b.thread.id === threadId);
  • Règle : si le store contient la liste (bookmarks, likes, follows), l'état booléen se dérive avec .some() ou .has()

  • Contexte technique : React Native / Zustand — app-alexandrie story 4.4, 20-03-2026


Flag isLoading unique pour des opérations de natures différentes

Risques

  • Un même flag (ex: isBookmarking) utilisé à la fois pour les mutations (add/remove) et le chargement de la liste provoque des bugs visuels — spinner manquant au premier chargement si une mutation est en cours en parallèle

Symptômes

  • Spinner absent au premier chargement de la liste bookmarks
  • Bouton "ajouter" désactivé alors qu'aucune mutation n'est en cours

Bonnes pratiques / mitigations

// ❌ Anti-pattern — un seul flag pour tout
isBookmarking: boolean;

// ✅ Pattern correct — séparation claire
isBookmarking: boolean;        // mutations add/remove
isLoadingBookmarks: boolean;   // chargement de la liste (GET)
  • Contexte technique : React Native / Zustand — app-alexandrie story 4.4, 20-03-2026

Zustand : optimistic update sur item absent de la liste principale

Risques

  • Une action admin qui cherche l'item uniquement dans state.threads (liste paginée principale) manque les items présents exclusivement dans state.pinnedThreads ou state.showcasedThreads
  • L'optimistic update ne se reflète pas visuellement même si l'appel API a réussi

Symptômes

  • L'item mis à jour par une action admin n'apparaît pas dans la nouvelle sous-liste après l'action
  • Bug reproductible uniquement quand l'item est épinglé / en vitrine mais pas dans la page courante du flux principal

Bonnes pratiques / mitigations

// ❌ Anti-pattern : cherche uniquement dans la liste principale paginée
const target = state.threads.find((t) => t.id === threadId);
// → manque les items présents uniquement dans pinnedThreads / showcasedThreads

// ✅ Pattern correct : fallback sur toutes les sous-listes du store
const target =
  state.threads.find((t) => t.id === threadId) ??
  state.pinnedThreads.find((t) => t.id === threadId) ??
  state.showcasedThreads.find((t) => t.id === threadId);
  • Règle : toute action qui opère sur un item pouvant être présent dans plusieurs sous-listes doit chercher dans l'ensemble de ces listes

  • Règle complémentaire : ne pas mettre à jour une sous-liste (ex: pinnedThreads) lors d'une action qui n'y a pas de rapport (ex: mise en vitrine ne touche pas pinnedThreads)

  • Contexte technique : React Native / Zustand — app-alexandrie 23-03-2026


Store Zustand : méthodes update* qui avalent les erreurs sans rethrow

Risques

  • Une méthode store qui catch une erreur sans la relancer (throw) avale silencieusement les erreurs métier (ex: UNSAFE_LINK)
  • L'écran appelant ne reçoit jamais l'erreur → impossible d'afficher un feedback à l'utilisateur

Symptômes

  • L'action semble réussir côté UI mais la donnée n'a pas changé en base
  • Erreurs métier (ex: lien interdit) invisibles pour l'utilisateur final

Bonnes pratiques / mitigations

// ❌ MAUVAIS — l'erreur est avalée, l'écran ne sait pas que ça a échoué
async updateThread(forumSlug, threadId, body) {
  await communityService.updateThread(accessToken, forumSlug, threadId, body);
},

// ✅ BON — l'erreur est propagée pour que l'écran puisse réagir
async updateThread(forumSlug, threadId, body) {
  try {
    await communityService.updateThread(accessToken, forumSlug, threadId, body);
  } catch (e) {
    const err = e as Error & { code?: string };
    throw err; // Le code d'erreur (ex: UNSAFE_LINK) est préservé sur l'objet
  }
},
  • Règle : toute méthode store qui appelle le service réseau doit soit (1) relancer l'erreur enrichie avec throw err, soit (2) la stocker dans le state (set({ error: err.message })). Jamais les deux à la fois sans rethrow si l'écran doit réagir au catch.

  • Contexte technique : React Native / Zustand — app-alexandrie 24-03-2026