@faasjs/react 3.0.0 → 3.1.1

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 CHANGED
@@ -15,6 +15,11 @@ React plugin for FaasJS.
15
15
  - Compatible with [why-did-you-render](https://github.com/welldone-software/why-did-you-render).
16
16
  - Additional React functions:
17
17
  - Utils:
18
+ - [equal](functions/equal.md): Compare two values for deep equality.
19
+ - [useEqualMemoize](functions/useEqualMemoize.md): Memoize a value with deep equality.
20
+ - [useEqualEffect](functions/useEqualEffect.md): Run an effect with deep equality.
21
+ - [useEqualMemo](functions/useEqualMemo.md): Memoize a value with deep equality.
22
+ - [useEqualCallback](functions/useEqualCallback.md): Memoize a callback with deep equality.
18
23
  - [useConstant](functions/useConstant.md): Create a constant value with hooks.
19
24
  - [createSplittingContext](functions/createSplittingContext.md): Create a context for code splitting.
20
25
  - [OptionalWrapper](functions/OptionalWrapper.md): Render a component optionally.
@@ -34,12 +39,17 @@ npm install @faasjs/react
34
39
  ## Functions
35
40
 
36
41
  - [createSplittingContext](functions/createSplittingContext.md)
42
+ - [equal](functions/equal.md)
37
43
  - [faas](functions/faas.md)
38
44
  - [FaasDataWrapper](functions/FaasDataWrapper.md)
39
45
  - [FaasReactClient](functions/FaasReactClient.md)
40
46
  - [getClient](functions/getClient.md)
41
47
  - [OptionalWrapper](functions/OptionalWrapper.md)
42
48
  - [useConstant](functions/useConstant.md)
49
+ - [useEqualCallback](functions/useEqualCallback.md)
50
+ - [useEqualEffect](functions/useEqualEffect.md)
51
+ - [useEqualMemo](functions/useEqualMemo.md)
52
+ - [useEqualMemoize](functions/useEqualMemoize.md)
43
53
  - [useFaas](functions/useFaas.md)
44
54
  - [withFaasData](functions/withFaasData.md)
45
55
 
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FaasAction, FaasData, FaasParams } from '@faasjs/types';
2
2
  export { FaasAction, FaasData, FaasParams } from '@faasjs/types';
3
- import { Response, ResponseError, BaseUrl, Options, FaasBrowserClient } from '@faasjs/browser';
3
+ import { Response, BaseUrl, ResponseError, Options, FaasBrowserClient } from '@faasjs/browser';
4
4
  export { Options, Response, ResponseError, ResponseHeaders } from '@faasjs/browser';
5
5
  import * as react from 'react';
6
6
  import { ReactNode, ReactElement, Component, ComponentType, ComponentProps } from 'react';
@@ -14,6 +14,50 @@ declare namespace useConstant {
14
14
  var whyDidYouRender: boolean;
15
15
  }
16
16
 
17
+ /**
18
+ * Compares two values for deep equality.
19
+ *
20
+ * This function checks if two values are deeply equal by comparing their types and contents.
21
+ * It handles various data types including primitives, arrays, dates, regular expressions, functions,
22
+ * maps, sets, and promises.
23
+ *
24
+ * @param a - The first value to compare.
25
+ * @param b - The second value to compare.
26
+ * @returns `true` if the values are deeply equal, `false` otherwise.
27
+ */
28
+ declare function equal(a: any, b: any): boolean;
29
+ /**
30
+ * Custom hook that memoizes a value using deep equality comparison.
31
+ *
32
+ * @param value - The value to be memoized.
33
+ * @returns The memoized value.
34
+ */
35
+ declare function useEqualMemoize(value: any): any;
36
+ /**
37
+ * Custom hook that works like `useEffect` but uses deep comparison on dependencies.
38
+ *
39
+ * @param callback - The effect callback function to run.
40
+ * @param dependencies - The list of dependencies for the effect.
41
+ * @returns The result of the `useEffect` hook with memoized dependencies.
42
+ */
43
+ declare function useEqualEffect(callback: React.EffectCallback, dependencies: any[]): void;
44
+ /**
45
+ * Custom hook that works like `useMemo` but uses deep comparison on dependencies.
46
+ *
47
+ * @param callback - The callback function to run.
48
+ * @param dependencies - The list of dependencies.
49
+ * @returns The result of the `useMemo` hook with memoized dependencies.
50
+ */
51
+ declare function useEqualMemo<T>(callback: () => T, dependencies: any[]): T;
52
+ /**
53
+ * Custom hook that works like `useCallback` but uses deep comparison on dependencies.
54
+ *
55
+ * @param callback - The callback function to run.
56
+ * @param dependencies - The list of dependencies.
57
+ * @returns The result of the `useCallback` hook with memoized dependencies.
58
+ */
59
+ declare function useEqualCallback<T extends (...args: any[]) => any>(callback: T, dependencies: any[]): T;
60
+
17
61
  /**
18
62
  * Creates a splitting context with the given default value.
19
63
  *
@@ -79,6 +123,11 @@ type FaasDataInjection<PathOrData extends FaasAction = any> = {
79
123
  data: FaasData<PathOrData>;
80
124
  error: any;
81
125
  promise: Promise<Response<FaasData<PathOrData>>>;
126
+ /**
127
+ * Reloads data with new or existing parameters.
128
+ *
129
+ * **Note**: It will sets skip to false before loading data.
130
+ */
82
131
  reload(params?: Record<string, any>): Promise<Response<PathOrData>>;
83
132
  setData: React.Dispatch<React.SetStateAction<FaasData<PathOrData>>>;
84
133
  setLoading: React.Dispatch<React.SetStateAction<boolean>>;
@@ -96,9 +145,9 @@ type FaasDataWrapperProps<PathOrData extends FaasAction> = {
96
145
  data?: FaasData<PathOrData>;
97
146
  /** use custom setData, should work with data */
98
147
  setData?: React.Dispatch<React.SetStateAction<FaasData<PathOrData>>>;
99
- baseUrl?: string;
148
+ baseUrl?: BaseUrl;
100
149
  };
101
- declare function FaasDataWrapper<PathOrData extends FaasAction>({ action, params, fallback, render, children, onDataChange, data, setData, baseUrl, }: FaasDataWrapperProps<PathOrData>): JSX.Element;
150
+ declare function FaasDataWrapper<PathOrData extends FaasAction>(props: FaasDataWrapperProps<PathOrData>): JSX.Element;
102
151
  declare namespace FaasDataWrapper {
103
152
  var whyDidYouRender: boolean;
104
153
  }
@@ -116,11 +165,15 @@ type useFaasOptions<PathOrData extends FaasAction> = {
116
165
  params?: FaasParams<PathOrData>;
117
166
  data?: FaasData<PathOrData>;
118
167
  setData?: React.Dispatch<React.SetStateAction<FaasData<PathOrData>>>;
119
- /** if skip is true, will not send request */
168
+ /**
169
+ * If skip is true, the request will not be sent.
170
+ *
171
+ * However, you can still use reload to send the request.
172
+ */
120
173
  skip?: boolean | ((params: FaasParams<PathOrData>) => boolean);
121
- /** send the last request after milliseconds */
174
+ /** Send the last request after milliseconds */
122
175
  debounce?: number;
123
- baseUrl?: string;
176
+ baseUrl?: BaseUrl;
124
177
  };
125
178
  /**
126
179
  * Request faas server with React hook
@@ -256,4 +309,4 @@ declare const OptionalWrapper: React.FC<OptionalWrapperProps> & {
256
309
  whyDidYouRender: boolean;
257
310
  };
258
311
 
259
- export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, faas, getClient, useConstant, useFaas, type useFaasOptions, withFaasData };
312
+ export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, type useFaasOptions, withFaasData };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FaasAction, FaasData, FaasParams } from '@faasjs/types';
2
2
  export { FaasAction, FaasData, FaasParams } from '@faasjs/types';
3
- import { Response, ResponseError, BaseUrl, Options, FaasBrowserClient } from '@faasjs/browser';
3
+ import { Response, BaseUrl, ResponseError, Options, FaasBrowserClient } from '@faasjs/browser';
4
4
  export { Options, Response, ResponseError, ResponseHeaders } from '@faasjs/browser';
5
5
  import * as react from 'react';
6
6
  import { ReactNode, ReactElement, Component, ComponentType, ComponentProps } from 'react';
@@ -14,6 +14,50 @@ declare namespace useConstant {
14
14
  var whyDidYouRender: boolean;
15
15
  }
16
16
 
17
+ /**
18
+ * Compares two values for deep equality.
19
+ *
20
+ * This function checks if two values are deeply equal by comparing their types and contents.
21
+ * It handles various data types including primitives, arrays, dates, regular expressions, functions,
22
+ * maps, sets, and promises.
23
+ *
24
+ * @param a - The first value to compare.
25
+ * @param b - The second value to compare.
26
+ * @returns `true` if the values are deeply equal, `false` otherwise.
27
+ */
28
+ declare function equal(a: any, b: any): boolean;
29
+ /**
30
+ * Custom hook that memoizes a value using deep equality comparison.
31
+ *
32
+ * @param value - The value to be memoized.
33
+ * @returns The memoized value.
34
+ */
35
+ declare function useEqualMemoize(value: any): any;
36
+ /**
37
+ * Custom hook that works like `useEffect` but uses deep comparison on dependencies.
38
+ *
39
+ * @param callback - The effect callback function to run.
40
+ * @param dependencies - The list of dependencies for the effect.
41
+ * @returns The result of the `useEffect` hook with memoized dependencies.
42
+ */
43
+ declare function useEqualEffect(callback: React.EffectCallback, dependencies: any[]): void;
44
+ /**
45
+ * Custom hook that works like `useMemo` but uses deep comparison on dependencies.
46
+ *
47
+ * @param callback - The callback function to run.
48
+ * @param dependencies - The list of dependencies.
49
+ * @returns The result of the `useMemo` hook with memoized dependencies.
50
+ */
51
+ declare function useEqualMemo<T>(callback: () => T, dependencies: any[]): T;
52
+ /**
53
+ * Custom hook that works like `useCallback` but uses deep comparison on dependencies.
54
+ *
55
+ * @param callback - The callback function to run.
56
+ * @param dependencies - The list of dependencies.
57
+ * @returns The result of the `useCallback` hook with memoized dependencies.
58
+ */
59
+ declare function useEqualCallback<T extends (...args: any[]) => any>(callback: T, dependencies: any[]): T;
60
+
17
61
  /**
18
62
  * Creates a splitting context with the given default value.
19
63
  *
@@ -79,6 +123,11 @@ type FaasDataInjection<PathOrData extends FaasAction = any> = {
79
123
  data: FaasData<PathOrData>;
80
124
  error: any;
81
125
  promise: Promise<Response<FaasData<PathOrData>>>;
126
+ /**
127
+ * Reloads data with new or existing parameters.
128
+ *
129
+ * **Note**: It will sets skip to false before loading data.
130
+ */
82
131
  reload(params?: Record<string, any>): Promise<Response<PathOrData>>;
83
132
  setData: React.Dispatch<React.SetStateAction<FaasData<PathOrData>>>;
84
133
  setLoading: React.Dispatch<React.SetStateAction<boolean>>;
@@ -96,9 +145,9 @@ type FaasDataWrapperProps<PathOrData extends FaasAction> = {
96
145
  data?: FaasData<PathOrData>;
97
146
  /** use custom setData, should work with data */
98
147
  setData?: React.Dispatch<React.SetStateAction<FaasData<PathOrData>>>;
99
- baseUrl?: string;
148
+ baseUrl?: BaseUrl;
100
149
  };
101
- declare function FaasDataWrapper<PathOrData extends FaasAction>({ action, params, fallback, render, children, onDataChange, data, setData, baseUrl, }: FaasDataWrapperProps<PathOrData>): JSX.Element;
150
+ declare function FaasDataWrapper<PathOrData extends FaasAction>(props: FaasDataWrapperProps<PathOrData>): JSX.Element;
102
151
  declare namespace FaasDataWrapper {
103
152
  var whyDidYouRender: boolean;
104
153
  }
@@ -116,11 +165,15 @@ type useFaasOptions<PathOrData extends FaasAction> = {
116
165
  params?: FaasParams<PathOrData>;
117
166
  data?: FaasData<PathOrData>;
118
167
  setData?: React.Dispatch<React.SetStateAction<FaasData<PathOrData>>>;
119
- /** if skip is true, will not send request */
168
+ /**
169
+ * If skip is true, the request will not be sent.
170
+ *
171
+ * However, you can still use reload to send the request.
172
+ */
120
173
  skip?: boolean | ((params: FaasParams<PathOrData>) => boolean);
121
- /** send the last request after milliseconds */
174
+ /** Send the last request after milliseconds */
122
175
  debounce?: number;
123
- baseUrl?: string;
176
+ baseUrl?: BaseUrl;
124
177
  };
125
178
  /**
126
179
  * Request faas server with React hook
@@ -256,4 +309,4 @@ declare const OptionalWrapper: React.FC<OptionalWrapperProps> & {
256
309
  whyDidYouRender: boolean;
257
310
  };
258
311
 
259
- export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, faas, getClient, useConstant, useFaas, type useFaasOptions, withFaasData };
312
+ export { ErrorBoundary, type ErrorBoundaryProps, type ErrorChildrenProps, type FaasDataInjection, FaasDataWrapper, type FaasDataWrapperProps, FaasReactClient, type FaasReactClientInstance, type FaasReactClientOptions, type OnError, OptionalWrapper, type OptionalWrapperProps, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, type useFaasOptions, withFaasData };
package/dist/index.js CHANGED
@@ -13,6 +13,69 @@ function useConstant(fn) {
13
13
  return ref.current.v;
14
14
  }
15
15
  useConstant.whyDidYouRender = true;
16
+ function equal(a, b) {
17
+ if (a === b) return true;
18
+ if ((a === null || a === void 0) && (b === null || b === void 0))
19
+ return true;
20
+ if (typeof a !== typeof b) return false;
21
+ const ctor = a.constructor;
22
+ if (ctor !== b.constructor) return false;
23
+ switch (ctor) {
24
+ case String:
25
+ case Boolean:
26
+ return a === b;
27
+ case Number:
28
+ return Number.isNaN(a) && Number.isNaN(b) || a === b;
29
+ case Array: {
30
+ if (a.length !== b.length) return false;
31
+ for (let i = 0; i < a.length; i++) {
32
+ if (!equal(a[i], b[i])) return false;
33
+ }
34
+ return true;
35
+ }
36
+ case Date:
37
+ return a.getTime() === b.getTime();
38
+ case RegExp:
39
+ case Function:
40
+ return a.toString() === b.toString();
41
+ case Map:
42
+ case Set:
43
+ return equal(Array.from(a), Array.from(b));
44
+ case Promise:
45
+ return a === b;
46
+ case Object: {
47
+ const keys = Object.keys(a);
48
+ if (keys.length !== Object.keys(b).length) return false;
49
+ for (const key of keys) {
50
+ if (!equal(a[key], b[key])) return false;
51
+ }
52
+ return true;
53
+ }
54
+ default:
55
+ throw Error(`Unsupported type: ${ctor}`);
56
+ }
57
+ }
58
+ function useEqualMemoize(value) {
59
+ const ref = react.useRef(value);
60
+ const signalRef = react.useRef(0);
61
+ if (!equal(value, ref.current)) {
62
+ ref.current = value;
63
+ signalRef.current += 1;
64
+ }
65
+ return react.useMemo(() => ref.current, [signalRef.current]);
66
+ }
67
+ function useEqualEffect(callback, dependencies) {
68
+ return react.useEffect(callback, useEqualMemoize(dependencies));
69
+ }
70
+ function useEqualMemo(callback, dependencies) {
71
+ return react.useMemo(callback, useEqualMemoize(dependencies));
72
+ }
73
+ function useEqualCallback(callback, dependencies) {
74
+ return react.useCallback(
75
+ (...args) => callback(...args),
76
+ useEqualMemoize(dependencies)
77
+ );
78
+ }
16
79
  function createSplittingContext(defaultValue) {
17
80
  const contexts = {};
18
81
  const keys = Object.keys(defaultValue);
@@ -42,34 +105,28 @@ function createSplittingContext(defaultValue) {
42
105
  use
43
106
  };
44
107
  }
45
- function FaasDataWrapper({
46
- action,
47
- params,
48
- fallback,
49
- render,
50
- children,
51
- onDataChange,
52
- data,
53
- setData,
54
- baseUrl
55
- }) {
56
- const request = getClient(baseUrl).useFaas(action, params, {
57
- data,
58
- setData
59
- });
108
+ function FaasDataWrapper(props) {
109
+ const request = getClient(props.baseUrl).useFaas(
110
+ props.action,
111
+ props.params,
112
+ {
113
+ data: props.data,
114
+ setData: props.setData
115
+ }
116
+ );
60
117
  const [loaded, setLoaded] = react.useState(false);
61
118
  react.useEffect(() => {
62
119
  if (!loaded && !request.loading) setLoaded(true);
63
120
  }, [request.loading]);
64
- react.useEffect(() => {
65
- if (onDataChange) onDataChange(request);
66
- }, [JSON.stringify(request.data)]);
67
- const child = react.useMemo(() => {
121
+ useEqualEffect(() => {
122
+ if (props.onDataChange) props.onDataChange(request);
123
+ }, [request.data]);
124
+ const child = useEqualMemo(() => {
68
125
  if (loaded) {
69
- if (children) return react.cloneElement(children, request);
70
- if (render) return render(request);
126
+ if (props.children) return react.cloneElement(props.children, request);
127
+ if (props.render) return props.render(request);
71
128
  }
72
- return fallback || null;
129
+ return props.fallback || null;
73
130
  }, [
74
131
  loaded,
75
132
  request.action,
@@ -84,47 +141,45 @@ FaasDataWrapper.whyDidYouRender = true;
84
141
  function withFaasData(Component2, faasProps) {
85
142
  return (props) => /* @__PURE__ */ jsxRuntime.jsx(FaasDataWrapper, { ...faasProps, children: /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props }) });
86
143
  }
87
- function useFaas(action, defaultParams, options) {
88
- if (!options) options = {};
144
+ function useFaas(action, defaultParams, options = {}) {
89
145
  const [loading, setLoading] = react.useState(true);
90
146
  const [data, setData] = react.useState();
91
147
  const [error, setError] = react.useState();
92
- const [promise, setPromise] = react.useState();
93
148
  const [params, setParams] = react.useState(defaultParams);
94
149
  const [reloadTimes, setReloadTimes] = react.useState(0);
95
150
  const [fails, setFails] = react.useState(0);
96
151
  const [skip, setSkip] = react.useState(
97
152
  typeof options.skip === "function" ? options.skip(defaultParams) : options.skip
98
153
  );
99
- const client = getClient(options.baseUrl);
100
- react.useEffect(() => {
154
+ const promiseRef = react.useRef();
155
+ const controllerRef = react.useRef(null);
156
+ useEqualEffect(() => {
101
157
  setSkip(
102
158
  typeof options.skip === "function" ? options.skip(params) : options.skip
103
159
  );
104
- }, [
105
- typeof options.skip === "function" ? JSON.stringify(params) : options.skip
106
- ]);
107
- react.useEffect(() => {
108
- if (JSON.stringify(defaultParams) !== JSON.stringify(params)) {
160
+ }, [typeof options.skip === "function" ? params : options.skip]);
161
+ useEqualEffect(() => {
162
+ if (!equal(defaultParams, params)) {
109
163
  setParams(defaultParams);
110
164
  }
111
- }, [JSON.stringify(defaultParams)]);
112
- react.useEffect(() => {
165
+ }, [defaultParams]);
166
+ useEqualEffect(() => {
113
167
  if (!action || skip) {
114
168
  setLoading(false);
115
169
  return;
116
170
  }
117
171
  setLoading(true);
118
- const controller = new AbortController();
172
+ controllerRef.current = new AbortController();
173
+ const client = getClient(options.baseUrl);
119
174
  function send() {
120
175
  const request = client.faas(
121
176
  action,
122
177
  options.params || params,
123
- { signal: controller.signal }
178
+ { signal: controllerRef.current.signal }
124
179
  );
125
- setPromise(request);
180
+ promiseRef.current = request;
126
181
  request.then((r) => {
127
- options?.setData ? options.setData(r.data) : setData(r.data);
182
+ options.setData ? options.setData(r.data) : setData(r.data);
128
183
  setLoading(false);
129
184
  }).catch(async (e) => {
130
185
  if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0)
@@ -145,40 +200,41 @@ function useFaas(action, defaultParams, options) {
145
200
  return Promise.reject(e);
146
201
  });
147
202
  }
148
- if (options?.debounce) {
203
+ if (options.debounce) {
149
204
  const timeout = setTimeout(send, options.debounce);
150
205
  return () => {
151
206
  clearTimeout(timeout);
152
- controller.abort();
207
+ controllerRef.current?.abort();
153
208
  setLoading(false);
154
209
  };
155
210
  }
156
211
  send();
157
212
  return () => {
158
- controller.abort();
213
+ controllerRef.current?.abort();
159
214
  setLoading(false);
160
215
  };
161
- }, [action, JSON.stringify(options.params || params), reloadTimes, skip]);
162
- const reload = react.useCallback(
216
+ }, [action, options.params || params, reloadTimes, skip]);
217
+ const reload = useEqualCallback(
163
218
  (params2) => {
219
+ if (skip) setSkip(false);
164
220
  if (params2) setParams(params2);
165
221
  setReloadTimes((prev) => prev + 1);
166
- return promise;
222
+ return promiseRef.current;
167
223
  },
168
- [params]
224
+ [params, skip]
169
225
  );
170
226
  return {
171
227
  action,
172
228
  params,
173
229
  loading,
174
- data: options?.data || data,
230
+ data: options.data || data,
175
231
  reloadTimes,
176
232
  error,
177
- promise,
233
+ promise: promiseRef.current,
178
234
  reload,
179
- setData: options?.setData || setData,
235
+ setData: options.setData || setData,
180
236
  setLoading,
181
- setPromise,
237
+ setPromise: (newPromise) => typeof newPromise === "function" ? newPromise(promiseRef.current) : promiseRef.current = newPromise,
182
238
  setError
183
239
  };
184
240
  }
@@ -265,8 +321,13 @@ exports.FaasDataWrapper = FaasDataWrapper;
265
321
  exports.FaasReactClient = FaasReactClient;
266
322
  exports.OptionalWrapper = OptionalWrapper;
267
323
  exports.createSplittingContext = createSplittingContext;
324
+ exports.equal = equal;
268
325
  exports.faas = faas;
269
326
  exports.getClient = getClient;
270
327
  exports.useConstant = useConstant;
328
+ exports.useEqualCallback = useEqualCallback;
329
+ exports.useEqualEffect = useEqualEffect;
330
+ exports.useEqualMemo = useEqualMemo;
331
+ exports.useEqualMemoize = useEqualMemoize;
271
332
  exports.useFaas = useFaas;
272
333
  exports.withFaasData = withFaasData;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { useRef, createContext, useState, useEffect, useMemo, cloneElement, useCallback, Component, useContext } from 'react';
1
+ import { useRef, useMemo, useEffect, useCallback, createContext, useState, cloneElement, Component, useContext } from 'react';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { FaasBrowserClient } from '@faasjs/browser';
4
4
 
@@ -11,6 +11,69 @@ function useConstant(fn) {
11
11
  return ref.current.v;
12
12
  }
13
13
  useConstant.whyDidYouRender = true;
14
+ function equal(a, b) {
15
+ if (a === b) return true;
16
+ if ((a === null || a === void 0) && (b === null || b === void 0))
17
+ return true;
18
+ if (typeof a !== typeof b) return false;
19
+ const ctor = a.constructor;
20
+ if (ctor !== b.constructor) return false;
21
+ switch (ctor) {
22
+ case String:
23
+ case Boolean:
24
+ return a === b;
25
+ case Number:
26
+ return Number.isNaN(a) && Number.isNaN(b) || a === b;
27
+ case Array: {
28
+ if (a.length !== b.length) return false;
29
+ for (let i = 0; i < a.length; i++) {
30
+ if (!equal(a[i], b[i])) return false;
31
+ }
32
+ return true;
33
+ }
34
+ case Date:
35
+ return a.getTime() === b.getTime();
36
+ case RegExp:
37
+ case Function:
38
+ return a.toString() === b.toString();
39
+ case Map:
40
+ case Set:
41
+ return equal(Array.from(a), Array.from(b));
42
+ case Promise:
43
+ return a === b;
44
+ case Object: {
45
+ const keys = Object.keys(a);
46
+ if (keys.length !== Object.keys(b).length) return false;
47
+ for (const key of keys) {
48
+ if (!equal(a[key], b[key])) return false;
49
+ }
50
+ return true;
51
+ }
52
+ default:
53
+ throw Error(`Unsupported type: ${ctor}`);
54
+ }
55
+ }
56
+ function useEqualMemoize(value) {
57
+ const ref = useRef(value);
58
+ const signalRef = useRef(0);
59
+ if (!equal(value, ref.current)) {
60
+ ref.current = value;
61
+ signalRef.current += 1;
62
+ }
63
+ return useMemo(() => ref.current, [signalRef.current]);
64
+ }
65
+ function useEqualEffect(callback, dependencies) {
66
+ return useEffect(callback, useEqualMemoize(dependencies));
67
+ }
68
+ function useEqualMemo(callback, dependencies) {
69
+ return useMemo(callback, useEqualMemoize(dependencies));
70
+ }
71
+ function useEqualCallback(callback, dependencies) {
72
+ return useCallback(
73
+ (...args) => callback(...args),
74
+ useEqualMemoize(dependencies)
75
+ );
76
+ }
14
77
  function createSplittingContext(defaultValue) {
15
78
  const contexts = {};
16
79
  const keys = Object.keys(defaultValue);
@@ -40,34 +103,28 @@ function createSplittingContext(defaultValue) {
40
103
  use
41
104
  };
42
105
  }
43
- function FaasDataWrapper({
44
- action,
45
- params,
46
- fallback,
47
- render,
48
- children,
49
- onDataChange,
50
- data,
51
- setData,
52
- baseUrl
53
- }) {
54
- const request = getClient(baseUrl).useFaas(action, params, {
55
- data,
56
- setData
57
- });
106
+ function FaasDataWrapper(props) {
107
+ const request = getClient(props.baseUrl).useFaas(
108
+ props.action,
109
+ props.params,
110
+ {
111
+ data: props.data,
112
+ setData: props.setData
113
+ }
114
+ );
58
115
  const [loaded, setLoaded] = useState(false);
59
116
  useEffect(() => {
60
117
  if (!loaded && !request.loading) setLoaded(true);
61
118
  }, [request.loading]);
62
- useEffect(() => {
63
- if (onDataChange) onDataChange(request);
64
- }, [JSON.stringify(request.data)]);
65
- const child = useMemo(() => {
119
+ useEqualEffect(() => {
120
+ if (props.onDataChange) props.onDataChange(request);
121
+ }, [request.data]);
122
+ const child = useEqualMemo(() => {
66
123
  if (loaded) {
67
- if (children) return cloneElement(children, request);
68
- if (render) return render(request);
124
+ if (props.children) return cloneElement(props.children, request);
125
+ if (props.render) return props.render(request);
69
126
  }
70
- return fallback || null;
127
+ return props.fallback || null;
71
128
  }, [
72
129
  loaded,
73
130
  request.action,
@@ -82,47 +139,45 @@ FaasDataWrapper.whyDidYouRender = true;
82
139
  function withFaasData(Component2, faasProps) {
83
140
  return (props) => /* @__PURE__ */ jsx(FaasDataWrapper, { ...faasProps, children: /* @__PURE__ */ jsx(Component2, { ...props }) });
84
141
  }
85
- function useFaas(action, defaultParams, options) {
86
- if (!options) options = {};
142
+ function useFaas(action, defaultParams, options = {}) {
87
143
  const [loading, setLoading] = useState(true);
88
144
  const [data, setData] = useState();
89
145
  const [error, setError] = useState();
90
- const [promise, setPromise] = useState();
91
146
  const [params, setParams] = useState(defaultParams);
92
147
  const [reloadTimes, setReloadTimes] = useState(0);
93
148
  const [fails, setFails] = useState(0);
94
149
  const [skip, setSkip] = useState(
95
150
  typeof options.skip === "function" ? options.skip(defaultParams) : options.skip
96
151
  );
97
- const client = getClient(options.baseUrl);
98
- useEffect(() => {
152
+ const promiseRef = useRef();
153
+ const controllerRef = useRef(null);
154
+ useEqualEffect(() => {
99
155
  setSkip(
100
156
  typeof options.skip === "function" ? options.skip(params) : options.skip
101
157
  );
102
- }, [
103
- typeof options.skip === "function" ? JSON.stringify(params) : options.skip
104
- ]);
105
- useEffect(() => {
106
- if (JSON.stringify(defaultParams) !== JSON.stringify(params)) {
158
+ }, [typeof options.skip === "function" ? params : options.skip]);
159
+ useEqualEffect(() => {
160
+ if (!equal(defaultParams, params)) {
107
161
  setParams(defaultParams);
108
162
  }
109
- }, [JSON.stringify(defaultParams)]);
110
- useEffect(() => {
163
+ }, [defaultParams]);
164
+ useEqualEffect(() => {
111
165
  if (!action || skip) {
112
166
  setLoading(false);
113
167
  return;
114
168
  }
115
169
  setLoading(true);
116
- const controller = new AbortController();
170
+ controllerRef.current = new AbortController();
171
+ const client = getClient(options.baseUrl);
117
172
  function send() {
118
173
  const request = client.faas(
119
174
  action,
120
175
  options.params || params,
121
- { signal: controller.signal }
176
+ { signal: controllerRef.current.signal }
122
177
  );
123
- setPromise(request);
178
+ promiseRef.current = request;
124
179
  request.then((r) => {
125
- options?.setData ? options.setData(r.data) : setData(r.data);
180
+ options.setData ? options.setData(r.data) : setData(r.data);
126
181
  setLoading(false);
127
182
  }).catch(async (e) => {
128
183
  if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0)
@@ -143,40 +198,41 @@ function useFaas(action, defaultParams, options) {
143
198
  return Promise.reject(e);
144
199
  });
145
200
  }
146
- if (options?.debounce) {
201
+ if (options.debounce) {
147
202
  const timeout = setTimeout(send, options.debounce);
148
203
  return () => {
149
204
  clearTimeout(timeout);
150
- controller.abort();
205
+ controllerRef.current?.abort();
151
206
  setLoading(false);
152
207
  };
153
208
  }
154
209
  send();
155
210
  return () => {
156
- controller.abort();
211
+ controllerRef.current?.abort();
157
212
  setLoading(false);
158
213
  };
159
- }, [action, JSON.stringify(options.params || params), reloadTimes, skip]);
160
- const reload = useCallback(
214
+ }, [action, options.params || params, reloadTimes, skip]);
215
+ const reload = useEqualCallback(
161
216
  (params2) => {
217
+ if (skip) setSkip(false);
162
218
  if (params2) setParams(params2);
163
219
  setReloadTimes((prev) => prev + 1);
164
- return promise;
220
+ return promiseRef.current;
165
221
  },
166
- [params]
222
+ [params, skip]
167
223
  );
168
224
  return {
169
225
  action,
170
226
  params,
171
227
  loading,
172
- data: options?.data || data,
228
+ data: options.data || data,
173
229
  reloadTimes,
174
230
  error,
175
- promise,
231
+ promise: promiseRef.current,
176
232
  reload,
177
- setData: options?.setData || setData,
233
+ setData: options.setData || setData,
178
234
  setLoading,
179
- setPromise,
235
+ setPromise: (newPromise) => typeof newPromise === "function" ? newPromise(promiseRef.current) : promiseRef.current = newPromise,
180
236
  setError
181
237
  };
182
238
  }
@@ -258,4 +314,4 @@ var OptionalWrapper = ({ condition, Wrapper, wrapperProps, children }) => {
258
314
  };
259
315
  OptionalWrapper.whyDidYouRender = true;
260
316
 
261
- export { ErrorBoundary, FaasDataWrapper, FaasReactClient, OptionalWrapper, createSplittingContext, faas, getClient, useConstant, useFaas, withFaasData };
317
+ export { ErrorBoundary, FaasDataWrapper, FaasReactClient, OptionalWrapper, createSplittingContext, equal, faas, getClient, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, withFaasData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -34,7 +34,7 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@faasjs/browser": "3.0.0"
37
+ "@faasjs/browser": "3.1.1"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "*"