Skip to content

Commit

Permalink
(refactor) Refactor usePatientPrograms hook to use SWR
Browse files Browse the repository at this point in the history
Refactors the usePatientProgram hook to leverage a SWR hook for fetching patient programs. With this, we get:

- Automatic caching and revalidation
- Conditional fetching based on the patientUuid and the `formJson.meta.programs.hasProgramFields` property of the form
- Built-in error and loading states
- Better handling of race conditions
  • Loading branch information
denniskigen committed Dec 11, 2024
1 parent 611e050 commit c255b13
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 45 deletions.
47 changes: 24 additions & 23 deletions src/hooks/usePatientPrograms.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import useSWR from 'swr';
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import { useEffect, useState } from 'react';
import { type FormSchema, type PatientProgram } from '../types';
const customRepresentation = `custom:(uuid,display,program:(uuid,name,allWorkflows),dateEnrolled,dateCompleted,location:(uuid,display),states:(startDate,endDate,state:(uuid,name,retired,concept:(uuid),programWorkflow:(uuid)))`;
import type { FormSchema, PatientProgram } from '../types';

export const usePatientPrograms = (patientUuid: string, formJson: FormSchema) => {
const [patientPrograms, setPatientPrograms] = useState<Array<PatientProgram>>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const useActiveProgramEnrollments = (patientUuid: string) => {
const customRepresentation = `custom:(uuid,display,program:(uuid,name,allWorkflows),dateEnrolled,dateCompleted,location:(uuid,display),states:(startDate,endDate,state:(uuid,name,retired,concept:(uuid),programWorkflow:(uuid)))`;
const apiUrl = `${restBaseUrl}/programenrollment?patient=${patientUuid}&v=${customRepresentation}`;

const { data, error, isLoading } = useSWR<{ data: Array<PatientProgram> }, Error>(
patientUuid ? apiUrl : null,
openmrsFetch,
);

useEffect(() => {
if (formJson.meta?.programs?.hasProgramFields) {
openmrsFetch(`${restBaseUrl}/programenrollment?patient=${patientUuid}&v=${customRepresentation}`)
.then((response) => {
setPatientPrograms(response.data.results.filter((enrollment) => enrollment.dateCompleted === null));
setIsLoading(false);
})
.catch((error) => {
setError(error);
setIsLoading(false);
});
} else {
setIsLoading(false);
}
}, [formJson]);
const activePrograms = data?.data?.filter((enrollment) => enrollment.dateCompleted === null);

return {
patientPrograms,
activePrograms,
error,
isLoading,
};
};

export const usePatientPrograms = (patientUuid: string, formJson: FormSchema) => {
const { activePrograms, error, isLoading } = useActiveProgramEnrollments(
formJson.meta?.programs?.hasProgramFields ? patientUuid : null,
);

return {
patientPrograms: activePrograms,
errorFetchingPatientPrograms: error,
isLoadingPatientPrograms: isLoading,
};
};
41 changes: 19 additions & 22 deletions src/processors/encounter/encounter-form-processor.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import {
type FormField,
type FormPage,
type FormProcessorContextProps,
type FormSchema,
type FormSection,
type ValueAndDisplay,
} from '../../types';
import { usePatientPrograms } from '../../hooks/usePatientPrograms';
import { useEffect, useState } from 'react';
import { useEncounter } from '../../hooks/useEncounter';
import { isEmpty } from '../../validators/form-validator';
import { type FormContextProps } from '../../provider/form-provider';
import { FormProcessor } from '../form-processor';
import { type OpenmrsResource, showSnackbar, translateFrom } from '@openmrs/esm-framework';
import {
getMutableSessionProps,
hydrateRepeatField,
Expand All @@ -23,23 +11,32 @@ import {
savePatientIdentifiers,
savePatientPrograms,
} from './encounter-processor-helper';
import { type OpenmrsResource, showSnackbar, translateFrom } from '@openmrs/esm-framework';
import { moduleName } from '../../globals';
import {
type FormField,
type FormPage,
type FormProcessorContextProps,
type FormSchema,
type FormSection,
type ValueAndDisplay,
} from '../../types';
import { evaluateAsyncExpression, type FormNode } from '../../utils/expression-runner';
import { extractErrorMessagesFromResponse } from '../../utils/error-utils';
import { extractObsValueAndDisplay } from '../../utils/form-helper';
import { FormProcessor } from '../form-processor';
import { getPreviousEncounter, saveEncounter } from '../../api';
import { useEncounterRole } from '../../hooks/useEncounterRole';
import { evaluateAsyncExpression, type FormNode } from '../../utils/expression-runner';
import { hasRendering } from '../../utils/common-utils';
import { extractObsValueAndDisplay } from '../../utils/form-helper';
import { isEmpty } from '../../validators/form-validator';
import { moduleName } from '../../globals';
import { type FormContextProps } from '../../provider/form-provider';
import { useEncounter } from '../../hooks/useEncounter';
import { useEncounterRole } from '../../hooks/useEncounterRole';
import { usePatientPrograms } from '../../hooks/usePatientPrograms';

function useCustomHooks(context: Partial<FormProcessorContextProps>) {
const [isLoading, setIsLoading] = useState(true);
const { encounter, isLoading: isLoadingEncounter } = useEncounter(context.formJson);
const { encounterRole, isLoading: isLoadingEncounterRole } = useEncounterRole();
const { isLoading: isLoadingPatientPrograms, patientPrograms } = usePatientPrograms(
context.patient?.id,
context.formJson,
);
const { isLoadingPatientPrograms, patientPrograms } = usePatientPrograms(context.patient?.id, context.formJson);

useEffect(() => {
setIsLoading(isLoadingPatientPrograms || isLoadingEncounter || isLoadingEncounterRole);
Expand Down

0 comments on commit c255b13

Please sign in to comment.