@openmrs/esm-react-utils 5.1.0 → 5.1.1-pre.1003

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": "5.1.0",
3
+ "version": "5.1.1-pre.1003",
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
  "react-i18next": "11.x"
57
57
  },
58
58
  "devDependencies": {
59
- "@openmrs/esm-api": "^5.1.0",
60
- "@openmrs/esm-config": "^5.1.0",
61
- "@openmrs/esm-error-handling": "^5.1.0",
62
- "@openmrs/esm-extensions": "^5.1.0",
63
- "@openmrs/esm-feature-flags": "^5.1.0",
64
- "@openmrs/esm-globals": "^5.1.0",
59
+ "@openmrs/esm-api": "^5.1.1-pre.1003",
60
+ "@openmrs/esm-config": "^5.1.1-pre.1003",
61
+ "@openmrs/esm-error-handling": "^5.1.1-pre.1003",
62
+ "@openmrs/esm-extensions": "^5.1.1-pre.1003",
63
+ "@openmrs/esm-feature-flags": "^5.1.1-pre.1003",
64
+ "@openmrs/esm-globals": "^5.1.1-pre.1003",
65
65
  "dayjs": "^1.10.8",
66
66
  "i18next": "^19.6.0",
67
67
  "react": "^18.1.0",
@@ -70,5 +70,5 @@
70
70
  "rxjs": "^6.5.3",
71
71
  "webpack": "^5.88.0"
72
72
  },
73
- "gitHead": "b3a3b5208270447ef1fd85b2f7e4ce55e717b9ec"
73
+ "gitHead": "1bd2d52c33783c93102f9c696db84faf26932c82"
74
74
  }
@@ -1,8 +1,8 @@
1
1
  /** @module @category Navigation */
2
2
  import React, {
3
- MouseEvent,
4
- AnchorHTMLAttributes,
5
- PropsWithChildren,
3
+ type MouseEvent,
4
+ type AnchorHTMLAttributes,
5
+ type PropsWithChildren,
6
6
  } from "react";
7
7
  import { navigate, interpolateUrl, TemplateParams } from "@openmrs/esm-config";
8
8
 
package/src/Extension.tsx CHANGED
@@ -5,6 +5,7 @@ import React, {
5
5
  useEffect,
6
6
  useRef,
7
7
  useState,
8
+ type ReactElement,
8
9
  } from "react";
9
10
  import type { Parcel } from "single-spa";
10
11
  import { ComponentContext } from ".";
@@ -16,7 +17,7 @@ export type ExtensionProps = {
16
17
  wrap?(
17
18
  slot: React.ReactNode,
18
19
  extension: ExtensionData
19
- ): React.ReactElement<any, any> | null;
20
+ ): ReactElement<any, any> | null;
20
21
  } & Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
21
22
  children?:
22
23
  | React.ReactNode
@@ -1,5 +1,5 @@
1
1
  /** @module @category Framework */
2
- import React from "react";
2
+ import React, { ComponentType } from "react";
3
3
  import ReactDOMClient from "react-dom/client";
4
4
  import singleSpaReact from "single-spa-react";
5
5
  import {
@@ -8,7 +8,7 @@ import {
8
8
  } from "./openmrsComponentDecorator";
9
9
 
10
10
  export function getLifecycle<T>(
11
- Component: React.ComponentType<T>,
11
+ Component: ComponentType<T>,
12
12
  options: ComponentDecoratorOptions
13
13
  ) {
14
14
  return singleSpaReact({
@@ -19,7 +19,7 @@ export function getLifecycle<T>(
19
19
  }
20
20
 
21
21
  export function getAsyncLifecycle<T>(
22
- lazy: () => Promise<{ default: React.ComponentType<T> }>,
22
+ lazy: () => Promise<{ default: ComponentType<T> }>,
23
23
  options: ComponentDecoratorOptions
24
24
  ) {
25
25
  return () =>
@@ -27,7 +27,7 @@ export function getAsyncLifecycle<T>(
27
27
  }
28
28
 
29
29
  export function getSyncLifecycle<T>(
30
- Component: React.ComponentType<T>,
30
+ Component: ComponentType<T>,
31
31
  options: ComponentDecoratorOptions
32
32
  ) {
33
33
  return () => Promise.resolve(getLifecycle(Component, options));
package/src/index.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from "./ComponentContext";
2
2
  export * from "./ConfigurableLink";
3
- export * from "./createUseStore";
4
3
  export * from "./Extension";
5
4
  export * from "./ExtensionSlot";
6
5
  export * from "./getLifecycle";
@@ -1,4 +1,4 @@
1
- import React, { Suspense, useEffect, useRef } from "react";
1
+ import React, { type ComponentType, Suspense } from "react";
2
2
  import { I18nextProvider } from "react-i18next";
3
3
  import type {} from "@openmrs/esm-globals";
4
4
  import {
@@ -42,8 +42,8 @@ export function openmrsComponentDecorator(userOpts: ComponentDecoratorOptions) {
42
42
  const opts = Object.assign({}, defaultOpts, userOpts);
43
43
 
44
44
  return function decorateComponent(
45
- Comp: React.ComponentType<any>
46
- ): React.ComponentType<any> {
45
+ Comp: ComponentType<any>
46
+ ): ComponentType<any> {
47
47
  return class OpenmrsReactComponent extends React.Component<
48
48
  OpenmrsReactComponentProps,
49
49
  OpenmrsReactComponentState
package/src/public.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export { type ExtensionData } from "./ComponentContext";
2
2
  export * from "./ConfigurableLink";
3
- export * from "./createUseStore";
4
3
  export * from "./Extension";
5
4
  export * from "./ExtensionSlot";
6
5
  export * from "./getLifecycle";
@@ -20,6 +20,12 @@ function RenderConfig(props) {
20
20
  return <button>{config[props.configKey]}</button>;
21
21
  }
22
22
 
23
+ function RenderExternalConfig(props) {
24
+ const config = useConfig({ externalModuleName: props.externalModuleName });
25
+
26
+ return <button>{config[props.configKey]}</button>;
27
+ }
28
+
23
29
  function clearConfig() {
24
30
  mockConfigInternalStore.resetMock();
25
31
  }
@@ -297,4 +303,40 @@ describe(`useConfig in an extension`, () => {
297
303
  expect(screen.findByText("Yet another thing")).toBeTruthy()
298
304
  );
299
305
  });
306
+
307
+ it("can optionally load an external module's configuration", async () => {
308
+ defineConfigSchema("first-module", {
309
+ thing: {
310
+ _default: "first thing",
311
+ },
312
+ });
313
+
314
+ defineConfigSchema("second-module", {
315
+ thing: {
316
+ _default: "second thing",
317
+ },
318
+ });
319
+
320
+ render(
321
+ <React.Suspense fallback={<div>Suspense!</div>}>
322
+ <ComponentContext.Provider
323
+ value={{
324
+ moduleName: "first-module",
325
+ extension: {
326
+ extensionSlotName: "fooSlot",
327
+ extensionSlotModuleName: "slot-mod",
328
+ extensionId: "fooExt#id1",
329
+ },
330
+ }}
331
+ >
332
+ <RenderExternalConfig
333
+ externalModuleName="second-module"
334
+ configKey="thing"
335
+ />
336
+ </ComponentContext.Provider>
337
+ </React.Suspense>
338
+ );
339
+
340
+ await waitFor(() => expect(screen.findByText("second thing")).toBeTruthy());
341
+ });
300
342
  });
package/src/useConfig.ts CHANGED
@@ -157,16 +157,24 @@ function useNormalConfig(moduleName: string) {
157
157
  return state;
158
158
  }
159
159
 
160
+ interface UseConfigOptions {
161
+ /** An external module to load the configuration from. This option should only be used if
162
+ absolutely necessary as it can end up making frontend modules coupled to one another. */
163
+ externalModuleName?: string;
164
+ }
165
+
160
166
  /**
161
167
  * Use this React Hook to obtain your module's configuration.
168
+ *
169
+ * @param options Additional options that can be passed to useConfig()
162
170
  */
163
- export function useConfig<
164
- T = Omit<ConfigObject, "Display conditions" | "Translation overrides">
165
- >() {
171
+ export function useConfig<T = Record<string, any>>(options?: UseConfigOptions) {
166
172
  // This hook uses the config of the MF defining the component.
167
173
  // If the component is used in an extension slot then the slot
168
174
  // may override (part of) its configuration.
169
- const { moduleName, extension } = useContext(ComponentContext);
175
+ const { moduleName: contextModuleName, extension } =
176
+ useContext(ComponentContext);
177
+ const moduleName = options?.externalModuleName ?? contextModuleName;
170
178
 
171
179
  if (!moduleName && !extension) {
172
180
  throw Error(errorMessage);
@@ -175,11 +183,11 @@ export function useConfig<
175
183
  const normalConfig = useNormalConfig(moduleName);
176
184
  const extensionConfig = useExtensionConfig(extension);
177
185
  const config = useMemo(
178
- () => ({
179
- ...normalConfig,
180
- ...extensionConfig,
181
- }),
182
- [normalConfig, extensionConfig]
186
+ () =>
187
+ options?.externalModuleName && moduleName === options.externalModuleName
188
+ ? { ...normalConfig }
189
+ : { ...normalConfig, ...extensionConfig },
190
+ [moduleName, options?.externalModuleName, normalConfig, extensionConfig]
183
191
  );
184
192
 
185
193
  return config as T;
@@ -2,7 +2,7 @@ import {
2
2
  ExtensionInternalStore,
3
3
  getExtensionInternalStore,
4
4
  } from "@openmrs/esm-extensions";
5
- import { createUseStore } from "./createUseStore";
5
+ import { createUseStore } from "./useStore";
6
6
 
7
7
  /** @internal
8
8
  * @deprecated Use `useStore(getExtensionInternalStore())`
@@ -1,6 +1,6 @@
1
1
  /** @module @category Extension */
2
2
  import { ExtensionStore, getExtensionStore } from "@openmrs/esm-extensions";
3
- import { createUseStore } from "./createUseStore";
3
+ import { createUseStore } from "./useStore";
4
4
 
5
5
  export const useExtensionStore = createUseStore<ExtensionStore>(
6
6
  getExtensionStore()
@@ -1,5 +1,5 @@
1
1
  /** @module @category UI */
2
- import { useMemo, useState } from "react";
2
+ import { useCallback, useMemo, useState } from "react";
3
3
 
4
4
  const defaultResultsPerPage = 10;
5
5
 
@@ -22,25 +22,47 @@ export function usePagination<T>(
22
22
  return data.slice(lowerBound, upperBound);
23
23
  }, [data, page, resultsPerPage]);
24
24
 
25
- return {
26
- results,
27
- totalPages,
28
- currentPage: page,
29
- paginated: data.length > resultsPerPage,
30
- showNextButton: page < totalPages,
31
- showPreviousButton: page > 1,
32
- goTo(page: number) {
25
+ const goTo = useCallback(
26
+ (page: number) => {
33
27
  setPage(Math.max(1, Math.min(totalPages, page)));
34
28
  },
35
- goToNext() {
36
- if (page < totalPages) {
37
- setPage(page + 1);
38
- }
39
- },
40
- goToPrevious() {
41
- if (page > 1) {
42
- setPage(page - 1);
43
- }
44
- },
45
- };
29
+ [setPage, totalPages]
30
+ );
31
+ const goToNext = useCallback(() => {
32
+ if (page < totalPages) {
33
+ setPage(page + 1);
34
+ }
35
+ }, [page, totalPages, setPage]);
36
+
37
+ const goToPrevious = useCallback(() => {
38
+ if (page > 1) {
39
+ setPage(page - 1);
40
+ }
41
+ }, [page, setPage]);
42
+
43
+ const memoisedPaginatedData = useMemo(
44
+ () => ({
45
+ results,
46
+ totalPages,
47
+ currentPage: page,
48
+ paginated: data.length > resultsPerPage,
49
+ showNextButton: page < totalPages,
50
+ showPreviousButton: page > 1,
51
+ goTo,
52
+ goToNext,
53
+ goToPrevious,
54
+ }),
55
+ [
56
+ results,
57
+ totalPages,
58
+ data.length,
59
+ resultsPerPage,
60
+ page,
61
+ goTo,
62
+ goToNext,
63
+ goToPrevious,
64
+ ]
65
+ );
66
+
67
+ return memoisedPaginatedData;
46
68
  }
@@ -0,0 +1,50 @@
1
+ import { act, renderHook } from "@testing-library/react";
2
+ import { createGlobalStore } from "@openmrs/esm-state";
3
+ import { useStore, useStoreWithActions } from "@openmrs/esm-react-utils";
4
+
5
+ describe("useStore", () => {
6
+ it("updates state, selects, and correctly binds actions", () => {
7
+ const store = createGlobalStore("scoreboard", {
8
+ good: { count: 0 },
9
+ evil: { count: 0 },
10
+ });
11
+ const actions = {
12
+ tally: (state, team, number) => ({
13
+ [team]: { count: state[team].count + number },
14
+ }),
15
+ };
16
+
17
+ const { result } = renderHook(() =>
18
+ useStore(store, (state) => state.good, actions)
19
+ );
20
+
21
+ expect(result.current.count).toBe(0);
22
+ act(() => {
23
+ result.current.tally("good", 2);
24
+ });
25
+ expect(result.current.count).toBe(2);
26
+ });
27
+ });
28
+
29
+ describe("useStoreWithActions", () => {
30
+ it("should correctly bind actions", () => {
31
+ const store = createGlobalStore("counter", { count: 0 });
32
+ const actions = {
33
+ increment: (state) => ({ count: state.count + 1 }),
34
+ incrementBy: (state, number) => ({ count: state.count + number }),
35
+ };
36
+
37
+ const { result } = renderHook(() => useStoreWithActions(store, actions));
38
+
39
+ expect(result.current.count).toBe(0);
40
+ act(() => {
41
+ result.current.increment();
42
+ });
43
+ expect(store.getState().count).toBe(1);
44
+ expect(result.current.count).toBe(1);
45
+ act(() => {
46
+ result.current.incrementBy(3);
47
+ });
48
+ expect(result.current.count).toBe(4);
49
+ });
50
+ });
package/src/useStore.ts CHANGED
@@ -2,9 +2,14 @@
2
2
  import { subscribeTo } from "@openmrs/esm-state";
3
3
  import { useEffect, useMemo, useState } from "react";
4
4
  import type { StoreApi } from "zustand";
5
- import type { Actions, BoundActions } from "./createUseStore";
6
5
 
7
- function bindActions<T>(store: StoreApi<T>, actions: Actions): BoundActions {
6
+ export type ActionFunction<T> = (state: T, ...args: any[]) => Partial<T>;
7
+ export type Actions<T> =
8
+ | ((store: StoreApi<T>) => Record<string, ActionFunction<T>>)
9
+ | Record<string, ActionFunction<T>>;
10
+ export type BoundActions = { [key: string]: (...args: any[]) => void };
11
+
12
+ function bindActions<T>(store: StoreApi<T>, actions: Actions<T>): BoundActions {
8
13
  if (typeof actions == "function") {
9
14
  actions = actions(store);
10
15
  }
@@ -12,7 +17,7 @@ function bindActions<T>(store: StoreApi<T>, actions: Actions): BoundActions {
12
17
  const bound = {};
13
18
 
14
19
  for (let i in actions) {
15
- bound[i] = () => {
20
+ bound[i] = function () {
16
21
  const args = arguments;
17
22
  store.setState((state) => {
18
23
  let _args = [state];
@@ -35,17 +40,17 @@ function useStore<T, U>(store: StoreApi<T>, select: (state: T) => U): U;
35
40
  function useStore<T, U>(
36
41
  store: StoreApi<T>,
37
42
  select: undefined,
38
- actions: Actions
43
+ actions: Actions<T>
39
44
  ): T & BoundActions;
40
45
  function useStore<T, U>(
41
46
  store: StoreApi<T>,
42
47
  select: (state: T) => U,
43
- actions: Actions
48
+ actions: Actions<T>
44
49
  ): U & BoundActions;
45
50
  function useStore<T, U>(
46
51
  store: StoreApi<T>,
47
52
  select: (state: T) => U = defaultSelectFunction,
48
- actions?: Actions
53
+ actions?: Actions<T>
49
54
  ) {
50
55
  const [state, setState] = useState<U>(() => select(store.getState()));
51
56
  useEffect(() => subscribeTo(store, select, setState), [store, select]);
@@ -58,11 +63,39 @@ function useStore<T, U>(
58
63
  return { ...state, ...boundActions };
59
64
  }
60
65
 
66
+ /**
67
+ *
68
+ * @param store A zustand store
69
+ * @param actions
70
+ * @returns
71
+ */
61
72
  function useStoreWithActions<T>(
62
73
  store: StoreApi<T>,
63
- actions: Actions
74
+ actions: Actions<T>
64
75
  ): T & BoundActions {
65
76
  return useStore(store, defaultSelectFunction, actions);
66
77
  }
67
78
 
68
- export { useStore, useStoreWithActions };
79
+ /**
80
+ * Whenever possible, use `useStore(yourStore)` instead. This function is for creating a
81
+ * custom hook for a specific store.
82
+ */
83
+ function createUseStore<T>(store: StoreApi<T>) {
84
+ function useStore(): T;
85
+ function useStore(actions: Actions<T>): T & BoundActions;
86
+ function useStore(actions?: Actions<T>): T & BoundActions;
87
+ function useStore(actions?: Actions<T>) {
88
+ const [state, set] = useState(store.getState());
89
+ useEffect(() => store.subscribe((state) => set(state)), []);
90
+ let boundActions: BoundActions = useMemo(
91
+ () => (actions ? bindActions(store, actions) : {}),
92
+ [actions]
93
+ );
94
+
95
+ return { ...state, ...boundActions };
96
+ }
97
+
98
+ return useStore;
99
+ }
100
+
101
+ export { createUseStore, useStore, useStoreWithActions };
package/src/useVisit.ts CHANGED
@@ -17,17 +17,30 @@ export interface VisitReturnType {
17
17
  error: Error;
18
18
  mutate: () => void;
19
19
  isValidating: boolean;
20
+ activeVisit: Visit | null;
20
21
  currentVisit: Visit | null;
21
- isRetrospective: boolean;
22
+ currentVisitIsRetrospective: boolean;
22
23
  isLoading: boolean;
23
24
  }
24
25
 
25
26
  /**
26
- * This React hook returns a visit object. If the `patientUuid` is provided
27
- * as a parameter, then the currentVisit, error and mutate function
28
- * for that patient visit is returned.
27
+ * This React hook returns visit information if the patient UUID is not null. There are
28
+ * potentially two relevant visits at a time: "active" and "current".
29
+ *
30
+ * The active visit is the most recent visit without an end date. The presence of an active
31
+ * visit generally means that the patient is in the facility.
32
+ *
33
+ * The current visit is the active visit, unless a retrospective visit has been selected.
34
+ * If there is no active visit and no selected retrospective visit, then there is no
35
+ * current visit. If there is no active visit but there is a retrospective visit, then
36
+ * the retrospective visit is the current visit. `currentVisitIsRetrospective` tells you
37
+ * whether the current visit is a retrospective visit.
38
+ *
39
+ * The active visit and current visit require two separate API calls. `error` contains
40
+ * the error from either one, if there is an error. `isValidating` is true if either
41
+ * API call is in progress. `mutate` refreshes the data from both API calls.
42
+ *
29
43
  * @param patientUuid Unique patient identifier `string`
30
- * @returns Object {`error` `isValidating`, `currentVisit`, `mutate`}
31
44
  */
32
45
  export function useVisit(patientUuid: string): VisitReturnType {
33
46
  const { patientUuid: visitStorePatientUuid, manuallySetVisitUuid } = useStore(
@@ -38,31 +51,60 @@ export function useVisit(patientUuid: string): VisitReturnType {
38
51
  patientUuid && visitStorePatientUuid == patientUuid
39
52
  ? manuallySetVisitUuid
40
53
  : null;
41
- const visitGetUrlSuffix = retrospectiveVisitUuid
42
- ? `/${retrospectiveVisitUuid}`
43
- : `?patient=${patientUuid}&v=${defaultVisitCustomRepresentation}&includeInactive=false`;
44
- const { data, error, mutate, isValidating } = useSWR<{
54
+ const activeVisitUrlSuffix = `?patient=${patientUuid}&v=${defaultVisitCustomRepresentation}&includeInactive=false`;
55
+ const retrospectiveVisitUrlSuffix = `/${retrospectiveVisitUuid}`;
56
+
57
+ const {
58
+ data: activeData,
59
+ error: activeError,
60
+ mutate: activeMutate,
61
+ isValidating: activeIsValidating,
62
+ } = useSWR<{
45
63
  data: Visit | { results: Array<Visit> };
46
64
  }>(
47
- patientUuid ? `/ws/rest/v1/visit${visitGetUrlSuffix}` : null,
65
+ patientUuid ? `/ws/rest/v1/visit${activeVisitUrlSuffix}` : null,
48
66
  openmrsFetch
49
67
  );
50
68
 
51
- const currentVisit = useMemo(
69
+ const {
70
+ data: retroData,
71
+ error: retroError,
72
+ mutate: retroMutate,
73
+ isValidating: retroIsValidating,
74
+ } = useSWR<{
75
+ data: Visit | { results: Array<Visit> };
76
+ }>(
77
+ patientUuid && retrospectiveVisitUuid
78
+ ? `/ws/rest/v1/visit${retrospectiveVisitUrlSuffix}`
79
+ : null,
80
+ openmrsFetch
81
+ );
82
+
83
+ const activeVisit = useMemo(
52
84
  () =>
53
- retrospectiveVisitUuid
54
- ? data?.data
55
- : data?.data.results.find((visit) => visit.stopDatetime === null) ??
56
- null,
57
- [data]
85
+ activeData?.data.results.find((visit) => visit.stopDatetime === null) ??
86
+ null,
87
+ [activeData]
88
+ );
89
+
90
+ const currentVisit = useMemo(
91
+ () => (retrospectiveVisitUuid ? retroData?.data : activeVisit ?? null),
92
+ [retroData, activeVisit, retrospectiveVisitUuid]
58
93
  );
59
94
 
60
95
  return {
61
- error,
62
- mutate,
63
- isValidating,
96
+ error: activeError || retroError,
97
+ mutate: () => {
98
+ activeMutate();
99
+ retroMutate();
100
+ },
101
+ isValidating: activeIsValidating || retroIsValidating,
102
+ activeVisit,
64
103
  currentVisit,
65
- isRetrospective: Boolean(retrospectiveVisitUuid),
66
- isLoading: !data && !error,
104
+ currentVisitIsRetrospective: Boolean(retrospectiveVisitUuid),
105
+ isLoading: Boolean(
106
+ (!activeData || (retrospectiveVisitUuid && !retroData)) &&
107
+ (!activeError || !retroError)
108
+ ),
67
109
  };
68
110
  }
@@ -1,49 +0,0 @@
1
- /** @module @category Store */
2
- import { useEffect, useMemo, useState } from "react";
3
- import type { StoreApi } from "zustand";
4
-
5
- export type Actions = Function | Record<string, Function>;
6
- export type BoundActions = { [key: string]: (...args: any[]) => void };
7
-
8
- function bindActions<T>(store: StoreApi<T>, actions: Actions): BoundActions {
9
- if (typeof actions == "function") {
10
- actions = actions(store);
11
- }
12
-
13
- const bound = {};
14
-
15
- for (let i in actions) {
16
- bound[i] = () => {
17
- const args = arguments;
18
- store.setState((state) => {
19
- let _args = [state];
20
- for (let i = 0; i < args.length; i++) {
21
- _args.push(args[i]);
22
- }
23
-
24
- return actions[i](_args);
25
- });
26
- };
27
- }
28
-
29
- return bound;
30
- }
31
-
32
- /** Avoid this; generally prefer to have clients use `useStore(yourStore)` */
33
- export function createUseStore<T>(store: StoreApi<T>) {
34
- function useStore(): T;
35
- function useStore(actions: Actions): T & BoundActions;
36
- function useStore(actions?: Actions): T & BoundActions;
37
- function useStore(actions?: Actions) {
38
- const [state, set] = useState(store.getState());
39
- useEffect(() => store.subscribe((state) => set(state)), []);
40
- let boundActions: BoundActions = useMemo(
41
- () => (actions ? bindActions(store, actions) : {}),
42
- [actions]
43
- );
44
-
45
- return { ...state, ...boundActions };
46
- }
47
-
48
- return useStore;
49
- }