@openmrs/esm-react-utils 3.2.0 → 3.2.1-pre.1015

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-react-utils",
3
- "version": "3.2.0",
3
+ "version": "3.2.1-pre.1015",
4
4
  "license": "MPL-2.0",
5
5
  "description": "React utilities for OpenMRS.",
6
6
  "browser": "dist/openmrs-esm-react-utils.js",
@@ -39,7 +39,8 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "lodash-es": "^4.17.21",
42
- "single-spa-react": "^4.1.1"
42
+ "single-spa-react": "^4.1.1",
43
+ "swr": "^1.2.2"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "@openmrs/esm-api": "3.x",
@@ -53,11 +54,11 @@
53
54
  "react-i18next": "11.x"
54
55
  },
55
56
  "devDependencies": {
56
- "@openmrs/esm-api": "^3.2.0",
57
- "@openmrs/esm-config": "^3.2.0",
58
- "@openmrs/esm-error-handling": "^3.2.0",
59
- "@openmrs/esm-extensions": "^3.2.0",
60
- "@openmrs/esm-globals": "^3.2.0",
57
+ "@openmrs/esm-api": "^3.2.1-pre.1015",
58
+ "@openmrs/esm-config": "^3.2.1-pre.1015",
59
+ "@openmrs/esm-error-handling": "^3.2.1-pre.1015",
60
+ "@openmrs/esm-extensions": "^3.2.1-pre.1015",
61
+ "@openmrs/esm-globals": "^3.2.1-pre.1015",
61
62
  "i18next": "^19.6.0",
62
63
  "react": "^16.13.1",
63
64
  "react-dom": "^16.13.1",
@@ -65,5 +66,5 @@
65
66
  "rxjs": "^6.5.3",
66
67
  "unistore": "^3.5.2"
67
68
  },
68
- "gitHead": "9c4a04089e29ff99321d6fef72294f402deb0f7b"
69
+ "gitHead": "2a10b8956cd63006bd913b18a2a482fa9a0134ec"
69
70
  }
package/src/Extension.tsx CHANGED
@@ -1,6 +1,7 @@
1
- import React from "react";
1
+ import { renderExtension } from "@openmrs/esm-extensions";
2
+ import React, { useCallback, useContext, useEffect, useState } from "react";
3
+ import { ComponentContext } from ".";
2
4
  import { ExtensionData } from "./ComponentContext";
3
- import { useExtension } from "./useExtension";
4
5
 
5
6
  export interface ExtensionProps {
6
7
  state?: Record<string, any>;
@@ -20,7 +21,31 @@ export interface ExtensionProps {
20
21
  * and *must* only be used once within that `<ExtensionSlot>`.
21
22
  */
22
23
  export const Extension: React.FC<ExtensionProps> = ({ state, wrap }) => {
23
- const [ref, extension] = useExtension<HTMLDivElement>(state);
24
+ const [domElement, setDomElement] = useState<HTMLDivElement>();
25
+ const { extension } = useContext(ComponentContext);
26
+
27
+ const ref = useCallback((node) => {
28
+ setDomElement(node);
29
+ }, []);
30
+
31
+ useEffect(() => {
32
+ if (domElement != null && extension) {
33
+ return renderExtension(
34
+ domElement,
35
+ extension.extensionSlotName,
36
+ extension.extensionSlotModuleName,
37
+ extension.extensionId,
38
+ undefined,
39
+ state
40
+ );
41
+ }
42
+ }, [
43
+ extension?.extensionSlotName,
44
+ extension?.extensionId,
45
+ extension?.extensionSlotModuleName,
46
+ state,
47
+ domElement,
48
+ ]);
24
49
 
25
50
  // The extension is rendered into the `<slot>`. It is surrounded by a
26
51
  // `<div>` with relative positioning in order to allow the UI Editor
@@ -1,5 +1,5 @@
1
1
  import React, { useRef, useMemo } from "react";
2
- import { ConnectedExtension } from "./useConnectedExtensions";
2
+ import { ConnectedExtension } from "@openmrs/esm-extensions";
3
3
  import { ComponentContext } from "./ComponentContext";
4
4
  import { Extension } from "./Extension";
5
5
  import { useExtensionSlot } from "./useExtensionSlot";
@@ -76,7 +76,7 @@ export const ExtensionSlot: React.FC<ExtensionSlotProps> = ({
76
76
  <ComponentContext.Provider
77
77
  key={extension.id}
78
78
  value={{
79
- moduleName: extension.moduleName,
79
+ moduleName: extensionSlotModuleName, // moduleName is not used by the receiving Extension
80
80
  extension: {
81
81
  extensionId: extension.id,
82
82
  extensionSlotName,
@@ -87,7 +87,7 @@ export const ExtensionSlot: React.FC<ExtensionSlotProps> = ({
87
87
  {children ?? <Extension state={stateRef.current} />}
88
88
  </ComponentContext.Provider>
89
89
  )),
90
- [select, extensions, extensionSlotName]
90
+ [select, extensions, extensionSlotName, stateRef.current]
91
91
  );
92
92
 
93
93
  return (
package/src/index.ts CHANGED
@@ -5,15 +5,15 @@ export * from "./Extension";
5
5
  export * from "./ExtensionSlot";
6
6
  export * from "./getLifecycle";
7
7
  export * from "./openmrsComponentDecorator";
8
+ export * from "./useAssignedExtensions";
8
9
  export * from "./useAssignedExtensionIds";
9
- export * from "./useAttachedExtensionIds";
10
10
  export * from "./useBodyScrollLock";
11
11
  export * from "./useConfig";
12
12
  export * from "./useConnectedExtensions";
13
13
  export * from "./useConnectivity";
14
14
  export * from "./usePatient";
15
15
  export * from "./useCurrentPatient";
16
- export * from "./useExtension";
16
+ export * from "./useExtensionInternalStore";
17
17
  export * from "./useExtensionSlotConfig";
18
18
  export * from "./useExtensionSlot";
19
19
  export * from "./useExtensionSlotMeta";
@@ -1,29 +1,26 @@
1
1
  import { useEffect, useState } from "react";
2
- import { getAssignedIds } from "@openmrs/esm-extensions";
3
- import { useExtensionSlotConfig } from "./useExtensionSlotConfig";
4
- import { useAttachedExtensionIds } from "./useAttachedExtensionIds";
2
+ import { getExtensionStore } from "@openmrs/esm-extensions";
3
+ import { isEqual } from "lodash";
5
4
 
6
5
  /**
7
6
  * Gets the assigned extension ids for a given extension slot name.
8
7
  * Does not consider if offline or online.
9
- * @param extensionSlotName The name of the slot to get the assigned IDs for.
8
+ * @param slotName The name of the slot to get the assigned IDs for.
9
+ *
10
+ * @deprecated Use `useAssignedExtensions`
10
11
  */
11
- export function useAssignedExtensionIds(extensionSlotName: string) {
12
- const config = useExtensionSlotConfig(extensionSlotName);
13
- const attachedIds = useAttachedExtensionIds(extensionSlotName);
14
- const [assignedIds, setAssignedIds] = useState<Array<string>>([]);
12
+ export function useAssignedExtensionIds(slotName: string) {
13
+ const [ids, setIds] = useState<Array<string>>([]);
15
14
 
16
15
  useEffect(() => {
17
- const newAssignedIds = getAssignedIds(
18
- extensionSlotName,
19
- config,
20
- attachedIds
21
- );
16
+ return getExtensionStore().subscribe((state) => {
17
+ const newIds =
18
+ state.slots[slotName]?.assignedExtensions.map((e) => e.id) ?? [];
19
+ if (!isEqual(newIds, ids)) {
20
+ setIds(newIds);
21
+ }
22
+ });
23
+ }, []);
22
24
 
23
- if (newAssignedIds.join(",") !== assignedIds.join(",")) {
24
- setAssignedIds(newAssignedIds);
25
- }
26
- }, [attachedIds, config]);
27
-
28
- return assignedIds;
25
+ return ids;
29
26
  }
@@ -0,0 +1,29 @@
1
+ import { useEffect, useState } from "react";
2
+ import {
3
+ AssignedExtension,
4
+ ExtensionStore,
5
+ getExtensionStore,
6
+ } from "@openmrs/esm-extensions";
7
+ import { isEqual } from "lodash";
8
+
9
+ /**
10
+ * Gets the assigned extensions for a given extension slot name.
11
+ * Does not consider if offline or online.
12
+ * @param slotName The name of the slot to get the assigned extensions for.
13
+ */
14
+ export function useAssignedExtensions(slotName: string) {
15
+ const [extensions, setExtensions] = useState<Array<AssignedExtension>>([]);
16
+
17
+ useEffect(() => {
18
+ function update(state: ExtensionStore) {
19
+ const newExtensions = state.slots[slotName]?.assignedExtensions ?? [];
20
+ if (!isEqual(newExtensions, extensions)) {
21
+ setExtensions(newExtensions);
22
+ }
23
+ }
24
+ update(getExtensionStore().getState());
25
+ return getExtensionStore().subscribe(update);
26
+ }, [slotName]);
27
+
28
+ return extensions;
29
+ }
package/src/useConfig.ts CHANGED
@@ -120,8 +120,5 @@ export function useConfig() {
120
120
  [normalConfig, extensionConfig]
121
121
  );
122
122
 
123
- const configNameForDebugMessage = extension
124
- ? `${extension?.extensionSlotModuleName}-${extension?.extensionSlotName}-${extension?.extensionId}`
125
- : moduleName;
126
123
  return config;
127
124
  }
@@ -1,51 +1,25 @@
1
1
  import { useMemo } from "react";
2
2
  import {
3
- checkStatusFor,
4
- ExtensionMeta,
5
- ExtensionRegistration,
6
- extensionStore,
7
- getExtensionRegistrationFrom,
3
+ ConnectedExtension,
4
+ getConnectedExtensions,
8
5
  } from "@openmrs/esm-extensions";
9
- import { useAssignedExtensionIds } from "./useAssignedExtensionIds";
10
6
  import { useConnectivity } from "./useConnectivity";
11
-
12
- function isValidExtension(
13
- extension: ConnectedExtension | { id: string }
14
- ): extension is ConnectedExtension {
15
- return extension.hasOwnProperty("name");
16
- }
17
-
18
- /**
19
- * We have the following extension modes:
20
- *
21
- * - attached (set via code in form of: attach, detach, ...)
22
- * - configured (set via configuration in form of: added, removed, ...)
23
- * - assigned (computed from attached and configured)
24
- * - connected (computed from assigned using connectivity and online / offline)
25
- */
26
-
27
- export interface ConnectedExtension extends ExtensionRegistration {
28
- id: string;
29
- }
7
+ import { useAssignedExtensions } from "./useAssignedExtensions";
30
8
 
31
9
  /**
32
10
  * Gets the assigned extension for a given extension slot name.
33
11
  * Considers if offline or online.
34
- * @param extensionSlotName The name of the slot to get the assigned extensions for.
12
+ * @param slotName The name of the slot to get the assigned extensions for.
35
13
  */
36
14
  export function useConnectedExtensions(
37
- extensionSlotName: string
15
+ slotName: string
38
16
  ): Array<ConnectedExtension> {
39
17
  const online = useConnectivity();
40
- const extensionIdsToRender = useAssignedExtensionIds(extensionSlotName);
18
+ const assignedExtensions = useAssignedExtensions(slotName);
41
19
 
42
- const extensions = useMemo(() => {
43
- const state = extensionStore.getState();
44
- return extensionIdsToRender
45
- .map((id) => ({ id, ...getExtensionRegistrationFrom(state, id) }))
46
- .filter(isValidExtension)
47
- .filter((m) => checkStatusFor(online, m.online, m.offline));
48
- }, [extensionIdsToRender, online]);
20
+ const connectedExtensions = useMemo(() => {
21
+ return getConnectedExtensions(assignedExtensions, online);
22
+ }, [assignedExtensions, online]);
49
23
 
50
- return extensions;
24
+ return connectedExtensions;
51
25
  }
@@ -0,0 +1,10 @@
1
+ import {
2
+ ExtensionInternalStore,
3
+ getExtensionInternalStore,
4
+ } from "@openmrs/esm-extensions";
5
+ import { createUseStore } from "./createUseStore";
6
+
7
+ /** @internal */
8
+ export const useExtensionInternalStore = createUseStore<ExtensionInternalStore>(
9
+ getExtensionInternalStore()
10
+ );
@@ -1,12 +1,10 @@
1
1
  import { useContext, useEffect } from "react";
2
- import {
3
- registerExtensionSlot,
4
- unregisterExtensionSlot,
5
- } from "@openmrs/esm-extensions";
2
+ import { registerExtensionSlot } from "@openmrs/esm-extensions";
6
3
  import { ComponentContext } from "./ComponentContext";
7
4
  import { useConnectedExtensions } from "./useConnectedExtensions";
8
5
 
9
- export function useExtensionSlot(extensionSlotName: string) {
6
+ /** @internal */
7
+ export function useExtensionSlot(slotName: string) {
10
8
  const { moduleName } = useContext(ComponentContext);
11
9
 
12
10
  if (!moduleName) {
@@ -16,15 +14,14 @@ export function useExtensionSlot(extensionSlotName: string) {
16
14
  }
17
15
 
18
16
  useEffect(() => {
19
- registerExtensionSlot(moduleName, extensionSlotName);
20
- return () => unregisterExtensionSlot(moduleName, extensionSlotName);
17
+ registerExtensionSlot(moduleName, slotName);
21
18
  }, []);
22
19
 
23
- const extensions = useConnectedExtensions(extensionSlotName);
20
+ const extensions = useConnectedExtensions(slotName);
24
21
 
25
22
  return {
26
23
  extensions,
27
- extensionSlotName,
24
+ extensionSlotName: slotName,
28
25
  extensionSlotModuleName: moduleName,
29
26
  };
30
27
  }
@@ -1,10 +1,9 @@
1
- import { useContext, useCallback } from "react";
1
+ import { useCallback } from "react";
2
2
  import {
3
3
  ExtensionSlotConfigObject,
4
- ExtensionSlotConfigsStore,
5
- getExtensionSlotsConfigStore,
4
+ ExtensionSlotConfigStore,
5
+ getExtensionSlotConfigStore,
6
6
  } from "@openmrs/esm-config";
7
- import { ComponentContext } from "./ComponentContext";
8
7
  import { useStoreState } from "./useStoreState";
9
8
 
10
9
  const defaultConfig: ExtensionSlotConfigObject = {
@@ -13,12 +12,12 @@ const defaultConfig: ExtensionSlotConfigObject = {
13
12
  remove: [],
14
13
  };
15
14
 
16
- export function useExtensionSlotConfig(extensionSlotName: string) {
17
- const { moduleName } = useContext(ComponentContext);
18
- const store = getExtensionSlotsConfigStore(moduleName);
15
+ /** @internal */
16
+ export function useExtensionSlotConfig(slotName: string) {
17
+ const store = getExtensionSlotConfigStore(slotName);
19
18
  const select = useCallback(
20
- (s: ExtensionSlotConfigsStore) => s.extensionSlotConfigs[extensionSlotName],
21
- [extensionSlotName]
19
+ (s: ExtensionSlotConfigStore) => s.config,
20
+ [slotName]
22
21
  );
23
22
  const config = useStoreState(store, select);
24
23
  return config || defaultConfig;
@@ -1,4 +1,6 @@
1
- import { ExtensionStore, extensionStore } from "@openmrs/esm-extensions";
1
+ import { ExtensionStore, getExtensionStore } from "@openmrs/esm-extensions";
2
2
  import { createUseStore } from "./createUseStore";
3
3
 
4
- export const useExtensionStore = createUseStore<ExtensionStore>(extensionStore);
4
+ export const useExtensionStore = createUseStore<ExtensionStore>(
5
+ getExtensionStore()
6
+ );
package/src/usePatient.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useReducer } from "react";
1
+ import { useEffect, useReducer } from "react";
2
2
  import { fetchCurrentPatient, PatientUuid } from "@openmrs/esm-api";
3
3
 
4
4
  type NullablePatient = fhir.Patient | null;
@@ -6,6 +6,7 @@ type NullablePatient = fhir.Patient | null;
6
6
  interface CurrentPatientState {
7
7
  patientUuid: string | null;
8
8
  patient: NullablePatient;
9
+ isPendingUuid: boolean;
9
10
  isLoadingPatient: boolean;
10
11
  err: Error | null;
11
12
  }
@@ -36,7 +37,8 @@ enum ActionTypes {
36
37
  const initialState: CurrentPatientState = {
37
38
  patientUuid: null,
38
39
  patient: null,
39
- isLoadingPatient: true,
40
+ isPendingUuid: true,
41
+ isLoadingPatient: false,
40
42
  err: null,
41
43
  };
42
44
 
@@ -55,6 +57,7 @@ function reducer(
55
57
  ...state,
56
58
  patientUuid: action.patientUuid,
57
59
  patient: null,
60
+ isPendingUuid: false,
58
61
  isLoadingPatient: true,
59
62
  err: null,
60
63
  };
@@ -62,6 +65,7 @@ function reducer(
62
65
  return {
63
66
  ...state,
64
67
  patient: action.patient,
68
+ isPendingUuid: false,
65
69
  isLoadingPatient: false,
66
70
  err: null,
67
71
  };
@@ -69,6 +73,7 @@ function reducer(
69
73
  return {
70
74
  ...state,
71
75
  patient: null,
76
+ isPendingUuid: false,
72
77
  isLoadingPatient: false,
73
78
  err: action.err,
74
79
  };
@@ -87,15 +92,12 @@ export function usePatient(patientUuid?: string) {
87
92
  const [state, dispatch] = useReducer(reducer, {
88
93
  ...initialState,
89
94
  patientUuid: patientUuid ?? null,
95
+ isPendingUuid: !patientUuid,
96
+ isLoadingPatient: !!patientUuid,
90
97
  });
91
98
 
92
99
  useEffect(() => {
93
- if (state.patientUuid) {
94
- dispatch({
95
- type: ActionTypes.loadPatient,
96
- patientUuid: state.patientUuid,
97
- });
98
- } else {
100
+ if (state.isPendingUuid) {
99
101
  const patientUuidFromUrl = getPatientUuidFromUrl();
100
102
  if (patientUuidFromUrl) {
101
103
  dispatch({
@@ -106,9 +108,7 @@ export function usePatient(patientUuid?: string) {
106
108
  dispatch({ type: ActionTypes.newPatient, patient: null });
107
109
  }
108
110
  }
109
- }, [state.patientUuid]);
110
111
 
111
- useEffect(() => {
112
112
  let active = true;
113
113
  if (state.isLoadingPatient && state.patientUuid) {
114
114
  fetchCurrentPatient(state.patientUuid).then(
@@ -129,7 +129,7 @@ export function usePatient(patientUuid?: string) {
129
129
  return () => {
130
130
  active = false;
131
131
  };
132
- }, [state.isLoadingPatient, state.patientUuid]);
132
+ }, [state.isPendingUuid, state.isLoadingPatient, state.patientUuid]);
133
133
 
134
134
  useEffect(() => {
135
135
  const handleRouteUpdate = (evt) => {
@@ -144,10 +144,10 @@ export function usePatient(patientUuid?: string) {
144
144
  window.addEventListener("single-spa:routing-event", handleRouteUpdate);
145
145
  return () =>
146
146
  window.removeEventListener("single-spa:routing-event", handleRouteUpdate);
147
- }, []);
147
+ }, [state.patientUuid]);
148
148
 
149
149
  return {
150
- isLoading: state.isLoadingPatient,
150
+ isLoading: state.isPendingUuid || state.isLoadingPatient,
151
151
  patient: state.patient,
152
152
  patientUuid: patientUuid ?? state.patientUuid,
153
153
  error: state.err,
package/src/useVisit.ts CHANGED
@@ -1,49 +1,34 @@
1
- import { useState, useEffect } from "react";
2
- import dayjs from "dayjs";
3
1
  import {
4
- getVisitsForPatient,
5
- getStartedVisit,
6
- VisitMode,
7
- VisitStatus,
2
+ defaultVisitCustomRepresentation,
3
+ openmrsFetch,
8
4
  Visit,
9
5
  } from "@openmrs/esm-api";
6
+ import useSWR from "swr";
10
7
 
11
- export function useVisit(patientUuid: string) {
12
- const [currentVisit, setCurrentVisit] = useState<Visit | null>(null);
13
- const [error, setError] = useState(null);
14
-
15
- useEffect(() => {
16
- const sub = getStartedVisit.subscribe((visit) => {
17
- if (visit) {
18
- setCurrentVisit(visit?.visitData ?? null);
19
- } else {
20
- setCurrentVisit(null);
21
- }
22
- });
23
-
24
- return () => sub.unsubscribe();
25
- }, [patientUuid]);
8
+ interface VisitReturnType {
9
+ error: Error;
10
+ mutate: () => void;
11
+ isValidating: boolean;
12
+ currentVisit: Visit | null;
13
+ }
26
14
 
27
- useEffect(() => {
28
- const abortController = new AbortController();
29
- const sub = getVisitsForPatient(patientUuid, abortController).subscribe(
30
- ({ data }) => {
31
- const currentVisit = data.results.find(
32
- (visit) => visit.stopDatetime === null
33
- );
15
+ /**
16
+ * This React hook returns a visit object. If the `patientUuid` is provided
17
+ * as a parameter, then the currentVisit, error and mutate function
18
+ * for that patient visit is returned.
19
+ * @param patientUuid Unique patient identifier `string`
20
+ * @returns Object {`error` `isValidating`, `currentVisit`, `mutate`}
21
+ */
22
+ export function useVisit(patientUuid: string): VisitReturnType {
23
+ const { data, error, mutate, isValidating } = useSWR<{
24
+ data: { results: Array<Visit> };
25
+ }>(
26
+ `/ws/rest/v1/visit?patient=${patientUuid}&v=${defaultVisitCustomRepresentation}&includeInactive=false`,
27
+ openmrsFetch
28
+ );
34
29
 
35
- if (currentVisit) {
36
- getStartedVisit.next({
37
- mode: VisitMode.LOADING,
38
- visitData: currentVisit,
39
- status: VisitStatus.ONGOING,
40
- });
41
- }
42
- },
43
- setError
44
- );
45
- return () => sub && sub.unsubscribe();
46
- }, [patientUuid]);
30
+ const currentVisit =
31
+ data?.data.results.find((visit) => visit.stopDatetime === null) ?? null;
47
32
 
48
- return { currentVisit, error };
33
+ return { error, mutate, isValidating, currentVisit };
49
34
  }
@@ -1,18 +0,0 @@
1
- import { useCallback } from "react";
2
- import { extensionStore, ExtensionStore } from "@openmrs/esm-extensions";
3
- import { useStoreState } from "./useStoreState";
4
-
5
- const defaultArray: Array<string> = [];
6
-
7
- /**
8
- * Gets the assigned extension ids for the given slot.
9
- * @param extensionSlotName
10
- */
11
- export function useAttachedExtensionIds(extensionSlotName: string) {
12
- const select = useCallback(
13
- (s: ExtensionStore) => s.slots[extensionSlotName]?.attachedIds,
14
- [extensionSlotName]
15
- );
16
- const extensions = useStoreState(extensionStore, select);
17
- return extensions || defaultArray;
18
- }
@@ -1,31 +0,0 @@
1
- import { RefObject, useContext, useRef, useEffect } from "react";
2
- import { renderExtension } from "@openmrs/esm-extensions";
3
- import { ComponentContext, ExtensionData } from "./ComponentContext";
4
-
5
- export function useExtension<TRef extends HTMLElement>(
6
- state?: Record<string, any>
7
- ): [RefObject<TRef>, ExtensionData | undefined] {
8
- const ref = useRef<TRef>(null);
9
- const { extension } = useContext(ComponentContext);
10
-
11
- useEffect(() => {
12
- if (ref.current && extension) {
13
- return renderExtension(
14
- ref.current,
15
- extension.extensionSlotName,
16
- extension.extensionSlotModuleName,
17
- extension.extensionId,
18
- undefined,
19
- state
20
- );
21
- }
22
- }, [
23
- extension?.extensionSlotName,
24
- extension?.extensionId,
25
- extension?.extensionSlotModuleName,
26
- ref.current,
27
- state,
28
- ]);
29
-
30
- return [ref, extension];
31
- }