@medplum/react-hooks 2.1.9

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/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Medplum React Hooks Library
2
+
3
+ The Medplum React Hooks Library provides non-UI React features for your application.
4
+
5
+ Most users will want the full Medplum React Component Library, `@medplum/react`. However, that library has peer dependencies on Mantine, which may not be desired.
6
+
7
+ ## Key Features
8
+
9
+ - `useMedplum` - handles shared global instance of `MedplumClient`
10
+ - `useResource` - reads a resource by ID or reference with intelligent caching
11
+ - `useSearch` - performs a FHIR search with intelligent state management
12
+
13
+ ## Installation
14
+
15
+ Add as a dependency:
16
+
17
+ ```
18
+ npm install @medplum/react-hooks
19
+ ```
20
+
21
+ Note the following peer dependencies:
22
+
23
+ - [@medplum/core](https://www.npmjs.com/package/@medplum/core)
24
+ - [react](https://www.npmjs.com/package/react)
25
+ - [react-dom](https://www.npmjs.com/package/react-dom)
26
+
27
+ ## Setup
28
+
29
+ ```tsx
30
+ import { MedplumClient } from '@medplum/core';
31
+ import { MedplumProvider } from '@medplum/react';
32
+
33
+ const medplum = new MedplumClient();
34
+
35
+ export function App() {
36
+ return (
37
+ <MedplumProvider medplum={medplum}>
38
+ <MyPage1 />
39
+ <MyPage2 />
40
+ <Etc />
41
+ </MedplumProvider>
42
+ );
43
+ }
44
+ ```
45
+
46
+ For more details on how to setup `MedplumClient`, refer to the docs for [`medplum`](https://www.npmjs.com/package/medplum).
47
+
48
+ ## `useMedplum`
49
+
50
+ ```tsx
51
+ import { useMedplum } from '@medplum/react-hooks';
52
+
53
+ export function MyComponent() {
54
+ const medplum = useMedplum();
55
+ return <div>{JSON.stringify(medplum.getProfile())}</div>;
56
+ }
57
+ ```
58
+
59
+ ## About Medplum
60
+
61
+ Medplum is a healthcare platform that helps you quickly develop high-quality compliant applications. Medplum includes a FHIR server, React component library, and developer app.
62
+
63
+ ## License
64
+
65
+ Apache 2.0. Copyright &copy; Medplum 2023
@@ -0,0 +1,2 @@
1
+ "use strict";var S=Object.create;var m=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var N=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var Q=(t,e)=>{for(var n in e)m(t,n,{get:e[n],enumerable:!0})},C=(t,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of b(e))!F.call(t,o)&&o!==n&&m(t,o,{get:()=>e[o],enumerable:!(r=P(e,o))||r.enumerable});return t};var L=(t,e,n)=>(n=t!=null?S(N(t)):{},C(e||!t||!t.__esModule?m(n,"default",{value:t,enumerable:!0}):n,t)),k=t=>C(m({},"__esModule",{value:!0}),t);var j={};Q(j,{MedplumProvider:()=>I,reactContext:()=>R,useMedplum:()=>p,useMedplumContext:()=>x,useMedplumNavigate:()=>A,useMedplumProfile:()=>B,useResource:()=>z,useSearch:()=>J,useSearchOne:()=>U,useSearchResources:()=>X});module.exports=k(j);var s=L(require("react"));var l=require("react"),R=(0,l.createContext)(void 0);function x(){return(0,l.useContext)(R)}function p(){return x().medplum}function A(){return x().navigate}function B(){return x().profile}function I(t){let e=t.medplum,n=t.navigate??w,[r,o]=(0,s.useState)({profile:e.getProfile(),loading:!1});(0,s.useEffect)(()=>{function u(){o({...r,profile:e.getProfile()})}return e.addEventListener("change",u),()=>e.removeEventListener("change",u)},[e,r]);let i=(0,s.useMemo)(()=>({...r,medplum:e,navigate:n}),[r,e,n]);return s.default.createElement(R.Provider,{value:i},t.children)}function w(t){window.location.assign(t)}var c=require("@medplum/core"),d=require("react");function z(t,e){let n=p(),[r,o]=(0,d.useState)(K(n,t)),i=(0,d.useCallback)(u=>{(0,c.deepEquals)(u,r)||o(u)},[r,o]);return(0,d.useEffect)(()=>{i(K(n,t))},[n,t,i]),(0,d.useEffect)(()=>{let u=!0;return(0,c.isReference)(t)&&n.readReference(t).then(f=>{u&&i(f)}).catch(f=>{u&&(i(void 0),e&&e((0,c.normalizeOperationOutcome)(f)))}),()=>u=!1},[n,r,t,i,e]),r}function K(t,e){if(e){if((0,c.isResource)(e))return e;if((0,c.isReference)(e))return t.getCachedReference(e)}}var g=require("@medplum/core"),a=require("react");function J(t,e){return T("search",t,e)}function U(t,e){return T("searchOne",t,e)}function X(t,e){return T("searchResources",t,e)}function T(t,e,n){let r=p(),[o,i]=(0,a.useState)(),[u,f]=(0,a.useState)(!1),[v,h]=(0,a.useState)(),[E,y]=(0,a.useState)();return(0,a.useEffect)(()=>{let O=r.fhirSearchUrl(e,n).toString();O!==o&&(i(O),r[t](e,n).then(M=>{f(!1),h(M),y(g.allOk)}).catch(M=>{f(!1),h(void 0),y((0,g.normalizeOperationOutcome)(M))}))},[r,t,e,n,o,h]),[v,u,E]}
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/index.ts", "../../src/MedplumProvider/MedplumProvider.tsx", "../../src/MedplumProvider/MedplumProvider.context.ts", "../../src/useResource/useResource.ts", "../../src/useSearch/useSearch.ts"],
4
+ "sourcesContent": ["export * from './MedplumProvider/MedplumProvider';\nexport * from './MedplumProvider/MedplumProvider.context';\nexport * from './useResource/useResource';\nexport * from './useSearch/useSearch';\n", "import { MedplumClient } from '@medplum/core';\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { MepdlumNavigateFunction, reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n medplum: MedplumClient;\n navigate?: MepdlumNavigateFunction;\n children: React.ReactNode;\n}\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: false,\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState({\n ...state,\n profile: medplum.getProfile(),\n });\n }\n\n medplum.addEventListener('change', eventListener);\n return () => medplum.removeEventListener('change', eventListener);\n }, [medplum, state]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "import { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MepdlumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MepdlumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MepdlumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "import { deepEquals, isReference, isResource, MedplumClient, normalizeOperationOutcome } from '@medplum/core';\nimport { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value The resource or reference to resource.\n * @param setOutcome Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | T | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): T | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<T | undefined>(getInitialResource(medplum, value));\n\n const setResourceIfChanged = useCallback(\n (r: T | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource, setResource]\n );\n\n useEffect(() => {\n setResourceIfChanged(getInitialResource(medplum, value));\n }, [medplum, value, setResourceIfChanged]);\n\n useEffect(() => {\n let subscribed = true;\n\n if (isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, resource, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum The medplum client.\n * @param value The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | T | undefined\n): T | undefined {\n if (value) {\n if (isResource(value)) {\n return value;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "import { allOk, normalizeOperationOutcome, QueryTypes, ResourceArray } from '@medplum/core';\nimport { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType The FHIR resource type to search.\n * @param query Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType The FHIR resource type to search.\n * @param query Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType The FHIR resource type to search.\n * @param query Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query);\n}\n\nfunction useSearchImpl<K extends ResourceType, ReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined\n): [ReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [searchKey, setSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(false);\n const [result, setResult] = useState<ReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n useEffect(() => {\n const key = medplum.fhirSearchUrl(resourceType, query).toString();\n if (key !== searchKey) {\n setSearchKey(key);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as ReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, searchKey, setResult]);\n\n return [result, loading, outcome];\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["src_exports", "__export", "MedplumProvider", "reactContext", "useMedplum", "useMedplumContext", "useMedplumNavigate", "useMedplumProfile", "useResource", "useSearch", "useSearchOne", "useSearchResources", "__toCommonJS", "import_react", "import_react", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "eventListener", "medplumContext", "React", "reactContext", "path", "import_core", "import_react", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "getInitialResource", "setResourceIfChanged", "r", "subscribed", "err", "import_core", "import_react", "useSearch", "resourceType", "query", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "searchKey", "setSearchKey", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "key", "res", "err"]
7
+ }
@@ -0,0 +1 @@
1
+ {"type": "commonjs"}
@@ -0,0 +1,2 @@
1
+ import K,{useEffect as v,useMemo as E,useState as S}from"react";import{createContext as O,useContext as C}from"react";var p=O(void 0);function m(){return C(p)}function s(){return m().medplum}function w(){return m().navigate}function z(){return m().profile}function D(t){let e=t.medplum,r=t.navigate??P,[n,i]=S({profile:e.getProfile(),loading:!1});v(()=>{function o(){i({...n,profile:e.getProfile()})}return e.addEventListener("change",o),()=>e.removeEventListener("change",o)},[e,n]);let u=E(()=>({...n,medplum:e,navigate:r}),[n,e,r]);return K.createElement(p.Provider,{value:u},t.children)}function P(t){window.location.assign(t)}import{deepEquals as b,isReference as M,isResource as N,normalizeOperationOutcome as F}from"@medplum/core";import{useCallback as Q,useEffect as g,useState as L}from"react";function Z(t,e){let r=s(),[n,i]=L(h(r,t)),u=Q(o=>{b(o,n)||i(o)},[n,i]);return g(()=>{u(h(r,t))},[r,t,u]),g(()=>{let o=!0;return M(t)&&r.readReference(t).then(c=>{o&&u(c)}).catch(c=>{o&&(u(void 0),e&&e(F(c)))}),()=>o=!1},[r,n,t,u,e]),n}function h(t,e){if(e){if(N(e))return e;if(M(e))return t.getCachedReference(e)}}import{allOk as k,normalizeOperationOutcome as A}from"@medplum/core";import{useEffect as B,useState as d}from"react";function re(t,e){return l("search",t,e)}function oe(t,e){return l("searchOne",t,e)}function ue(t,e){return l("searchResources",t,e)}function l(t,e,r){let n=s(),[i,u]=d(),[o,c]=d(!1),[T,a]=d(),[y,R]=d();return B(()=>{let x=n.fhirSearchUrl(e,r).toString();x!==i&&(u(x),n[t](e,r).then(f=>{c(!1),a(f),R(k)}).catch(f=>{c(!1),a(void 0),R(A(f))}))},[n,t,e,r,i,a]),[T,o,y]}export{D as MedplumProvider,p as reactContext,s as useMedplum,m as useMedplumContext,w as useMedplumNavigate,z as useMedplumProfile,Z as useResource,re as useSearch,oe as useSearchOne,ue as useSearchResources};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/MedplumProvider/MedplumProvider.tsx", "../../src/MedplumProvider/MedplumProvider.context.ts", "../../src/useResource/useResource.ts", "../../src/useSearch/useSearch.ts"],
4
+ "sourcesContent": ["import { MedplumClient } from '@medplum/core';\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { MepdlumNavigateFunction, reactContext } from './MedplumProvider.context';\n\nexport interface MedplumProviderProps {\n medplum: MedplumClient;\n navigate?: MepdlumNavigateFunction;\n children: React.ReactNode;\n}\n\n/**\n * The MedplumProvider component provides Medplum context state.\n *\n * Medplum context includes:\n * 1) medplum - Medplum client library\n * 2) profile - The current user profile (if signed in)\n * @param props The MedplumProvider React props.\n * @returns The MedplumProvider React node.\n */\nexport function MedplumProvider(props: MedplumProviderProps): JSX.Element {\n const medplum = props.medplum;\n const navigate = props.navigate ?? defaultNavigate;\n\n const [state, setState] = useState({\n profile: medplum.getProfile(),\n loading: false,\n });\n\n useEffect(() => {\n function eventListener(): void {\n setState({\n ...state,\n profile: medplum.getProfile(),\n });\n }\n\n medplum.addEventListener('change', eventListener);\n return () => medplum.removeEventListener('change', eventListener);\n }, [medplum, state]);\n\n const medplumContext = useMemo(\n () => ({\n ...state,\n medplum,\n navigate,\n }),\n [state, medplum, navigate]\n );\n\n return <reactContext.Provider value={medplumContext}>{props.children}</reactContext.Provider>;\n}\n\n/**\n * The default \"navigate\" function which simply uses window.location.href.\n * @param path The path to navigate to.\n */\nfunction defaultNavigate(path: string): void {\n window.location.assign(path);\n}\n", "import { MedplumClient, ProfileResource } from '@medplum/core';\nimport { createContext, useContext } from 'react';\n\nexport const reactContext = createContext(undefined as MedplumContext | undefined);\n\nexport type MepdlumNavigateFunction = (path: string) => void;\n\nexport interface MedplumContext {\n medplum: MedplumClient;\n navigate: MepdlumNavigateFunction;\n profile?: ProfileResource;\n loading: boolean;\n}\n\n/**\n * Returns the MedplumContext instance.\n * @returns The MedplumContext instance.\n */\nexport function useMedplumContext(): MedplumContext {\n return useContext(reactContext) as MedplumContext;\n}\n\n/**\n * Returns the MedplumClient instance.\n * This is a shortcut for useMedplumContext().medplum.\n * @returns The MedplumClient instance.\n */\nexport function useMedplum(): MedplumClient {\n return useMedplumContext().medplum;\n}\n\n/**\n * Returns the Medplum navigate function.\n * @returns The Medplum navigate function.\n */\nexport function useMedplumNavigate(): MepdlumNavigateFunction {\n return useMedplumContext().navigate;\n}\n\n/**\n * Returns the current Medplum user profile (if signed in).\n * This is a shortcut for useMedplumContext().profile.\n * @returns The current user profile.\n */\nexport function useMedplumProfile(): ProfileResource | undefined {\n return useMedplumContext().profile;\n}\n", "import { deepEquals, isReference, isResource, MedplumClient, normalizeOperationOutcome } from '@medplum/core';\nimport { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';\nimport { useCallback, useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\n/**\n * React Hook to use a FHIR reference.\n * Handles the complexity of resolving references and caching resources.\n * @param value The resource or reference to resource.\n * @param setOutcome Optional callback to set the OperationOutcome.\n * @returns The resolved resource.\n */\nexport function useResource<T extends Resource>(\n value: Reference<T> | T | undefined,\n setOutcome?: (outcome: OperationOutcome) => void\n): T | undefined {\n const medplum = useMedplum();\n const [resource, setResource] = useState<T | undefined>(getInitialResource(medplum, value));\n\n const setResourceIfChanged = useCallback(\n (r: T | undefined) => {\n if (!deepEquals(r, resource)) {\n setResource(r);\n }\n },\n [resource, setResource]\n );\n\n useEffect(() => {\n setResourceIfChanged(getInitialResource(medplum, value));\n }, [medplum, value, setResourceIfChanged]);\n\n useEffect(() => {\n let subscribed = true;\n\n if (isReference(value)) {\n medplum\n .readReference(value as Reference<T>)\n .then((r) => {\n if (subscribed) {\n setResourceIfChanged(r);\n }\n })\n .catch((err) => {\n if (subscribed) {\n setResourceIfChanged(undefined);\n if (setOutcome) {\n setOutcome(normalizeOperationOutcome(err));\n }\n }\n });\n }\n\n return (() => (subscribed = false)) as () => void;\n }, [medplum, resource, value, setResourceIfChanged, setOutcome]);\n\n return resource;\n}\n\n/**\n * Returns the initial resource value based on the input value.\n * If the input value is a resource, returns the resource.\n * If the input value is a reference to a resource available in the cache, returns the resource.\n * Otherwise, returns undefined.\n * @param medplum The medplum client.\n * @param value The resource or reference to resource.\n * @returns An initial resource if available; undefined otherwise.\n */\nfunction getInitialResource<T extends Resource>(\n medplum: MedplumClient,\n value: Reference<T> | T | undefined\n): T | undefined {\n if (value) {\n if (isResource(value)) {\n return value;\n }\n\n if (isReference(value)) {\n return medplum.getCachedReference(value as Reference<T>);\n }\n }\n\n return undefined;\n}\n", "import { allOk, normalizeOperationOutcome, QueryTypes, ResourceArray } from '@medplum/core';\nimport { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';\nimport { useEffect, useState } from 'react';\nimport { useMedplum } from '../MedplumProvider/MedplumProvider.context';\n\ntype SearchFn = 'search' | 'searchOne' | 'searchResources';\n\n/**\n * React hook for searching FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.search() method.\n *\n * @param resourceType The FHIR resource type to search.\n * @param query Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearch<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, Bundle<ExtractResource<K>>>('search', resourceType, query);\n}\n\n/**\n * React hook for searching for a single FHIR resource.\n *\n * This is a convenience hook for calling the MedplumClient.searchOne() method.\n *\n * @param resourceType The FHIR resource type to search.\n * @param query Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchOne<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ExtractResource<K>>('searchOne', resourceType, query);\n}\n\n/**\n * React hook for searching for an array of FHIR resources.\n *\n * This is a convenience hook for calling the MedplumClient.searchResources() method.\n *\n * @param resourceType The FHIR resource type to search.\n * @param query Optional search parameters.\n * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.\n */\nexport function useSearchResources<K extends ResourceType>(\n resourceType: K,\n query?: QueryTypes\n): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined] {\n return useSearchImpl<K, ResourceArray<ExtractResource<K>>>('searchResources', resourceType, query);\n}\n\nfunction useSearchImpl<K extends ResourceType, ReturnType>(\n searchFn: SearchFn,\n resourceType: K,\n query: QueryTypes | undefined\n): [ReturnType | undefined, boolean, OperationOutcome | undefined] {\n const medplum = useMedplum();\n const [searchKey, setSearchKey] = useState<string>();\n const [loading, setLoading] = useState<boolean>(false);\n const [result, setResult] = useState<ReturnType>();\n const [outcome, setOutcome] = useState<OperationOutcome>();\n\n useEffect(() => {\n const key = medplum.fhirSearchUrl(resourceType, query).toString();\n if (key !== searchKey) {\n setSearchKey(key);\n medplum[searchFn](resourceType, query)\n .then((res) => {\n setLoading(false);\n setResult(res as ReturnType);\n setOutcome(allOk);\n })\n .catch((err) => {\n setLoading(false);\n setResult(undefined);\n setOutcome(normalizeOperationOutcome(err));\n });\n }\n }, [medplum, searchFn, resourceType, query, searchKey, setResult]);\n\n return [result, loading, outcome];\n}\n"],
5
+ "mappings": "AACA,OAAOA,GAAS,aAAAC,EAAW,WAAAC,EAAS,YAAAC,MAAgB,QCApD,OAAS,iBAAAC,EAAe,cAAAC,MAAkB,QAEnC,IAAMC,EAAeF,EAAc,MAAuC,EAe1E,SAASG,GAAoC,CAClD,OAAOF,EAAWC,CAAY,CAChC,CAOO,SAASE,GAA4B,CAC1C,OAAOD,EAAkB,EAAE,OAC7B,CAMO,SAASE,GAA8C,CAC5D,OAAOF,EAAkB,EAAE,QAC7B,CAOO,SAASG,GAAiD,CAC/D,OAAOH,EAAkB,EAAE,OAC7B,CD3BO,SAASI,EAAgBC,EAA0C,CACxE,IAAMC,EAAUD,EAAM,QAChBE,EAAWF,EAAM,UAAYG,EAE7B,CAACC,EAAOC,CAAQ,EAAIC,EAAS,CACjC,QAASL,EAAQ,WAAW,EAC5B,QAAS,EACX,CAAC,EAEDM,EAAU,IAAM,CACd,SAASC,GAAsB,CAC7BH,EAAS,CACP,GAAGD,EACH,QAASH,EAAQ,WAAW,CAC9B,CAAC,CACH,CAEA,OAAAA,EAAQ,iBAAiB,SAAUO,CAAa,EACzC,IAAMP,EAAQ,oBAAoB,SAAUO,CAAa,CAClE,EAAG,CAACP,EAASG,CAAK,CAAC,EAEnB,IAAMK,EAAiBC,EACrB,KAAO,CACL,GAAGN,EACH,QAAAH,EACA,SAAAC,CACF,GACA,CAACE,EAAOH,EAASC,CAAQ,CAC3B,EAEA,OAAOS,EAAA,cAACC,EAAa,SAAb,CAAsB,MAAOH,GAAiBT,EAAM,QAAS,CACvE,CAMA,SAASG,EAAgBU,EAAoB,CAC3C,OAAO,SAAS,OAAOA,CAAI,CAC7B,CE1DA,OAAS,cAAAC,EAAY,eAAAC,EAAa,cAAAC,EAA2B,6BAAAC,MAAiC,gBAE9F,OAAS,eAAAC,EAAa,aAAAC,EAAW,YAAAC,MAAgB,QAU1C,SAASC,EACdC,EACAC,EACe,CACf,IAAMC,EAAUC,EAAW,EACrB,CAACC,EAAUC,CAAW,EAAIC,EAAwBC,EAAmBL,EAASF,CAAK,CAAC,EAEpFQ,EAAuBC,EAC1BC,GAAqB,CACfC,EAAWD,EAAGN,CAAQ,GACzBC,EAAYK,CAAC,CAEjB,EACA,CAACN,EAAUC,CAAW,CACxB,EAEA,OAAAO,EAAU,IAAM,CACdJ,EAAqBD,EAAmBL,EAASF,CAAK,CAAC,CACzD,EAAG,CAACE,EAASF,EAAOQ,CAAoB,CAAC,EAEzCI,EAAU,IAAM,CACd,IAAIC,EAAa,GAEjB,OAAIC,EAAYd,CAAK,GACnBE,EACG,cAAcF,CAAqB,EACnC,KAAMU,GAAM,CACPG,GACFL,EAAqBE,CAAC,CAE1B,CAAC,EACA,MAAOK,GAAQ,CACVF,IACFL,EAAqB,MAAS,EAC1BP,GACFA,EAAWe,EAA0BD,CAAG,CAAC,EAG/C,CAAC,EAGG,IAAOF,EAAa,EAC9B,EAAG,CAACX,EAASE,EAAUJ,EAAOQ,EAAsBP,CAAU,CAAC,EAExDG,CACT,CAWA,SAASG,EACPL,EACAF,EACe,CACf,GAAIA,EAAO,CACT,GAAIiB,EAAWjB,CAAK,EAClB,OAAOA,EAGT,GAAIc,EAAYd,CAAK,EACnB,OAAOE,EAAQ,mBAAmBF,CAAqB,CAE3D,CAGF,CCnFA,OAAS,SAAAkB,EAAO,6BAAAC,MAA4D,gBAE5E,OAAS,aAAAC,EAAW,YAAAC,MAAgB,QAc7B,SAASC,GACdC,EACAC,EACiF,CACjF,OAAOC,EAA6C,SAAUF,EAAcC,CAAK,CACnF,CAWO,SAASE,GACdH,EACAC,EACyE,CACzE,OAAOC,EAAqC,YAAaF,EAAcC,CAAK,CAC9E,CAWO,SAASG,GACdJ,EACAC,EACwF,CACxF,OAAOC,EAAoD,kBAAmBF,EAAcC,CAAK,CACnG,CAEA,SAASC,EACPG,EACAL,EACAC,EACiE,CACjE,IAAMK,EAAUC,EAAW,EACrB,CAACC,EAAWC,CAAY,EAAIC,EAAiB,EAC7C,CAACC,EAASC,CAAU,EAAIF,EAAkB,EAAK,EAC/C,CAACG,EAAQC,CAAS,EAAIJ,EAAqB,EAC3C,CAACK,EAASC,CAAU,EAAIN,EAA2B,EAEzD,OAAAO,EAAU,IAAM,CACd,IAAMC,EAAMZ,EAAQ,cAAcN,EAAcC,CAAK,EAAE,SAAS,EAC5DiB,IAAQV,IACVC,EAAaS,CAAG,EAChBZ,EAAQD,CAAQ,EAAEL,EAAcC,CAAK,EAClC,KAAMkB,GAAQ,CACbP,EAAW,EAAK,EAChBE,EAAUK,CAAiB,EAC3BH,EAAWI,CAAK,CAClB,CAAC,EACA,MAAOC,GAAQ,CACdT,EAAW,EAAK,EAChBE,EAAU,MAAS,EACnBE,EAAWM,EAA0BD,CAAG,CAAC,CAC3C,CAAC,EAEP,EAAG,CAACf,EAASD,EAAUL,EAAcC,EAAOO,EAAWM,CAAS,CAAC,EAE1D,CAACD,EAAQF,EAASI,CAAO,CAClC",
6
+ "names": ["React", "useEffect", "useMemo", "useState", "createContext", "useContext", "reactContext", "useMedplumContext", "useMedplum", "useMedplumNavigate", "useMedplumProfile", "MedplumProvider", "props", "medplum", "navigate", "defaultNavigate", "state", "setState", "useState", "useEffect", "eventListener", "medplumContext", "useMemo", "React", "reactContext", "path", "deepEquals", "isReference", "isResource", "normalizeOperationOutcome", "useCallback", "useEffect", "useState", "useResource", "value", "setOutcome", "medplum", "useMedplum", "resource", "setResource", "useState", "getInitialResource", "setResourceIfChanged", "useCallback", "r", "deepEquals", "useEffect", "subscribed", "isReference", "err", "normalizeOperationOutcome", "isResource", "allOk", "normalizeOperationOutcome", "useEffect", "useState", "useSearch", "resourceType", "query", "useSearchImpl", "useSearchOne", "useSearchResources", "searchFn", "medplum", "useMedplum", "searchKey", "setSearchKey", "useState", "loading", "setLoading", "result", "setResult", "outcome", "setOutcome", "useEffect", "key", "res", "allOk", "err", "normalizeOperationOutcome"]
7
+ }
@@ -0,0 +1 @@
1
+ {"type": "module"}
@@ -0,0 +1,32 @@
1
+ /// <reference types="react" />
2
+ import { MedplumClient, ProfileResource } from '@medplum/core';
3
+ export declare const reactContext: import("react").Context<MedplumContext | undefined>;
4
+ export type MepdlumNavigateFunction = (path: string) => void;
5
+ export interface MedplumContext {
6
+ medplum: MedplumClient;
7
+ navigate: MepdlumNavigateFunction;
8
+ profile?: ProfileResource;
9
+ loading: boolean;
10
+ }
11
+ /**
12
+ * Returns the MedplumContext instance.
13
+ * @returns The MedplumContext instance.
14
+ */
15
+ export declare function useMedplumContext(): MedplumContext;
16
+ /**
17
+ * Returns the MedplumClient instance.
18
+ * This is a shortcut for useMedplumContext().medplum.
19
+ * @returns The MedplumClient instance.
20
+ */
21
+ export declare function useMedplum(): MedplumClient;
22
+ /**
23
+ * Returns the Medplum navigate function.
24
+ * @returns The Medplum navigate function.
25
+ */
26
+ export declare function useMedplumNavigate(): MepdlumNavigateFunction;
27
+ /**
28
+ * Returns the current Medplum user profile (if signed in).
29
+ * This is a shortcut for useMedplumContext().profile.
30
+ * @returns The current user profile.
31
+ */
32
+ export declare function useMedplumProfile(): ProfileResource | undefined;
@@ -0,0 +1,18 @@
1
+ import { MedplumClient } from '@medplum/core';
2
+ import React from 'react';
3
+ import { MepdlumNavigateFunction } from './MedplumProvider.context';
4
+ export interface MedplumProviderProps {
5
+ medplum: MedplumClient;
6
+ navigate?: MepdlumNavigateFunction;
7
+ children: React.ReactNode;
8
+ }
9
+ /**
10
+ * The MedplumProvider component provides Medplum context state.
11
+ *
12
+ * Medplum context includes:
13
+ * 1) medplum - Medplum client library
14
+ * 2) profile - The current user profile (if signed in)
15
+ * @param props The MedplumProvider React props.
16
+ * @returns The MedplumProvider React node.
17
+ */
18
+ export declare function MedplumProvider(props: MedplumProviderProps): JSX.Element;
@@ -0,0 +1,4 @@
1
+ export * from './MedplumProvider/MedplumProvider';
2
+ export * from './MedplumProvider/MedplumProvider.context';
3
+ export * from './useResource/useResource';
4
+ export * from './useSearch/useSearch';
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,9 @@
1
+ import { OperationOutcome, Reference, Resource } from '@medplum/fhirtypes';
2
+ /**
3
+ * React Hook to use a FHIR reference.
4
+ * Handles the complexity of resolving references and caching resources.
5
+ * @param value The resource or reference to resource.
6
+ * @param setOutcome Optional callback to set the OperationOutcome.
7
+ * @returns The resolved resource.
8
+ */
9
+ export declare function useResource<T extends Resource>(value: Reference<T> | T | undefined, setOutcome?: (outcome: OperationOutcome) => void): T | undefined;
@@ -0,0 +1,32 @@
1
+ import { QueryTypes, ResourceArray } from '@medplum/core';
2
+ import { Bundle, ExtractResource, OperationOutcome, ResourceType } from '@medplum/fhirtypes';
3
+ /**
4
+ * React hook for searching FHIR resources.
5
+ *
6
+ * This is a convenience hook for calling the MedplumClient.search() method.
7
+ *
8
+ * @param resourceType The FHIR resource type to search.
9
+ * @param query Optional search parameters.
10
+ * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.
11
+ */
12
+ export declare function useSearch<K extends ResourceType>(resourceType: K, query?: QueryTypes): [Bundle<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined];
13
+ /**
14
+ * React hook for searching for a single FHIR resource.
15
+ *
16
+ * This is a convenience hook for calling the MedplumClient.searchOne() method.
17
+ *
18
+ * @param resourceType The FHIR resource type to search.
19
+ * @param query Optional search parameters.
20
+ * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.
21
+ */
22
+ export declare function useSearchOne<K extends ResourceType>(resourceType: K, query?: QueryTypes): [ExtractResource<K> | undefined, boolean, OperationOutcome | undefined];
23
+ /**
24
+ * React hook for searching for an array of FHIR resources.
25
+ *
26
+ * This is a convenience hook for calling the MedplumClient.searchResources() method.
27
+ *
28
+ * @param resourceType The FHIR resource type to search.
29
+ * @param query Optional search parameters.
30
+ * @returns A 3-element tuple containing the search result, loading flag, and operation outcome.
31
+ */
32
+ export declare function useSearchResources<K extends ResourceType>(resourceType: K, query?: QueryTypes): [ResourceArray<ExtractResource<K>> | undefined, boolean, OperationOutcome | undefined];
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@medplum/react-hooks",
3
+ "version": "2.1.9",
4
+ "description": "Medplum React Hooks Library",
5
+ "author": "Medplum <hello@medplum.com>",
6
+ "license": "Apache-2.0",
7
+ "homepage": "https://www.medplum.com/",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/medplum/medplum.git",
11
+ "directory": "packages/react-hooks"
12
+ },
13
+ "engines": {
14
+ "node": ">=18.0.0"
15
+ },
16
+ "scripts": {
17
+ "clean": "rimraf dist",
18
+ "build": "npm run clean && tsc --project tsconfig.build.json && node esbuild.mjs",
19
+ "test": "jest"
20
+ },
21
+ "devDependencies": {
22
+ "@medplum/core": "*",
23
+ "@medplum/definitions": "*",
24
+ "@medplum/fhirtypes": "*",
25
+ "@medplum/mock": "*",
26
+ "@testing-library/dom": "9.3.3",
27
+ "@testing-library/jest-dom": "6.1.4",
28
+ "@testing-library/react": "14.0.0",
29
+ "@types/jest": "29.5.5",
30
+ "@types/node": "20.8.6",
31
+ "@types/react": "18.2.28",
32
+ "@types/react-dom": "18.2.13",
33
+ "jest": "29.7.0",
34
+ "jest-each": "29.7.0",
35
+ "react": "18.2.0",
36
+ "react-dom": "18.2.0",
37
+ "rimraf": "5.0.5",
38
+ "typescript": "5.2.2"
39
+ },
40
+ "peerDependencies": {
41
+ "@medplum/core": "*",
42
+ "react": "^17.0.2 || ^18.0.0",
43
+ "react-dom": "^17.0.2 || ^18.0.0"
44
+ },
45
+ "main": "dist/cjs/index.cjs",
46
+ "module": "dist/esm/index.mjs",
47
+ "types": "dist/types/index.d.ts",
48
+ "exports": {
49
+ "types": "./dist/types/index.d.ts",
50
+ "require": "./dist/cjs/index.cjs",
51
+ "import": "./dist/esm/index.mjs"
52
+ },
53
+ "sideEffects": false,
54
+ "keywords": [
55
+ "medplum",
56
+ "fhir",
57
+ "healthcare",
58
+ "interoperability",
59
+ "json",
60
+ "serialization",
61
+ "hl7",
62
+ "standards",
63
+ "clinical",
64
+ "dstu2",
65
+ "stu3",
66
+ "r4",
67
+ "normative",
68
+ "ui",
69
+ "input",
70
+ "react",
71
+ "react-component"
72
+ ]
73
+ }