Skip to Content
DocumentationConventions de codageRécupération des données

Récupération des données

  • Colocalisez votre logique de récupération de données dans un fichier suffixé avec .resource. Par exemple, user.resource.ts contient la logique de récupération de données pour le composant User.

  • Dans la mesure du possible, préférez abstraire votre récupération de données dans un hook personnalisé plutôt que de récupérer avec des effets . La récupération de données avec des effets présente de nombreux inconvénients  et doit être évitée. Préférez plutôt l’utilisation de hooks SWR .

  • Utilisez les hooks SWR  pour récupérer les données du backend. Utilisez useSWRImmutable  pour les ressources qui ne sont pas censées changer souvent, telles que les concepts ou les configurations backend. Alternativement, vous pouvez utiliser useOpenmrsSWR depuis @openmrs/esm-framework, qui est un wrapper autour de useSWR qui gère automatiquement les contrôleurs d’abandon et s’intègre avec openmrsFetch.

  • Placez le hook SWR dans un fichier de ressource et exportez-le en tant que fonction. Cela nous permet de réutiliser le même hook dans plusieurs composants.

  • Mémorisez la valeur de retour de votre hook SWR en utilisant useMemo pour éviter des rerenders inutiles. Ceci est particulièrement important si le hook est utilisé dans un composant qui est rendu plusieurs fois, comme une ligne de tableau. Lors de la création de hooks personnalisés qui encapsulent SWR, mémorisez toujours l’objet de retour :

    import { useMemo } from 'react'; import { restBaseUrl, useOpenmrsSWR } from '@openmrs/esm-framework'; // Bon - valeur de retour mémorisée export function usePatient(patientUuid?: string) { const url = patientUuid ? `${restBaseUrl}/patient/${patientUuid}` : null; const { data: response, error, isLoading, isValidating } = useOpenmrsSWR<Patient>(url); const patient = response?.data; return useMemo( () => ({ isLoading, isValidating, patient, patientUuid, error, }), [isLoading, isValidating, error, patient, patientUuid], ); }
  • Les hooks de récupération de données doivent suivre la convention de nommage use<resource>. Par exemple, useUser est le hook pour récupérer les données utilisateur.

  • Utilisez openmrsFetch depuis @openmrs/esm-framework pour récupérer les données du backend. openmrsFetch est un wrapper autour de l’API fetch qui résout les URLs relatives à OpenMRS, définit des en-têtes adaptés au JSON, gère les en-têtes UI de l’API REST OpenMRS, analyse les corps de réponse et lève une erreur pour les réponses non-2xx. Si vous le passez directement à useSWR, typez les données SWR comme FetchResponse<T> et lisez la charge utile depuis response.data. Pour la plupart des hooks de ressource, préférez useOpenmrsSWR, qui fournit openmrsFetch et un signal d’abandon pour vous.

  • Utilisez les propriétés error, isLoading, isValidating et mutate du hook SWR pour gérer les erreurs, les états de chargement et les mutations. Ne recréez pas ces propriétés manuellement.

  • Utilisez le modèle de récupération conditionnelle de données de SWR  lorsque la requête dépend d’une condition. Par exemple, si la requête dépend d’une prop, ne faites la requête que si la prop est vraie.

    import { type FetchResponse, openmrsFetch } from '@openmrs/esm-framework'; // Ne récupérer les données utilisateur que si userId est fourni const url = userId ? `/ws/rest/v1/user/${userId}` : null; const { data: response, error, isLoading, isValidating, mutate, } = useSWR<FetchResponse<User>, Error>(url, openmrsFetch); const user = response?.data;

Contrats : rendre les états explicites

Les hooks de récupération de données doivent rendre les états de chargement, d’erreur et de succès explicites et faciles à gérer.

  • Ne retournez pas “peut-être des données” sans retourner les indicateurs d’état SWR associés.
  • Les composants consommant les hooks doivent gérer :
    • le chargement (isLoading / isValidating),
    • l’erreur (error),
    • le succès vide (chargé mais aucun résultat).

Cela empêche les bugs d‘“hypothèses implicites” (par exemple, le rendu avec des données non définies).

⚠️

Rendre les invariants visibles : Toujours retourner une forme cohérente avec { data, error, isLoading } et exiger que l’interface utilisateur gère explicitement les trois états.

// Bon - gestion explicite des états const { data, error, isLoading } = usePatient(patientUuid); if (isLoading) { return <InlineLoading />; } if (error) { return <ErrorState error={error} />; } if (!data) { return <EmptyState />; } return <PatientBanner patient={data} />;

Comportement borné : éviter les nouvelles tentatives/polling non bornés

Les nouvelles tentatives et les boucles de rafraîchissement non bornées causent une latence et une charge imprévisibles.

  • Les nouvelles tentatives doivent être bornées et intentionnelles :
    • Pas de nouvelles tentatives infinies
    • Nouvelle tentative uniquement sur les échecs transitoires
  • Le polling/rafraîchissement doit être borné :
    • Préférez la revalidation sur focus / reconnexion lorsque c’est approprié
    • Si le polling est requis, définissez un intervalle explicite et documentez pourquoi
🔒

Comportement borné : Politique de nouvelle tentative standard (nombre max de tentatives, backoff, quand ne pas réessayer), limites de polling, valeurs par défaut de pagination.

// Bon - politique de nouvelle tentative explicite const { data } = useOpenmrsSWR<VisitSearchResponse>(url, { swrConfig: { errorRetryCount: 3, errorRetryInterval: 1000, revalidateOnFocus: true, revalidateOnReconnect: true, refreshInterval: 0, // Désactiver explicitement le polling }, });

Préférer les hooks étroits aux hooks génériques

Concevez les hooks pour qu’ils soient difficiles à utiliser incorrectement :

  • Préférez les hooks spécifiques à une ressource (usePatientVisits(patientUuid)) aux hooks “faire n’importe quoi”.
  • Préférez des paramètres typés et contraints aux sacs d’options qui permettent des combinaisons invalides.
🎯

Concevoir les API pour les mauvais usages : Décourager le “hook de récupération générique qui fait n’importe quoi” ; préférer les hooks spécifiques à une ressource avec des paramètres étroits.

// Bon - hook étroit et spécifique import { restBaseUrl, useOpenmrsSWR } from '@openmrs/esm-framework'; import type { SWRConfiguration } from 'swr'; interface VisitSearchResponse { results: Array<Visit>; } export function usePatientVisits(patientUuid?: string) { const url = patientUuid ? `${restBaseUrl}/visit?patient=${patientUuid}` : null; const { data: response, error, isLoading } = useOpenmrsSWR<VisitSearchResponse>(url); return { visits: response?.data?.results ?? [], error, isLoading, }; } // À éviter - trop générique, facile à mal utiliser export function useGenericFetch<Data>(url: string, options?: SWRConfiguration) { return useSWR<Data>(url, options?.fetcher, options); }

Observabilité : inclure le contexte avec les échecs

Lors de l’exposition des erreurs :

  • Conservez l’objet d’erreur original.
  • Attachez suffisamment de contexte pour déboguer (nom de la ressource + entrées clés, pas de secrets).
  • Assurez-vous que l’interface utilisateur affiche des états d’erreur significatifs ; évitez le repli silencieux vers une interface utilisateur vide.
🔍

Observabilité : Exiger des erreurs contextuelles (point de terminaison + paramètres + ID de corrélation si disponible) et une interface utilisateur d’erreur cohérente pour l’utilisateur.

// Bon - l'erreur inclut le contexte if (error) { console.error('Échec de la récupération des visites du patient', { patientUuid, endpoint: '/ws/rest/v1/visit', error: error.message, }); return ( <ErrorState error={error} headerTitle={t('errorLoadingVisits', 'Erreur lors du chargement des visites')} /> ); }

Valeurs par défaut (recommandées)

Si un hook choisit un comportement SWR non par défaut, il doit être explicite dans le hook :

  • Politique de nouvelle tentative (nombre + conditions)
  • Stratégie de rafraîchissement (focus/reconnexion/polling)
  • Stratégie de déduplication/stale

Ces décisions appartiennent au hook (la limite de ressource), pas dispersées dans les composants.

⚙️

Valeurs par défaut : Le délai d’attente, le nombre de tentatives, l’intervalle de déduplication, la stratégie stale et la politique de pagination doivent être explicites dans les hooks.

// Bon - les valeurs par défaut sont explicites dans le hook export function usePatientVisits(patientUuid: string) { const url = patientUuid ? `/ws/rest/v1/visit?patient=${patientUuid}` : null; return useOpenmrsSWR<VisitSearchResponse>(url, { swrConfig: { // Politique de nouvelle tentative explicite errorRetryCount: 3, errorRetryInterval: 1000, // Stratégie de rafraîchissement explicite revalidateOnFocus: false, // Les visites ne changent pas fréquemment revalidateOnReconnect: true, refreshInterval: 0, // Pas de polling }, }); }
  • Filtrez les données invalides (null, undefined ou enregistrements incomplets) au niveau du hook plutôt que dans les composants. Cela garantit que tous les consommateurs reçoivent des données propres et valides et empêche les erreurs lors de l’accès aux propriétés imbriquées :

    // Bon - filtrage au niveau du hook export function usePatients(patientUuids: string[]) { const { data: response, error, isLoading } = useOpenmrsSWR<{ results: Array<Patient | null> }>(...); const validPatients = useMemo( () => response?.data?.results?.filter((patient): patient is Patient => patient !== null && patient.person !== null ) ?? [], [response?.data?.results] ); return { patients: validPatients, error, isLoading }; }
  • Lors de l’utilisation de représentations personnalisées dans les appels API, définissez-les comme constantes au niveau du module pour la réutilisabilité :

    // Bon - représentation personnalisée réutilisable const patientProperties = [ 'patientId', 'uuid', 'identifiers', 'person:(gender,age,birthdate,personName)', ]; const patientSearchCustomRepresentation = `custom:(${patientProperties.join(',')})`; // Utiliser dans le hook const url = `${restBaseUrl}/patient?v=${patientSearchCustomRepresentation}`;
  • Lors de l’utilisation de useSWRInfinite, envisagez de définir initialSize en fonction de la longueur attendue des données pour optimiser le chargement initial :

    const { data, setSize, size } = useSWRInfinite(getKey, fetcher, { keepPreviousData: true, initialSize: patientUuids ? Math.min(resultsToFetch, patientUuids.length) : 0, });
Dernière mise à jour le