@faasjs/react 3.0.0 → 3.1.0

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,71 @@ 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
+ console.log(value, ref.current);
62
+ if (!equal(value, ref.current)) {
63
+ ref.current = value;
64
+ signalRef.current += 1;
65
+ console.log("signalRef.current", signalRef.current);
66
+ }
67
+ return react.useMemo(() => ref.current, [signalRef.current]);
68
+ }
69
+ function useEqualEffect(callback, dependencies) {
70
+ return react.useEffect(callback, useEqualMemoize(dependencies));
71
+ }
72
+ function useEqualMemo(callback, dependencies) {
73
+ return react.useMemo(callback, useEqualMemoize(dependencies));
74
+ }
75
+ function useEqualCallback(callback, dependencies) {
76
+ return react.useCallback(
77
+ (...args) => callback(...args),
78
+ useEqualMemoize(dependencies)
79
+ );
80
+ }
16
81
  function createSplittingContext(defaultValue) {
17
82
  const contexts = {};
18
83
  const keys = Object.keys(defaultValue);
@@ -42,34 +107,28 @@ function createSplittingContext(defaultValue) {
42
107
  use
43
108
  };
44
109
  }
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
- });
110
+ function FaasDataWrapper(props) {
111
+ const request = getClient(props.baseUrl).useFaas(
112
+ props.action,
113
+ props.params,
114
+ {
115
+ data: props.data,
116
+ setData: props.setData
117
+ }
118
+ );
60
119
  const [loaded, setLoaded] = react.useState(false);
61
120
  react.useEffect(() => {
62
121
  if (!loaded && !request.loading) setLoaded(true);
63
122
  }, [request.loading]);
64
- react.useEffect(() => {
65
- if (onDataChange) onDataChange(request);
66
- }, [JSON.stringify(request.data)]);
67
- const child = react.useMemo(() => {
123
+ useEqualEffect(() => {
124
+ if (props.onDataChange) props.onDataChange(request);
125
+ }, [request.data]);
126
+ const child = useEqualMemo(() => {
68
127
  if (loaded) {
69
- if (children) return react.cloneElement(children, request);
70
- if (render) return render(request);
128
+ if (props.children) return react.cloneElement(props.children, request);
129
+ if (props.render) return props.render(request);
71
130
  }
72
- return fallback || null;
131
+ return props.fallback || null;
73
132
  }, [
74
133
  loaded,
75
134
  request.action,
@@ -84,47 +143,45 @@ FaasDataWrapper.whyDidYouRender = true;
84
143
  function withFaasData(Component2, faasProps) {
85
144
  return (props) => /* @__PURE__ */ jsxRuntime.jsx(FaasDataWrapper, { ...faasProps, children: /* @__PURE__ */ jsxRuntime.jsx(Component2, { ...props }) });
86
145
  }
87
- function useFaas(action, defaultParams, options) {
88
- if (!options) options = {};
146
+ function useFaas(action, defaultParams, options = {}) {
89
147
  const [loading, setLoading] = react.useState(true);
90
148
  const [data, setData] = react.useState();
91
149
  const [error, setError] = react.useState();
92
- const [promise, setPromise] = react.useState();
93
150
  const [params, setParams] = react.useState(defaultParams);
94
151
  const [reloadTimes, setReloadTimes] = react.useState(0);
95
152
  const [fails, setFails] = react.useState(0);
96
153
  const [skip, setSkip] = react.useState(
97
154
  typeof options.skip === "function" ? options.skip(defaultParams) : options.skip
98
155
  );
99
- const client = getClient(options.baseUrl);
100
- react.useEffect(() => {
156
+ const promiseRef = react.useRef();
157
+ const controllerRef = react.useRef(null);
158
+ useEqualEffect(() => {
101
159
  setSkip(
102
160
  typeof options.skip === "function" ? options.skip(params) : options.skip
103
161
  );
104
- }, [
105
- typeof options.skip === "function" ? JSON.stringify(params) : options.skip
106
- ]);
107
- react.useEffect(() => {
108
- if (JSON.stringify(defaultParams) !== JSON.stringify(params)) {
162
+ }, [typeof options.skip === "function" ? params : options.skip]);
163
+ useEqualEffect(() => {
164
+ if (!equal(defaultParams, params)) {
109
165
  setParams(defaultParams);
110
166
  }
111
- }, [JSON.stringify(defaultParams)]);
112
- react.useEffect(() => {
167
+ }, [defaultParams]);
168
+ useEqualEffect(() => {
113
169
  if (!action || skip) {
114
170
  setLoading(false);
115
171
  return;
116
172
  }
117
173
  setLoading(true);
118
- const controller = new AbortController();
174
+ controllerRef.current = new AbortController();
175
+ const client = getClient(options.baseUrl);
119
176
  function send() {
120
177
  const request = client.faas(
121
178
  action,
122
179
  options.params || params,
123
- { signal: controller.signal }
180
+ { signal: controllerRef.current.signal }
124
181
  );
125
- setPromise(request);
182
+ promiseRef.current = request;
126
183
  request.then((r) => {
127
- options?.setData ? options.setData(r.data) : setData(r.data);
184
+ options.setData ? options.setData(r.data) : setData(r.data);
128
185
  setLoading(false);
129
186
  }).catch(async (e) => {
130
187
  if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0)
@@ -145,40 +202,41 @@ function useFaas(action, defaultParams, options) {
145
202
  return Promise.reject(e);
146
203
  });
147
204
  }
148
- if (options?.debounce) {
205
+ if (options.debounce) {
149
206
  const timeout = setTimeout(send, options.debounce);
150
207
  return () => {
151
208
  clearTimeout(timeout);
152
- controller.abort();
209
+ controllerRef.current?.abort();
153
210
  setLoading(false);
154
211
  };
155
212
  }
156
213
  send();
157
214
  return () => {
158
- controller.abort();
215
+ controllerRef.current?.abort();
159
216
  setLoading(false);
160
217
  };
161
- }, [action, JSON.stringify(options.params || params), reloadTimes, skip]);
162
- const reload = react.useCallback(
218
+ }, [action, options.params || params, reloadTimes, skip]);
219
+ const reload = useEqualCallback(
163
220
  (params2) => {
221
+ if (skip) setSkip(false);
164
222
  if (params2) setParams(params2);
165
223
  setReloadTimes((prev) => prev + 1);
166
- return promise;
224
+ return promiseRef.current;
167
225
  },
168
- [params]
226
+ [params, skip]
169
227
  );
170
228
  return {
171
229
  action,
172
230
  params,
173
231
  loading,
174
- data: options?.data || data,
232
+ data: options.data || data,
175
233
  reloadTimes,
176
234
  error,
177
- promise,
235
+ promise: promiseRef.current,
178
236
  reload,
179
- setData: options?.setData || setData,
237
+ setData: options.setData || setData,
180
238
  setLoading,
181
- setPromise,
239
+ setPromise: (newPromise) => typeof newPromise === "function" ? newPromise(promiseRef.current) : promiseRef.current = newPromise,
182
240
  setError
183
241
  };
184
242
  }
@@ -265,8 +323,13 @@ exports.FaasDataWrapper = FaasDataWrapper;
265
323
  exports.FaasReactClient = FaasReactClient;
266
324
  exports.OptionalWrapper = OptionalWrapper;
267
325
  exports.createSplittingContext = createSplittingContext;
326
+ exports.equal = equal;
268
327
  exports.faas = faas;
269
328
  exports.getClient = getClient;
270
329
  exports.useConstant = useConstant;
330
+ exports.useEqualCallback = useEqualCallback;
331
+ exports.useEqualEffect = useEqualEffect;
332
+ exports.useEqualMemo = useEqualMemo;
333
+ exports.useEqualMemoize = useEqualMemoize;
271
334
  exports.useFaas = useFaas;
272
335
  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,71 @@ 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
+ console.log(value, ref.current);
60
+ if (!equal(value, ref.current)) {
61
+ ref.current = value;
62
+ signalRef.current += 1;
63
+ console.log("signalRef.current", signalRef.current);
64
+ }
65
+ return useMemo(() => ref.current, [signalRef.current]);
66
+ }
67
+ function useEqualEffect(callback, dependencies) {
68
+ return useEffect(callback, useEqualMemoize(dependencies));
69
+ }
70
+ function useEqualMemo(callback, dependencies) {
71
+ return useMemo(callback, useEqualMemoize(dependencies));
72
+ }
73
+ function useEqualCallback(callback, dependencies) {
74
+ return useCallback(
75
+ (...args) => callback(...args),
76
+ useEqualMemoize(dependencies)
77
+ );
78
+ }
14
79
  function createSplittingContext(defaultValue) {
15
80
  const contexts = {};
16
81
  const keys = Object.keys(defaultValue);
@@ -40,34 +105,28 @@ function createSplittingContext(defaultValue) {
40
105
  use
41
106
  };
42
107
  }
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
- });
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
+ );
58
117
  const [loaded, setLoaded] = useState(false);
59
118
  useEffect(() => {
60
119
  if (!loaded && !request.loading) setLoaded(true);
61
120
  }, [request.loading]);
62
- useEffect(() => {
63
- if (onDataChange) onDataChange(request);
64
- }, [JSON.stringify(request.data)]);
65
- const child = useMemo(() => {
121
+ useEqualEffect(() => {
122
+ if (props.onDataChange) props.onDataChange(request);
123
+ }, [request.data]);
124
+ const child = useEqualMemo(() => {
66
125
  if (loaded) {
67
- if (children) return cloneElement(children, request);
68
- if (render) return render(request);
126
+ if (props.children) return cloneElement(props.children, request);
127
+ if (props.render) return props.render(request);
69
128
  }
70
- return fallback || null;
129
+ return props.fallback || null;
71
130
  }, [
72
131
  loaded,
73
132
  request.action,
@@ -82,47 +141,45 @@ FaasDataWrapper.whyDidYouRender = true;
82
141
  function withFaasData(Component2, faasProps) {
83
142
  return (props) => /* @__PURE__ */ jsx(FaasDataWrapper, { ...faasProps, children: /* @__PURE__ */ jsx(Component2, { ...props }) });
84
143
  }
85
- function useFaas(action, defaultParams, options) {
86
- if (!options) options = {};
144
+ function useFaas(action, defaultParams, options = {}) {
87
145
  const [loading, setLoading] = useState(true);
88
146
  const [data, setData] = useState();
89
147
  const [error, setError] = useState();
90
- const [promise, setPromise] = useState();
91
148
  const [params, setParams] = useState(defaultParams);
92
149
  const [reloadTimes, setReloadTimes] = useState(0);
93
150
  const [fails, setFails] = useState(0);
94
151
  const [skip, setSkip] = useState(
95
152
  typeof options.skip === "function" ? options.skip(defaultParams) : options.skip
96
153
  );
97
- const client = getClient(options.baseUrl);
98
- useEffect(() => {
154
+ const promiseRef = useRef();
155
+ const controllerRef = useRef(null);
156
+ useEqualEffect(() => {
99
157
  setSkip(
100
158
  typeof options.skip === "function" ? options.skip(params) : options.skip
101
159
  );
102
- }, [
103
- typeof options.skip === "function" ? JSON.stringify(params) : options.skip
104
- ]);
105
- useEffect(() => {
106
- if (JSON.stringify(defaultParams) !== JSON.stringify(params)) {
160
+ }, [typeof options.skip === "function" ? params : options.skip]);
161
+ useEqualEffect(() => {
162
+ if (!equal(defaultParams, params)) {
107
163
  setParams(defaultParams);
108
164
  }
109
- }, [JSON.stringify(defaultParams)]);
110
- useEffect(() => {
165
+ }, [defaultParams]);
166
+ useEqualEffect(() => {
111
167
  if (!action || skip) {
112
168
  setLoading(false);
113
169
  return;
114
170
  }
115
171
  setLoading(true);
116
- const controller = new AbortController();
172
+ controllerRef.current = new AbortController();
173
+ const client = getClient(options.baseUrl);
117
174
  function send() {
118
175
  const request = client.faas(
119
176
  action,
120
177
  options.params || params,
121
- { signal: controller.signal }
178
+ { signal: controllerRef.current.signal }
122
179
  );
123
- setPromise(request);
180
+ promiseRef.current = request;
124
181
  request.then((r) => {
125
- options?.setData ? options.setData(r.data) : setData(r.data);
182
+ options.setData ? options.setData(r.data) : setData(r.data);
126
183
  setLoading(false);
127
184
  }).catch(async (e) => {
128
185
  if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0)
@@ -143,40 +200,41 @@ function useFaas(action, defaultParams, options) {
143
200
  return Promise.reject(e);
144
201
  });
145
202
  }
146
- if (options?.debounce) {
203
+ if (options.debounce) {
147
204
  const timeout = setTimeout(send, options.debounce);
148
205
  return () => {
149
206
  clearTimeout(timeout);
150
- controller.abort();
207
+ controllerRef.current?.abort();
151
208
  setLoading(false);
152
209
  };
153
210
  }
154
211
  send();
155
212
  return () => {
156
- controller.abort();
213
+ controllerRef.current?.abort();
157
214
  setLoading(false);
158
215
  };
159
- }, [action, JSON.stringify(options.params || params), reloadTimes, skip]);
160
- const reload = useCallback(
216
+ }, [action, options.params || params, reloadTimes, skip]);
217
+ const reload = useEqualCallback(
161
218
  (params2) => {
219
+ if (skip) setSkip(false);
162
220
  if (params2) setParams(params2);
163
221
  setReloadTimes((prev) => prev + 1);
164
- return promise;
222
+ return promiseRef.current;
165
223
  },
166
- [params]
224
+ [params, skip]
167
225
  );
168
226
  return {
169
227
  action,
170
228
  params,
171
229
  loading,
172
- data: options?.data || data,
230
+ data: options.data || data,
173
231
  reloadTimes,
174
232
  error,
175
- promise,
233
+ promise: promiseRef.current,
176
234
  reload,
177
- setData: options?.setData || setData,
235
+ setData: options.setData || setData,
178
236
  setLoading,
179
- setPromise,
237
+ setPromise: (newPromise) => typeof newPromise === "function" ? newPromise(promiseRef.current) : promiseRef.current = newPromise,
180
238
  setError
181
239
  };
182
240
  }
@@ -258,4 +316,4 @@ var OptionalWrapper = ({ condition, Wrapper, wrapperProps, children }) => {
258
316
  };
259
317
  OptionalWrapper.whyDidYouRender = true;
260
318
 
261
- export { ErrorBoundary, FaasDataWrapper, FaasReactClient, OptionalWrapper, createSplittingContext, faas, getClient, useConstant, useFaas, withFaasData };
319
+ 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.0",
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.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "react": "*"