Chapter 4: Health Data

By following this guide we will
  • Create components for different types of patient health information.
  • Prepare for Chapter 5 in which we will display the following types of information for a given patient:
    • Medications
    • Conditions
    • Allergies
    • Vitals
    • Lab results in fishbone format.

A word on data models

When developing FHIR applications generally, a significant amount of thought is put into deciding the data model that the application will use to store data. Because FHIR is an extensive standard, it is possible to model almost any clinical data using the built-in FHIR data types, which are called Resources.

However, the adoption of each individual Resource type comes with its own particular constraints, such as the presence of a required field which may not be immediately satisfiable in your application. Additionally, there may be more than one Resource type that the data you collect belongs to, or it may be ambiguous which Resource type is appropriate. For the purposes of this tutorial, we will be displaying a few Resource types in a very straightforward way, but note that a more workflow-oriented application may require careful consideration of the Resource types involved.

Connected components to display health data

There are two steps to displaying patient health information: fetching the data, and formatting the display of the data. Commure’s pre-built unconnected components provide basic templates for displaying the data. We will create connected components that, given a patientId, uses FhirDataQuery to fetch the data for that patientId and passes the data into the pre-built unconnected components for display.

We will walk through how to create a connected Medications component. The other connected components follow the same format.

Requisite components and functions

The Commure library provides several useful pre-built components and functions:

  • FhirDataQuery is a component that helps query data, as explained in Chapter 3.
  • fhirpath is a function that extracts the data from the result of type FhirDataQueryResponse.
  • buildMedicationsQuery is a function that helps us build the query for a patient’s medications. It requires a patientId and its result can be passed into FhirDataQuery.
  • MedicationsUnconnected is a component that displays a patient’s medications. We will need to fetch a patient’s medications data to pass into this component.

Helper variables

We also define a few helper variables:

  • FhirDataQueryResponse is the response type from FhirDataQuery.
  • errorToOperationOutcome is a function that parses the error from the result of type FhirDataQueryResponse.

In src/types/index.ts we add the following type definition for FhirDataQueryResponse:

src/types/index.ts
1import React from "react";
2import { Bundle, Resource } from "@commure/fhir-types/r4/types";
3
4export type HOFSmartApp = <P>(
5 WrappedComponent: React.FC<P>
6) => (props: P) => React.ReactElement;
7
8export type DashboardContextType =
9 | {
10 selectMenuItem: (id: string) => void;
11 }
12 | undefined;
13
14export type FhirDataQueryResponse = Readonly<{
15 data?: Bundle | Resource;
16 error?: Error;
17 loading: boolean;
18}>;

In a new file src/utils/helpers/errorConverter.ts we have the following code to define errorToOperationOutcome:

src/utils/helpers/errorConverter.ts
1import { OperationOutcome } from "@commure/fhir-types/r4/types";
2
3export function errorToOperationOutcome(
4 error: Error,
5 resourceType: string
6): OperationOutcome {
7 let operationOutcomeError: OperationOutcome;
8 try {
9 operationOutcomeError = JSON.parse(error.message) as OperationOutcome;
10 } catch (e) {
11 operationOutcomeError = {
12 resourceType,
13 issue: []
14 };
15 }
16 return operationOutcomeError;
17}

Putting it all together

To build the Medications component, we pass the buildMedicationsQuery into FhirDataQuery and handle the three possible response types: error, loading, and when there is a successful query. This is the code we put in src/components/Medications/Medications.tsx:

src/components/Medications/Medications.tsx
1import React from "react";
2
3// @ts-ignore No TS definitions for fhirpath yet.
4import fhirpath from "@commure/fhirpath";
5import { FhirDataQuery } from "@commure/components-data";
6import { patientView } from "@commure/components-core";
7
8import { FhirDataQueryResponse } from "../../types";
9import { errorToOperationOutcome } from "../../utils/helpers/errorConverter";
10
11const { MedicationsUnconnected } = patientView.medications;
12const { buildMedicationsQuery } = patientView.utils.queries;
13
14interface Props {
15 baseClass: string;
16 patientId: string;
17}
18
19const Medications: React.FC<Props> = ({ patientId, ...props }): JSX.Element => {
20 return (
21 <FhirDataQuery queryString={buildMedicationsQuery(patientId)}>
22 {({ data, error, loading }: FhirDataQueryResponse): JSX.Element => {
23 if (error) {
24 return (
25 <MedicationsUnconnected
26 isLoading={false}
27 error={errorToOperationOutcome(error, "MedicationStatement")}
28 {...props}
29 />
30 );
31 }
32 if (loading) {
33 return <MedicationsUnconnected isLoading {...props} />;
34 }
35 const medicationStatements = fhirpath("entry.resource", data);
36 return (
37 <MedicationsUnconnected
38 isLoading={false}
39 data={{ medicationStatements }}
40 {...props}
41 />
42 );
43 }}
44 </FhirDataQuery>
45 );
46};
47
48export default Medications;

The other connected components Condition, Allergies, Vitals, and Fishbone follow the same format as Medications; just replace the highlighted code with the appropriate code specific to the type of health information.

src/components/Allergies/Allergies.tsx
1import React from "react";
2
3// @ts-ignore No TS definitions for fhirpath yet.
4import fhirpath from "@commure/fhirpath";
5import { FhirDataQuery } from "@commure/components-data";
6import { patientView } from "@commure/components-core";
7
8import { FhirDataQueryResponse } from "../../types";
9import { errorToOperationOutcome } from "../../utils/helpers/errorConverter";
10
11const { AllergiesUnconnected } = patientView.allergies;
12const { buildAllergiesQuery } = patientView.utils.queries;
13
14interface Props {
15 baseClass: string;
16 patientId: string;
17}
18
19const Allergies: React.FC<Props> = ({ patientId, ...props }): JSX.Element => {
20 return (
21 <FhirDataQuery queryString={buildAllergiesQuery(patientId)}>
22 {({ data, error, loading }: FhirDataQueryResponse): JSX.Element => {
23 if (error) {
24 return (
25 <AllergiesUnconnected
26 isLoading={false}
27 error={errorToOperationOutcome(error, "AllergyIntolerance")}
28 {...props}
29 />
30 );
31 }
32 if (loading) {
33 return <AllergiesUnconnected isLoading {...props} />;
34 }
35 const allergyIntolerances = fhirpath("entry.resource", data);
36 return (
37 <AllergiesUnconnected
38 isLoading={false}
39 data={{ allergyIntolerances }}
40 {...props}
41 />
42 );
43 }}
44 </FhirDataQuery>
45 );
46};
47
48export default Allergies;
src/components/Condition/Condition.tsx
1import React from "react";
2
3// @ts-ignore No TS definitions for fhirpath yet.
4import fhirpath from "@commure/fhirpath";
5import { FhirDataQuery } from "@commure/components-data";
6import { patientView } from "@commure/components-core";
7
8import { FhirDataQueryResponse } from "../../types";
9import { errorToOperationOutcome } from "../../utils/helpers/errorConverter";
10
11const { PatientConditionUnconnected } = patientView.patientCondition;
12const { buildConditionQuery } = patientView.utils.queries;
13
14interface Props {
15 baseClass: string;
16 patientId: string;
17}
18
19const Condition: React.FC<Props> = ({ patientId, ...props }): JSX.Element => {
20 return (
21 <FhirDataQuery queryString={buildConditionQuery(patientId)}>
22 {({ data, error, loading }: FhirDataQueryResponse): JSX.Element => {
23 if (error) {
24 return (
25 <PatientConditionUnconnected
26 isLoading={false}
27 error={errorToOperationOutcome(error, "Condition")}
28 {...props}
29 />
30 );
31 }
32 if (loading) {
33 return <PatientConditionUnconnected isLoading {...props} />;
34 }
35 const conditions = fhirpath("entry.resource", data);
36 return (
37 <PatientConditionUnconnected
38 isLoading={false}
39 data={{ conditions }}
40 {...props}
41 />
42 );
43 }}
44 </FhirDataQuery>
45 );
46};
47
48export default Condition;
src/components/Vitals/Vitals.tsx
1import React from "react";
2
3// @ts-ignore No TS definitions for fhirpath yet.
4import fhirpath from "@commure/fhirpath";
5import { FhirDataQuery } from "@commure/components-data";
6import { patientView } from "@commure/components-core";
7
8import { FhirDataQueryResponse } from "../../types";
9import { errorToOperationOutcome } from "../../utils/helpers/errorConverter";
10
11const { VitalsUnconnected } = patientView.vitals;
12const { buildVitalsQuery } = patientView.utils.queries;
13
14interface Props {
15 baseClass: string;
16 patientId: string;
17}
18
19const Vitals: React.FC<Props> = ({ patientId, ...props }): JSX.Element => {
20 return (
21 <FhirDataQuery queryString={buildVitalsQuery(patientId)}>
22 {({ data, error, loading }: FhirDataQueryResponse): JSX.Element => {
23 if (error) {
24 return (
25 <VitalsUnconnected
26 isLoading={false}
27 error={errorToOperationOutcome(error, "Observation")}
28 {...props}
29 />
30 );
31 }
32 if (loading) {
33 return <VitalsUnconnected isLoading {...props} />;
34 }
35 const observations = fhirpath("entry.resource", data);
36 return (
37 <VitalsUnconnected
38 isLoading={false}
39 data={{ observations }}
40 {...props}
41 />
42 );
43 }}
44 </FhirDataQuery>
45 );
46};
47
48export default Vitals;

The only deviation is that the Fishbone component does have a bit of extra code, as it requires a parameter for the fishbone type.

src/components/Fishbone/Fishbone.tsx
1import React from "react";
2
3// @ts-ignore No TS definitions for fhirpath yet.
4import fhirpath from "@commure/fhirpath";
5import { FhirDataQuery } from "@commure/components-data";
6import { patientView } from "@commure/components-core";
7
8import { FhirDataQueryResponse } from "../../types";
9import { errorToOperationOutcome } from "../../utils/helpers/errorConverter";
10
11const { FishboneUnconnected } = patientView.fishbone;
12const { FISHBONE_LOINC_MAP } = patientView.fishbone.constants;
13const { buildFishboneQuery } = patientView.utils.queries;
14
15interface Props {
16 baseClass: string;
17 patientId: string;
18 fishboneType: keyof typeof FISHBONE_LOINC_MAP;
19}
20
21const Fishbone: React.FC<Props> = ({
22 patientId,
23 fishboneType,
24 ...props
25}): JSX.Element => {
26 return (
27 <FhirDataQuery queryString={buildFishboneQuery(patientId, fishboneType)}>
28 {({ data, error, loading }: FhirDataQueryResponse): JSX.Element => {
29 if (error) {
30 return (
31 <FishboneUnconnected
32 isLoading={false}
33 error={errorToOperationOutcome(error, "Observation")}
34 fishboneType={fishboneType}
35 {...props}
36 />
37 );
38 }
39 if (loading) {
40 return (
41 <FishboneUnconnected
42 isLoading
43 fishboneType={fishboneType}
44 {...props}
45 />
46 );
47 }
48 const observations = fhirpath("entry.resource", data);
49 return (
50 <FishboneUnconnected
51 isLoading={false}
52 data={{ observations }}
53 fishboneType={fishboneType}
54 {...props}
55 />
56 );
57 }}
58 </FhirDataQuery>
59 );
60};
61
62export default Fishbone;
box icon

Conclusion

In this chapter, we created connected components that fetch and display data for five types of patient information. We will use these components in the next chapter to display the information in a chart on the right side of the dashboard.