Skip to Content

Loading states

  • When creating custom hooks that wrap SWR, memoize the return value using useMemo to prevent unnecessary re-renders. This is especially important when returning an object with multiple properties:

    import { restBaseUrl, useOpenmrsSWR } from '@openmrs/esm-framework'; // Good - memoized return value 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], ); }
  • Use SWR’s isLoading for the initial blocking load, and use isValidating when you need to show a background refresh indicator while existing data remains on screen. Avoid treating every revalidation as a full loading state when stale data is already available:

    // Good - initial load and background refresh are distinct const { data: response, error, isLoading, isValidating } = useOpenmrsSWR<Patient>(url); const patient = response?.data; if (isLoading) { return <SkeletonPlaceholder />; } if (error) { return <ErrorState error={error} />; } if (!patient) { return <EmptyState />; } return ( <> {isValidating ? <InlineLoading description={t('refreshing', 'Refreshing')} /> : null} <PatientBanner patient={patient} /> </> );
  • Use Carbon’s loading components to provide visual feedback during data fetching:

    • Use InlineLoading for inline loading indicators within forms or small components
    • Use SkeletonText or SkeletonPlaceholder for loading placeholders that match the final content layout
    import { InlineLoading, SkeletonText } from '@carbon/react'; // Inline loading indicator {isLoading && ( <InlineLoading description={t('loading', 'Loading')} /> )} // Skeleton placeholder for content {isLoading ? ( <SkeletonText heading width="60%" /> ) : ( <div>{data?.content}</div> )}
  • Handle null or undefined data gracefully using optional chaining and nullish coalescing:

    // Good - safe data access const patientName = data?.patient?.name ?? t('unknownPatient', 'Unknown patient'); const totalCount = data?.total ?? 0; // Good - conditional rendering {data?.results?.length > 0 ? ( <DataTable data={data.results} /> ) : ( <EmptyState /> )}
  • When data might be undefined during initial load, use conditional rendering to avoid errors:

    // Good - handles undefined data {data && ( <PatientBanner patient={data} /> )} // Also good - using optional chaining {data?.patient && ( <PatientBanner patient={data.patient} /> )}
  • Use TypeScript type guards in filter functions to ensure type safety when filtering out invalid data:

    // Good - type guard ensures filtered array has correct type const validPatients = data?.filter( (patient): patient is Patient => patient !== null && patient.person !== null ) ?? []; // TypeScript now knows validPatients is Patient[], not (Patient | null)[] validPatients.forEach(patient => { console.log(patient.person.name); // Type-safe access });
Last updated on