@openmrs/esm-react-utils 5.2.1-pre.1094 → 5.2.1-pre.1101
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/.turbo/turbo-build.log +6 -6
- package/dist/openmrs-esm-react-utils.js +1 -1
- package/dist/openmrs-esm-react-utils.js.map +1 -1
- package/package.json +8 -8
- package/src/index.ts +5 -3
- package/src/public.ts +5 -3
- package/src/useAbortController.test.tsx +42 -0
- package/src/useAbortController.ts +40 -0
- package/src/useDebounce.ts +3 -2
- package/src/useOnClickOutside.test.tsx +8 -6
- package/src/useOpenmrsSWR.ts +86 -0
- package/src/useSession.test.tsx +1 -1
- /package/src/{useOnClickOutside.tsx → useOnClickOutside.ts} +0 -0
- /package/src/{useSession.tsx → useSession.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openmrs/esm-react-utils",
|
|
3
|
-
"version": "5.2.1-pre.
|
|
3
|
+
"version": "5.2.1-pre.1101",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "React utilities for OpenMRS.",
|
|
6
6
|
"browser": "dist/openmrs-esm-react-utils.js",
|
|
@@ -56,12 +56,12 @@
|
|
|
56
56
|
"swr": "2.x"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@openmrs/esm-api": "^5.2.1-pre.
|
|
60
|
-
"@openmrs/esm-config": "^5.2.1-pre.
|
|
61
|
-
"@openmrs/esm-error-handling": "^5.2.1-pre.
|
|
62
|
-
"@openmrs/esm-extensions": "^5.2.1-pre.
|
|
63
|
-
"@openmrs/esm-feature-flags": "^5.2.1-pre.
|
|
64
|
-
"@openmrs/esm-globals": "^5.2.1-pre.
|
|
59
|
+
"@openmrs/esm-api": "^5.2.1-pre.1101",
|
|
60
|
+
"@openmrs/esm-config": "^5.2.1-pre.1101",
|
|
61
|
+
"@openmrs/esm-error-handling": "^5.2.1-pre.1101",
|
|
62
|
+
"@openmrs/esm-extensions": "^5.2.1-pre.1101",
|
|
63
|
+
"@openmrs/esm-feature-flags": "^5.2.1-pre.1101",
|
|
64
|
+
"@openmrs/esm-globals": "^5.2.1-pre.1101",
|
|
65
65
|
"dayjs": "^1.10.8",
|
|
66
66
|
"i18next": "^21.10.0",
|
|
67
67
|
"react": "^18.1.0",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"swr": "^2.2.2",
|
|
72
72
|
"webpack": "^5.88.0"
|
|
73
73
|
},
|
|
74
|
-
"gitHead": "
|
|
74
|
+
"gitHead": "db76586824c390393c7cacffc288d22160e5a54b"
|
|
75
75
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,15 +2,17 @@ export * from "./ComponentContext";
|
|
|
2
2
|
export * from "./ConfigurableLink";
|
|
3
3
|
export * from "./Extension";
|
|
4
4
|
export * from "./ExtensionSlot";
|
|
5
|
+
export * from "./UserHasAccess";
|
|
5
6
|
export * from "./getLifecycle";
|
|
6
7
|
export * from "./openmrsComponentDecorator";
|
|
8
|
+
export * from "./useAbortController";
|
|
7
9
|
export * from "./useAssignedExtensions";
|
|
8
10
|
export * from "./useAssignedExtensionIds";
|
|
9
11
|
export * from "./useBodyScrollLock";
|
|
10
12
|
export * from "./useConfig";
|
|
11
13
|
export * from "./useConnectedExtensions";
|
|
12
14
|
export * from "./useConnectivity";
|
|
13
|
-
export * from "./
|
|
15
|
+
export * from "./useDebounce";
|
|
14
16
|
export * from "./useExtensionInternalStore";
|
|
15
17
|
export * from "./useExtensionSlot";
|
|
16
18
|
export * from "./useExtensionSlotMeta";
|
|
@@ -20,10 +22,10 @@ export * from "./useForceUpdate";
|
|
|
20
22
|
export * from "./useLayoutType";
|
|
21
23
|
export * from "./useLocations";
|
|
22
24
|
export * from "./useOnClickOutside";
|
|
23
|
-
export * from "./
|
|
25
|
+
export * from "./useOpenmrsSWR";
|
|
26
|
+
export * from "./usePatient";
|
|
24
27
|
export { useSession } from "./useSession";
|
|
25
28
|
export * from "./useStore";
|
|
26
29
|
export * from "./useVisit";
|
|
27
30
|
export * from "./useVisitTypes";
|
|
28
31
|
export * from "./usePagination";
|
|
29
|
-
export * from "./useDebounce";
|
package/src/public.ts
CHANGED
|
@@ -2,24 +2,26 @@ export { type ExtensionData } from "./ComponentContext";
|
|
|
2
2
|
export * from "./ConfigurableLink";
|
|
3
3
|
export * from "./Extension";
|
|
4
4
|
export * from "./ExtensionSlot";
|
|
5
|
+
export * from "./UserHasAccess";
|
|
5
6
|
export * from "./getLifecycle";
|
|
7
|
+
export * from "./useAbortController";
|
|
6
8
|
export * from "./useAssignedExtensions";
|
|
7
9
|
export * from "./useAssignedExtensionIds";
|
|
8
10
|
export * from "./useBodyScrollLock";
|
|
9
11
|
export * from "./useConfig";
|
|
10
12
|
export * from "./useConnectedExtensions";
|
|
11
13
|
export * from "./useConnectivity";
|
|
12
|
-
export * from "./
|
|
14
|
+
export * from "./useDebounce";
|
|
13
15
|
export * from "./useExtensionSlotMeta";
|
|
14
16
|
export * from "./useExtensionStore";
|
|
15
17
|
export * from "./useFeatureFlag";
|
|
16
18
|
export * from "./useLayoutType";
|
|
17
19
|
export * from "./useLocations";
|
|
18
20
|
export * from "./useOnClickOutside";
|
|
19
|
-
export * from "./
|
|
21
|
+
export * from "./useOpenmrsSWR";
|
|
22
|
+
export * from "./usePatient";
|
|
20
23
|
export { useSession } from "./useSession";
|
|
21
24
|
export * from "./useStore";
|
|
22
25
|
export * from "./useVisit";
|
|
23
26
|
export * from "./useVisitTypes";
|
|
24
27
|
export * from "./usePagination";
|
|
25
|
-
export * from "./useDebounce";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { renderHook, cleanup } from "@testing-library/react";
|
|
2
|
+
import "@testing-library/jest-dom/extend-expect";
|
|
3
|
+
import useAbortController from "./useAbortController";
|
|
4
|
+
|
|
5
|
+
describe("useAbortController", () => {
|
|
6
|
+
afterEach(cleanup);
|
|
7
|
+
|
|
8
|
+
it("returns an AbortController", () => {
|
|
9
|
+
const { result } = renderHook(() => useAbortController());
|
|
10
|
+
expect(result.current).not.toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns a consistent AbortController across re-renders", () => {
|
|
14
|
+
const { result, rerender } = renderHook(() => useAbortController());
|
|
15
|
+
const firstAc = result.current;
|
|
16
|
+
|
|
17
|
+
rerender();
|
|
18
|
+
|
|
19
|
+
expect(result.current).toBe(firstAc);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns a new AbortController after the previous controller has been aborted", () => {
|
|
23
|
+
const { result, rerender } = renderHook(() => useAbortController());
|
|
24
|
+
const firstAc = result.current;
|
|
25
|
+
|
|
26
|
+
firstAc.abort();
|
|
27
|
+
|
|
28
|
+
rerender();
|
|
29
|
+
|
|
30
|
+
expect(result.current).not.toBe(firstAc);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("aborts the AbortController when the component is unmounted", () => {
|
|
34
|
+
const { result, unmount } = renderHook(() => useAbortController());
|
|
35
|
+
|
|
36
|
+
expect(result.current.signal.aborted).toBe(false);
|
|
37
|
+
|
|
38
|
+
unmount();
|
|
39
|
+
|
|
40
|
+
expect(result.current.signal.aborted).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** @module @category Utility */
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @beta
|
|
6
|
+
*
|
|
7
|
+
* This hook creates an AbortController that lasts either until the previous AbortController
|
|
8
|
+
* is aborted or until the component unmounts. This can be used to ensure that all fetch requests
|
|
9
|
+
* are cancelled when a component is unmounted.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* import { useAbortController } from "@openmrs/esm-framework";
|
|
14
|
+
*
|
|
15
|
+
* function MyComponent() {
|
|
16
|
+
* const abortController = useAbortController();
|
|
17
|
+
* const { data } = useSWR(key, (key) => openmrsFetch(key, { signal: abortController.signal }));
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* // render something with data
|
|
21
|
+
* );
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function useAbortController() {
|
|
26
|
+
const abortController = useRef<AbortController>();
|
|
27
|
+
|
|
28
|
+
if (!abortController.current || abortController.current.signal.aborted) {
|
|
29
|
+
abortController.current = new AbortController();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const ac = abortController.current;
|
|
34
|
+
return () => ac?.abort();
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
return abortController.current;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default useAbortController;
|
package/src/useDebounce.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** @module @category
|
|
1
|
+
/** @module @category Utility */
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
|
|
|
8
8
|
*
|
|
9
9
|
* @example
|
|
10
10
|
* ```tsx
|
|
11
|
-
* import { useDebounce } from "@openmrs/esm-
|
|
11
|
+
* import { useDebounce } from "@openmrs/esm-framework";
|
|
12
12
|
*
|
|
13
13
|
* function MyComponent() {
|
|
14
14
|
* const [searchTerm, setSearchTerm] = useState('');
|
|
@@ -35,6 +35,7 @@ export function useDebounce<T>(value: T, delay: number = 300) {
|
|
|
35
35
|
const timer = setTimeout(() => {
|
|
36
36
|
setDebounceValue(value);
|
|
37
37
|
}, delay);
|
|
38
|
+
|
|
38
39
|
return () => {
|
|
39
40
|
clearTimeout(timer);
|
|
40
41
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { PropsWithChildren } from "react";
|
|
2
2
|
import { render, fireEvent } from "@testing-library/react";
|
|
3
3
|
import { useOnClickOutside } from "./useOnClickOutside";
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ describe("useOnClickOutside", () => {
|
|
|
8
8
|
|
|
9
9
|
it("should call the handler when clicking outside", () => {
|
|
10
10
|
// setup
|
|
11
|
-
const Component: React.FC = ({ children }) => {
|
|
11
|
+
const Component: React.FC<PropsWithChildren> = ({ children }) => {
|
|
12
12
|
const ref = useOnClickOutside<HTMLDivElement>(handler);
|
|
13
13
|
return <div ref={ref}>{children}</div>;
|
|
14
14
|
};
|
|
@@ -23,11 +23,11 @@ describe("useOnClickOutside", () => {
|
|
|
23
23
|
|
|
24
24
|
it("should not call the handler when clicking on the element", () => {
|
|
25
25
|
// setup
|
|
26
|
-
const Component: React.FC = ({ children }) => {
|
|
26
|
+
const Component: React.FC<PropsWithChildren> = ({ children }) => {
|
|
27
27
|
const ref = useOnClickOutside<HTMLDivElement>(handler);
|
|
28
28
|
return <div ref={ref}>{children}</div>;
|
|
29
29
|
};
|
|
30
|
-
const mutableRef: { current: HTMLDivElement } = { current:
|
|
30
|
+
const mutableRef: { current: HTMLDivElement | null } = { current: null };
|
|
31
31
|
render(
|
|
32
32
|
<Component>
|
|
33
33
|
<div ref={mutableRef}></div>
|
|
@@ -35,7 +35,9 @@ describe("useOnClickOutside", () => {
|
|
|
35
35
|
);
|
|
36
36
|
|
|
37
37
|
// act
|
|
38
|
-
|
|
38
|
+
if (mutableRef.current) {
|
|
39
|
+
fireEvent.click(mutableRef.current);
|
|
40
|
+
}
|
|
39
41
|
|
|
40
42
|
// verify
|
|
41
43
|
expect(handler).not.toHaveBeenCalled();
|
|
@@ -43,7 +45,7 @@ describe("useOnClickOutside", () => {
|
|
|
43
45
|
|
|
44
46
|
it("should unregister the event listener when unmounted", () => {
|
|
45
47
|
// setup
|
|
46
|
-
const Component: React.FC = ({ children }) => {
|
|
48
|
+
const Component: React.FC<PropsWithChildren> = ({ children }) => {
|
|
47
49
|
const ref = useOnClickOutside<HTMLDivElement>(handler);
|
|
48
50
|
return <div ref={ref}>{children}</div>;
|
|
49
51
|
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** @module @category Utility */
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import useSWR, { SWRConfiguration } from "swr";
|
|
4
|
+
import { type FetchConfig, openmrsFetch } from "@openmrs/esm-api";
|
|
5
|
+
import useAbortController from "./useAbortController";
|
|
6
|
+
|
|
7
|
+
export type ArgumentsTuple = [any, ...unknown[]];
|
|
8
|
+
export type Key = string | ArgumentsTuple | undefined | null;
|
|
9
|
+
export type UseOpenmrsSWROptions = {
|
|
10
|
+
abortController?: AbortController;
|
|
11
|
+
fetchInit?: FetchConfig;
|
|
12
|
+
url?: string | ((key: Key) => string);
|
|
13
|
+
swrConfig?: SWRConfiguration;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function getUrl(key: Key, url?: string | ((key: Key) => string)): string {
|
|
17
|
+
if (url) {
|
|
18
|
+
return typeof url === "function" ? url(key) : url;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (typeof key === "string") {
|
|
22
|
+
return key;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error(
|
|
26
|
+
`When using useOpenmrsSWR with a key that is not a string, you must provide a url() function that converts the key to a valid url. The key for this hook is ${key}.`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @beta
|
|
32
|
+
*
|
|
33
|
+
* This hook is intended to simplify using openmrsFetch in useSWR, while also ensuring that
|
|
34
|
+
* all useSWR usages properly use an abort controller, so that fetch requests are cancelled
|
|
35
|
+
* if the React component unmounts.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import { useOpenmrsSWR } from "@openmrs/esm-framework";
|
|
40
|
+
*
|
|
41
|
+
* function MyComponent() {
|
|
42
|
+
* const { data } = useOpenmrsSWR(key);
|
|
43
|
+
*
|
|
44
|
+
* return (
|
|
45
|
+
* // render something with data
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* Note that if you are using a complex SWR key you must provide a url function to the options parameter,
|
|
51
|
+
* which translates the key into a URL to be sent to `openmrsFetch()`
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* import { useOpenmrsSWR } from "@openmrs/esm-framework";
|
|
56
|
+
*
|
|
57
|
+
* function MyComponent() {
|
|
58
|
+
* const { data } = useOpenmrsSWR(['key', 'url'], { url: (key) => key[1] });
|
|
59
|
+
*
|
|
60
|
+
* return (
|
|
61
|
+
* // render something with data
|
|
62
|
+
* );
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
* @param key The SWR key to use
|
|
66
|
+
* @param options An object of optional parameters to provide, including a {@link FetchConfig} object
|
|
67
|
+
* to pass to {@link openmrsFetch} or options to pass to SWR
|
|
68
|
+
*/
|
|
69
|
+
export function useOpenmrsSWR(key: Key, options: UseOpenmrsSWROptions = {}) {
|
|
70
|
+
const { abortController, fetchInit, url, swrConfig } = options;
|
|
71
|
+
const ac = useAbortController();
|
|
72
|
+
const abortSignal = useMemo<AbortSignal>(
|
|
73
|
+
() => fetchInit?.signal ?? abortController?.signal ?? ac.signal,
|
|
74
|
+
[abortController?.signal, fetchInit?.signal, ac.signal]
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const fetcher = useCallback(
|
|
78
|
+
(key: Key) => {
|
|
79
|
+
const url_ = getUrl(key, url);
|
|
80
|
+
return openmrsFetch(url_, { ...fetchInit, signal: abortSignal });
|
|
81
|
+
},
|
|
82
|
+
[abortSignal, fetchInit, url]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return useSWR(key, fetcher, swrConfig);
|
|
86
|
+
}
|
package/src/useSession.test.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { Suspense } from "react";
|
|
2
2
|
import { act, render, screen } from "@testing-library/react";
|
|
3
3
|
import "@testing-library/jest-dom";
|
|
4
|
-
import { useSession, __cleanup } from "./useSession
|
|
4
|
+
import { useSession, __cleanup } from "./useSession";
|
|
5
5
|
import { createGlobalStore } from "@openmrs/esm-state";
|
|
6
6
|
import { SessionStore } from "@openmrs/esm-api";
|
|
7
7
|
|
|
File without changes
|
|
File without changes
|