@faasjs/react 8.0.0-beta.14 → 8.0.0-beta.15

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
@@ -1,46 +1,5 @@
1
1
  # @faasjs/react
2
2
 
3
- React plugin for FaasJS.
4
-
5
- [![License: MIT](https://img.shields.io/npm/l/@faasjs/react.svg)](https://github.com/faasjs/faasjs/blob/main/packages/react/LICENSE)
6
- [![NPM Version](https://img.shields.io/npm/v/@faasjs/react.svg)](https://www.npmjs.com/package/@faasjs/react)
7
-
8
- Includes browser client utilities (`FaasBrowserClient`, `ResponseError`, `setMock`) and React helpers.
9
-
10
- ## Features
11
-
12
- - Support [FaasJS Request Specifications](https://faasjs.com/guide/request-spec.html).
13
- - Support global and per-request configurations.
14
- - Compatible with [why-did-you-render](https://github.com/welldone-software/why-did-you-render).
15
- - Additional React functions:
16
- - Utils:
17
- - `equal`: Compare two values for deep equality.
18
- - `createSplittingContext`: Create a context for code splitting.
19
- - `useSplittingState`: Create splitting states.
20
- - Hooks:
21
- - `useEqualMemoize`: Memoize a value with deep equality.
22
- - `useEqualEffect`: Run an effect with deep equality.
23
- - `useEqualMemo`: Memoize a value with deep equality.
24
- - `useEqualCallback`: Memoize a callback with deep equality.
25
- - `useConstant`: Create a constant value with hooks.
26
- - `usePrevious`: Get the previous value of a state.
27
- - `useStateRef`: Create a state with a ref.
28
- - Components:
29
- - `OptionalWrapper`: Render a component optionally.
30
- - `ErrorBoundary`: Catch errors in the component tree.
31
- - Fetch Data:
32
- - `faas`: Fetch data from FaasJS.
33
- - `useFaas`: Fetch data from FaasJS with hooks.
34
- - `useFaasStream`: Fetch streaming data from FaasJS with hooks.
35
- - `FaasDataWrapper`: Fetch data from FaasJS with a wrapper component.
36
- - `withFaasData`: Fetch data from FaasJS using a higher-order component (HOC).
37
-
38
- ## Install
39
-
40
- ```sh
41
- npm install @faasjs/react react
42
- ```
43
-
44
3
  ## Functions
45
4
 
46
5
  - [createSplittingContext](functions/createSplittingContext.md)
package/dist/index.cjs CHANGED
@@ -39,7 +39,7 @@ function generateId(prefix = "", length = 18) {
39
39
  * @param {ResponseProps<T>} [props] - Response properties including status, headers, body, and data.
40
40
  * All properties are optional with sensible defaults.
41
41
  *
42
- * @remarks
42
+ * Notes:
43
43
  * - status defaults to 200 if data or body is present, 204 otherwise
44
44
  * - body is automatically populated from data if not explicitly provided
45
45
  * - headers defaults to an empty object if not provided
@@ -157,7 +157,7 @@ var Response = class {
157
157
  * including HTTP status code, response headers, response body, and the original error.
158
158
  *
159
159
  * @class ResponseError
160
- * @extends {Error}
160
+ * @augments Error
161
161
  *
162
162
  * @property {number} status - The HTTP status code of the failed response. Defaults to 500 if not provided.
163
163
  * @property {ResponseHeaders} headers - The response headers from the failed request.
@@ -238,7 +238,7 @@ var Response = class {
238
238
  * })
239
239
  * ```
240
240
  *
241
- * @remarks
241
+ * Notes:
242
242
  * - ResponseError is automatically thrown by the action method when the server returns an error (status >= 400)
243
243
  * - The error message from server responses is extracted from body.error.message if available
244
244
  * - When created from an Error object, the original error is preserved in the originalError property
@@ -261,7 +261,7 @@ var ResponseError = class extends Error {
261
261
  message: data,
262
262
  ...options
263
263
  };
264
- else if (data instanceof Error || data?.constructor?.name?.includes("Error")) props = {
264
+ else if (data instanceof Error || typeof data === "object" && data !== null && typeof data.constructor?.name === "string" && data.constructor.name.includes("Error")) props = {
265
265
  message: data.message,
266
266
  originalError: data,
267
267
  ...options
@@ -344,7 +344,7 @@ function setMock(handler) {
344
344
  * - Streaming support for large responses
345
345
  * - Multiple instance support with unique IDs
346
346
  *
347
- * @remarks
347
+ * Notes:
348
348
  * - All requests are POST requests by default
349
349
  * - Automatically adds X-FaasJS-Request-Id header for request tracking
350
350
  * - baseUrl must end with '/' (will throw Error if not)
@@ -488,7 +488,7 @@ var FaasBrowserClient = class {
488
488
  * @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
489
489
  * @throws {NetworkError} When network request fails
490
490
  *
491
- * @remarks
491
+ * Notes:
492
492
  * - All requests are POST requests by default
493
493
  * - Action path is automatically converted to lowercase
494
494
  * - A unique request ID is generated for each request and sent in X-FaasJS-Request-Id header
@@ -647,7 +647,7 @@ var FaasBrowserClient = class {
647
647
  headers,
648
648
  body
649
649
  }));
650
- } catch (_) {
650
+ } catch {
651
651
  return Promise.reject(new ResponseError({
652
652
  message: res,
653
653
  status: response.status,
@@ -733,13 +733,18 @@ function equal(a, b) {
733
733
  * @returns The memoized value.
734
734
  */
735
735
  function useEqualMemoize(value) {
736
+ const ref = (0, react.useRef)(value);
737
+ if (!equal(value, ref.current)) ref.current = value;
738
+ return ref.current;
739
+ }
740
+ function useEqualSignal(value) {
736
741
  const ref = (0, react.useRef)(value);
737
742
  const signalRef = (0, react.useRef)(0);
738
743
  if (!equal(value, ref.current)) {
739
744
  ref.current = value;
740
745
  signalRef.current += 1;
741
746
  }
742
- return (0, react.useMemo)(() => ref.current, [signalRef.current]);
747
+ return signalRef.current;
743
748
  }
744
749
  /**
745
750
  * Custom hook that works like `useEffect` but uses deep comparison on dependencies.
@@ -749,7 +754,7 @@ function useEqualMemoize(value) {
749
754
  * @returns The result of the `useEffect` hook with memoized dependencies.
750
755
  */
751
756
  function useEqualEffect(callback, dependencies) {
752
- return (0, react.useEffect)(callback, useEqualMemoize(dependencies));
757
+ return (0, react.useEffect)(callback, [useEqualSignal(dependencies)]);
753
758
  }
754
759
  /**
755
760
  * Custom hook that works like `useMemo` but uses deep comparison on dependencies.
@@ -759,7 +764,12 @@ function useEqualEffect(callback, dependencies) {
759
764
  * @returns The result of the `useMemo` hook with memoized dependencies.
760
765
  */
761
766
  function useEqualMemo(callback, dependencies) {
762
- return (0, react.useMemo)(callback, useEqualMemoize(dependencies));
767
+ const signal = useEqualSignal(dependencies);
768
+ const callbackRef = (0, react.useRef)(callback);
769
+ callbackRef.current = callback;
770
+ return (0, react.useMemo)(() => {
771
+ return callbackRef.current();
772
+ }, [signal]);
763
773
  }
764
774
  /**
765
775
  * Custom hook that works like `useCallback` but uses deep comparison on dependencies.
@@ -769,7 +779,7 @@ function useEqualMemo(callback, dependencies) {
769
779
  * @returns The result of the `useCallback` hook with memoized dependencies.
770
780
  */
771
781
  function useEqualCallback(callback, dependencies) {
772
- return (0, react.useCallback)((...args) => callback(...args), useEqualMemoize(dependencies));
782
+ return (0, react.useCallback)((...args) => callback(...args), [useEqualSignal(dependencies)]);
773
783
  }
774
784
  //#endregion
775
785
  //#region src/FaasDataWrapper.tsx
@@ -871,7 +881,8 @@ function useFaas(action, defaultParams, options = {}) {
871
881
  const nextData = r.data;
872
882
  setFails(0);
873
883
  setError(null);
874
- options.setData ? options.setData(nextData) : localSetData(nextData);
884
+ if (options.setData) options.setData(nextData);
885
+ else localSetData(nextData);
875
886
  setLoading(false);
876
887
  for (const { resolve } of pendingReloadsRef.current.values()) resolve(nextData);
877
888
  pendingReloadsRef.current.clear();
@@ -1349,34 +1360,30 @@ async function validValues(rules, items, values, lang) {
1349
1360
  //#region src/Form/Footer.tsx
1350
1361
  function FormFooter() {
1351
1362
  const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
1352
- const handleSubmit = (0, react.useCallback)(async () => {
1353
- setSubmitting(true);
1354
- setErrors({});
1355
- const errors = await validValues(rules, items, valuesRef.current, lang);
1356
- if (Object.keys(errors).length) {
1357
- setErrors(errors);
1358
- setSubmitting(false);
1359
- return;
1360
- }
1361
- onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1362
- }, [
1363
- setSubmitting,
1364
- setErrors,
1365
- rules,
1366
- items,
1367
- lang,
1368
- onSubmit
1369
- ]);
1370
- return (0, react.useMemo)(() => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Elements.Button, {
1363
+ const Button = Elements.Button;
1364
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1371
1365
  submitting,
1372
- submit: handleSubmit,
1366
+ submit: (0, react.useCallback)(async () => {
1367
+ setSubmitting(true);
1368
+ setErrors({});
1369
+ const errors = await validValues(rules, items, valuesRef.current, lang);
1370
+ if (Object.keys(errors).length) {
1371
+ setErrors(errors);
1372
+ setSubmitting(false);
1373
+ return;
1374
+ }
1375
+ onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1376
+ }, [
1377
+ setSubmitting,
1378
+ setErrors,
1379
+ rules,
1380
+ items,
1381
+ valuesRef,
1382
+ lang,
1383
+ onSubmit
1384
+ ]),
1373
1385
  children: lang.submit
1374
- }), [
1375
- submitting,
1376
- handleSubmit,
1377
- lang.submit,
1378
- Elements.Button
1379
- ]);
1386
+ });
1380
1387
  }
1381
1388
  FormFooter.displayName = "FormFooter";
1382
1389
  //#endregion
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import { FaasAction, FaasAction as FaasAction$1, FaasActionUnionType, FaasActionUnionType as FaasActionUnionType$1, FaasData, FaasData as FaasData$1, FaasParams, FaasParams as FaasParams$1 } from "@faasjs/types";
1
2
  import * as react from "react";
2
3
  import { Component, ComponentProps, ComponentType, Dispatch, ErrorInfo, JSX, JSXElementConstructor, ReactElement, ReactNode, RefObject, SetStateAction } from "react";
3
4
  import * as react_jsx_runtime0 from "react/jsx-runtime";
4
- import { FaasAction, FaasAction as FaasAction$1, FaasActionUnionType, FaasActionUnionType as FaasActionUnionType$1, FaasData, FaasData as FaasData$1, FaasParams, FaasParams as FaasParams$1 } from "@faasjs/types";
5
5
 
6
6
  //#region src/generateId.d.ts
7
7
  /**
@@ -24,7 +24,7 @@ declare function generateId(prefix?: string, length?: number): string;
24
24
  * Ensures that base URLs used in FaasJS requests always have a trailing '/' character,
25
25
  * which is required for proper URL construction when appending action paths.
26
26
  *
27
- * @remarks
27
+ * Notes:
28
28
  * - Type only accepts strings ending with '/' (e.g., 'https://api.example.com/', '/')
29
29
  * - Strings without trailing '/' will fail TypeScript type checking
30
30
  * - Used by FaasBrowserClient constructor and Options type
@@ -41,7 +41,7 @@ type BaseUrl = `${string}/`;
41
41
  * Extends the standard RequestInit interface with FaasJS-specific options for
42
42
  * customizing request behavior, adding request hooks, and overriding defaults.
43
43
  *
44
- * @remarks
44
+ * Notes:
45
45
  * - Options can be provided at client creation (defaultOptions) or per-request
46
46
  * - Per-request options override client default options
47
47
  * - headers are merged: per-request headers override default headers
@@ -73,7 +73,7 @@ type BaseUrl = `${string}/`;
73
73
  * Useful for processing large data incrementally or working with binary data streams.
74
74
  * When false or undefined, returns a wrapped Response with automatic JSON parsing.
75
75
  *
76
- * @extends RequestInit
76
+ * @augments RequestInit
77
77
  * @see FaasBrowserClient for client creation
78
78
  * @see Response for response object structure
79
79
  */
@@ -103,7 +103,7 @@ type Options = RequestInit & {
103
103
  * @property {string} [key] - Dynamic string keys for header names (e.g., 'Content-Type', 'Authorization').
104
104
  * Values must be strings. Multiple values for the same key are not supported.
105
105
  *
106
- * @remarks
106
+ * Notes:
107
107
  * - Headers are case-insensitive in HTTP but stored with exact casing in this object
108
108
  * - Common headers include: Content-Type, Authorization, X-Request-Id, X-Custom-Header
109
109
  * - No support for multi-value headers (use comma-separated values instead)
@@ -131,7 +131,7 @@ type ResponseHeaders = {
131
131
  *
132
132
  * @returns {Promise<Response<FaasData<PathOrData>> | Response>} - A Promise resolving to a Response object
133
133
  *
134
- * @remarks
134
+ * Notes:
135
135
  * - Used internally by FaasBrowserClient.action method
136
136
  * - Provides type-safe action method signature
137
137
  * - Return type includes both typed and untyped Response variants
@@ -163,7 +163,7 @@ type FaasBrowserClientAction = <PathOrData extends FaasActionUnionType$1>(action
163
163
  * @property {T} [data] - The parsed JSON data to include in the response.
164
164
  * Optional: contains the response payload when JSON data is provided.
165
165
  *
166
- * @remarks
166
+ * Notes:
167
167
  * - All properties are optional
168
168
  * - At least one of data or body should be provided for meaningful responses
169
169
  * - The Response class automatically defaults status to 200 or 204 based on content
@@ -199,7 +199,7 @@ type ResponseProps<T = any> = {
199
199
  * @param {ResponseProps<T>} [props] - Response properties including status, headers, body, and data.
200
200
  * All properties are optional with sensible defaults.
201
201
  *
202
- * @remarks
202
+ * Notes:
203
203
  * - status defaults to 200 if data or body is present, 204 otherwise
204
204
  * - body is automatically populated from data if not explicitly provided
205
205
  * - headers defaults to an empty object if not provided
@@ -318,7 +318,7 @@ type ResponseErrorProps = {
318
318
  * including HTTP status code, response headers, response body, and the original error.
319
319
  *
320
320
  * @class ResponseError
321
- * @extends {Error}
321
+ * @augments Error
322
322
  *
323
323
  * @property {number} status - The HTTP status code of the failed response. Defaults to 500 if not provided.
324
324
  * @property {ResponseHeaders} headers - The response headers from the failed request.
@@ -399,7 +399,7 @@ type ResponseErrorProps = {
399
399
  * })
400
400
  * ```
401
401
  *
402
- * @remarks
402
+ * Notes:
403
403
  * - ResponseError is automatically thrown by the action method when the server returns an error (status >= 400)
404
404
  * - The error message from server responses is extracted from body.error.message if available
405
405
  * - When created from an Error object, the original error is preserved in the originalError property
@@ -441,7 +441,7 @@ declare class ResponseError extends Error {
441
441
  * - void: Returns an empty response (204 No Content)
442
442
  * - Error: Throws ResponseError when returning an Error object
443
443
  *
444
- * @remarks
444
+ * Notes:
445
445
  * - Used by setMock() function to mock API calls during tests
446
446
  * - Affects all FaasBrowserClient instances when set globally
447
447
  * - Can return different responses based on action or params
@@ -568,7 +568,7 @@ declare function setMock(handler: MockHandler | ResponseProps | Response | null)
568
568
  * - Streaming support for large responses
569
569
  * - Multiple instance support with unique IDs
570
570
  *
571
- * @remarks
571
+ * Notes:
572
572
  * - All requests are POST requests by default
573
573
  * - Automatically adds X-FaasJS-Request-Id header for request tracking
574
574
  * - baseUrl must end with '/' (will throw Error if not)
@@ -706,7 +706,7 @@ declare class FaasBrowserClient {
706
706
  * @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
707
707
  * @throws {NetworkError} When network request fails
708
708
  *
709
- * @remarks
709
+ * Notes:
710
710
  * - All requests are POST requests by default
711
711
  * - Action path is automatically converted to lowercase
712
712
  * - A unique request ID is generated for each request and sent in X-FaasJS-Request-Id header
@@ -1228,13 +1228,13 @@ type FormContextProps<Values extends Record<string, any> = Record<string, any>,
1228
1228
  setErrors: Dispatch<SetStateAction<Record<string, Error>>>;
1229
1229
  valuesRef: RefObject<Values>;
1230
1230
  };
1231
- declare const FormContextProvider: <NewT extends FormContextProps<Record<string, any>, FormElementTypes, FormRules> = FormContextProps<Record<string, any>, FormElementTypes, FormRules>>(props: {
1231
+ declare const FormContextProvider: <NewT extends FormContextProps<Record<string, any>, FormElementTypes, FormRules> = FormContextProps<Record<string, any>, FormElementTypes, FormRules>>(this: void, props: {
1232
1232
  value?: Partial<NewT>;
1233
1233
  children: react.ReactNode;
1234
1234
  memo?: true | any[];
1235
1235
  initializeStates?: Partial<NewT>;
1236
1236
  }) => react.ReactNode;
1237
- declare const useFormContext: <NewT extends FormContextProps<Record<string, any>, FormElementTypes, FormRules> = FormContextProps<Record<string, any>, FormElementTypes, FormRules>>() => Readonly<NewT>;
1237
+ declare const useFormContext: <NewT extends FormContextProps<Record<string, any>, FormElementTypes, FormRules> = FormContextProps<Record<string, any>, FormElementTypes, FormRules>>(this: void) => Readonly<NewT>;
1238
1238
  //#endregion
1239
1239
  //#region src/OptionalWrapper.d.ts
1240
1240
  type OptionalWrapperProps<TWrapper extends ComponentType<{
@@ -1337,7 +1337,7 @@ declare function createSplittingContext<T extends Record<string, any>>(defaultVa
1337
1337
  * }
1338
1338
  * ```
1339
1339
  */
1340
- Provider<NewT extends T = T>(props: {
1340
+ Provider<NewT extends T = T>(this: void, props: {
1341
1341
  value?: Partial<NewT>;
1342
1342
  children: ReactNode;
1343
1343
  /**
@@ -1378,7 +1378,7 @@ declare function createSplittingContext<T extends Record<string, any>>(defaultVa
1378
1378
  * }
1379
1379
  * ```
1380
1380
  */
1381
- use: <NewT extends T = T>() => Readonly<NewT>;
1381
+ use: <NewT extends T = T>(this: void) => Readonly<NewT>;
1382
1382
  };
1383
1383
  //#endregion
1384
1384
  //#region src/splittingState.d.ts
@@ -1487,4 +1487,4 @@ declare function usePrevious<T = any>(value: T): T | undefined;
1487
1487
  */
1488
1488
  declare function useStateRef<T = any>(initialValue?: T): [T | null, Dispatch<SetStateAction<T | null>>, RefObject<T | null>];
1489
1489
  //#endregion
1490
- export { BaseUrl, ErrorBoundary, ErrorBoundaryProps, ErrorChildrenProps, type FaasAction, type FaasActionUnionType, FaasBrowserClient, FaasBrowserClientAction, type FaasData, FaasDataInjection, FaasDataWrapper, FaasDataWrapperProps, FaasDataWrapperRef, type FaasParams, FaasReactClient, FaasReactClientInstance, FaasReactClientOptions, FormContainer as Form, type FormButtonElementProps, FormContextProps, FormContextProvider, FormDefaultElements, FormDefaultLang, FormDefaultRules, FormDefaultRulesOptions, FormElementTypes, FormInput, type FormInputElementProps, FormInputProps, FormItem, FormItemName, FormItemProps, type FormLabelElementProps, FormLang, type FormProps, FormRule, FormRules, InferFormInputProps, InferFormRulesOptions, InferRuleOption, MockHandler, OnError, OptionalWrapper, OptionalWrapperProps, Options, Response, ResponseError, ResponseErrorProps, ResponseHeaders, ResponseProps, StateSetters, StatesWithSetters, UseFaasStreamOptions, UseFaasStreamResult, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasOptions, useFaasStream, useFormContext, usePrevious, useSplittingState, useStateRef, validValues, withFaasData };
1490
+ export { BaseUrl, ErrorBoundary, ErrorBoundaryProps, ErrorChildrenProps, type FaasAction, type FaasActionUnionType, FaasBrowserClient, FaasBrowserClientAction, type FaasData, FaasDataInjection, FaasDataWrapper, FaasDataWrapperProps, FaasDataWrapperRef, type FaasParams, FaasReactClient, FaasReactClientInstance, FaasReactClientOptions, FormContainer as Form, type FormButtonElementProps, FormContextProps, FormContextProvider, FormDefaultElements, FormDefaultLang, FormDefaultRules, FormDefaultRulesOptions, FormElementTypes, FormInput, type FormInputElementProps, FormInputProps, FormItem, FormItemName, FormItemProps, type FormLabelElementProps, FormLang, FormProps, FormRule, FormRules, InferFormInputProps, InferFormRulesOptions, InferRuleOption, MockHandler, OnError, OptionalWrapper, OptionalWrapperProps, Options, Response, ResponseError, ResponseErrorProps, ResponseHeaders, ResponseProps, StateSetters, StatesWithSetters, UseFaasStreamOptions, UseFaasStreamResult, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasOptions, useFaasStream, useFormContext, usePrevious, useSplittingState, useStateRef, validValues, withFaasData };
package/dist/index.mjs CHANGED
@@ -38,7 +38,7 @@ function generateId(prefix = "", length = 18) {
38
38
  * @param {ResponseProps<T>} [props] - Response properties including status, headers, body, and data.
39
39
  * All properties are optional with sensible defaults.
40
40
  *
41
- * @remarks
41
+ * Notes:
42
42
  * - status defaults to 200 if data or body is present, 204 otherwise
43
43
  * - body is automatically populated from data if not explicitly provided
44
44
  * - headers defaults to an empty object if not provided
@@ -156,7 +156,7 @@ var Response = class {
156
156
  * including HTTP status code, response headers, response body, and the original error.
157
157
  *
158
158
  * @class ResponseError
159
- * @extends {Error}
159
+ * @augments Error
160
160
  *
161
161
  * @property {number} status - The HTTP status code of the failed response. Defaults to 500 if not provided.
162
162
  * @property {ResponseHeaders} headers - The response headers from the failed request.
@@ -237,7 +237,7 @@ var Response = class {
237
237
  * })
238
238
  * ```
239
239
  *
240
- * @remarks
240
+ * Notes:
241
241
  * - ResponseError is automatically thrown by the action method when the server returns an error (status >= 400)
242
242
  * - The error message from server responses is extracted from body.error.message if available
243
243
  * - When created from an Error object, the original error is preserved in the originalError property
@@ -260,7 +260,7 @@ var ResponseError = class extends Error {
260
260
  message: data,
261
261
  ...options
262
262
  };
263
- else if (data instanceof Error || data?.constructor?.name?.includes("Error")) props = {
263
+ else if (data instanceof Error || typeof data === "object" && data !== null && typeof data.constructor?.name === "string" && data.constructor.name.includes("Error")) props = {
264
264
  message: data.message,
265
265
  originalError: data,
266
266
  ...options
@@ -343,7 +343,7 @@ function setMock(handler) {
343
343
  * - Streaming support for large responses
344
344
  * - Multiple instance support with unique IDs
345
345
  *
346
- * @remarks
346
+ * Notes:
347
347
  * - All requests are POST requests by default
348
348
  * - Automatically adds X-FaasJS-Request-Id header for request tracking
349
349
  * - baseUrl must end with '/' (will throw Error if not)
@@ -487,7 +487,7 @@ var FaasBrowserClient = class {
487
487
  * @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
488
488
  * @throws {NetworkError} When network request fails
489
489
  *
490
- * @remarks
490
+ * Notes:
491
491
  * - All requests are POST requests by default
492
492
  * - Action path is automatically converted to lowercase
493
493
  * - A unique request ID is generated for each request and sent in X-FaasJS-Request-Id header
@@ -646,7 +646,7 @@ var FaasBrowserClient = class {
646
646
  headers,
647
647
  body
648
648
  }));
649
- } catch (_) {
649
+ } catch {
650
650
  return Promise.reject(new ResponseError({
651
651
  message: res,
652
652
  status: response.status,
@@ -732,13 +732,18 @@ function equal(a, b) {
732
732
  * @returns The memoized value.
733
733
  */
734
734
  function useEqualMemoize(value) {
735
+ const ref = useRef(value);
736
+ if (!equal(value, ref.current)) ref.current = value;
737
+ return ref.current;
738
+ }
739
+ function useEqualSignal(value) {
735
740
  const ref = useRef(value);
736
741
  const signalRef = useRef(0);
737
742
  if (!equal(value, ref.current)) {
738
743
  ref.current = value;
739
744
  signalRef.current += 1;
740
745
  }
741
- return useMemo(() => ref.current, [signalRef.current]);
746
+ return signalRef.current;
742
747
  }
743
748
  /**
744
749
  * Custom hook that works like `useEffect` but uses deep comparison on dependencies.
@@ -748,7 +753,7 @@ function useEqualMemoize(value) {
748
753
  * @returns The result of the `useEffect` hook with memoized dependencies.
749
754
  */
750
755
  function useEqualEffect(callback, dependencies) {
751
- return useEffect(callback, useEqualMemoize(dependencies));
756
+ return useEffect(callback, [useEqualSignal(dependencies)]);
752
757
  }
753
758
  /**
754
759
  * Custom hook that works like `useMemo` but uses deep comparison on dependencies.
@@ -758,7 +763,12 @@ function useEqualEffect(callback, dependencies) {
758
763
  * @returns The result of the `useMemo` hook with memoized dependencies.
759
764
  */
760
765
  function useEqualMemo(callback, dependencies) {
761
- return useMemo(callback, useEqualMemoize(dependencies));
766
+ const signal = useEqualSignal(dependencies);
767
+ const callbackRef = useRef(callback);
768
+ callbackRef.current = callback;
769
+ return useMemo(() => {
770
+ return callbackRef.current();
771
+ }, [signal]);
762
772
  }
763
773
  /**
764
774
  * Custom hook that works like `useCallback` but uses deep comparison on dependencies.
@@ -768,7 +778,7 @@ function useEqualMemo(callback, dependencies) {
768
778
  * @returns The result of the `useCallback` hook with memoized dependencies.
769
779
  */
770
780
  function useEqualCallback(callback, dependencies) {
771
- return useCallback((...args) => callback(...args), useEqualMemoize(dependencies));
781
+ return useCallback((...args) => callback(...args), [useEqualSignal(dependencies)]);
772
782
  }
773
783
  const FaasDataWrapper = forwardRef((props, ref) => {
774
784
  const requestOptions = {
@@ -867,7 +877,8 @@ function useFaas(action, defaultParams, options = {}) {
867
877
  const nextData = r.data;
868
878
  setFails(0);
869
879
  setError(null);
870
- options.setData ? options.setData(nextData) : localSetData(nextData);
880
+ if (options.setData) options.setData(nextData);
881
+ else localSetData(nextData);
871
882
  setLoading(false);
872
883
  for (const { resolve } of pendingReloadsRef.current.values()) resolve(nextData);
873
884
  pendingReloadsRef.current.clear();
@@ -1345,34 +1356,30 @@ async function validValues(rules, items, values, lang) {
1345
1356
  //#region src/Form/Footer.tsx
1346
1357
  function FormFooter() {
1347
1358
  const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
1348
- const handleSubmit = useCallback(async () => {
1349
- setSubmitting(true);
1350
- setErrors({});
1351
- const errors = await validValues(rules, items, valuesRef.current, lang);
1352
- if (Object.keys(errors).length) {
1353
- setErrors(errors);
1354
- setSubmitting(false);
1355
- return;
1356
- }
1357
- onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1358
- }, [
1359
- setSubmitting,
1360
- setErrors,
1361
- rules,
1362
- items,
1363
- lang,
1364
- onSubmit
1365
- ]);
1366
- return useMemo(() => /* @__PURE__ */ jsx(Elements.Button, {
1359
+ const Button = Elements.Button;
1360
+ return /* @__PURE__ */ jsx(Button, {
1367
1361
  submitting,
1368
- submit: handleSubmit,
1362
+ submit: useCallback(async () => {
1363
+ setSubmitting(true);
1364
+ setErrors({});
1365
+ const errors = await validValues(rules, items, valuesRef.current, lang);
1366
+ if (Object.keys(errors).length) {
1367
+ setErrors(errors);
1368
+ setSubmitting(false);
1369
+ return;
1370
+ }
1371
+ onSubmit(valuesRef.current).finally(() => setSubmitting(false));
1372
+ }, [
1373
+ setSubmitting,
1374
+ setErrors,
1375
+ rules,
1376
+ items,
1377
+ valuesRef,
1378
+ lang,
1379
+ onSubmit
1380
+ ]),
1369
1381
  children: lang.submit
1370
- }), [
1371
- submitting,
1372
- handleSubmit,
1373
- lang.submit,
1374
- Elements.Button
1375
- ]);
1382
+ });
1376
1383
  }
1377
1384
  FormFooter.displayName = "FormFooter";
1378
1385
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "8.0.0-beta.14",
3
+ "version": "8.0.0-beta.15",
4
4
  "homepage": "https://faasjs.com/doc/react/",
5
5
  "bugs": {
6
6
  "url": "https://github.com/faasjs/faasjs/issues"
@@ -26,16 +26,13 @@
26
26
  "require": "./dist/index.cjs"
27
27
  }
28
28
  },
29
- "scripts": {
30
- "build": "tsdown src/index.ts --config ../../tsdown.config.ts --external react --dts.eager --no-sourcemap --no-dts.sourcemap"
31
- },
32
29
  "devDependencies": {
33
- "@faasjs/types": ">=8.0.0-beta.14",
30
+ "@faasjs/types": ">=8.0.0-beta.15",
34
31
  "@types/react": "^19.0.0",
35
32
  "react": "^19.0.0"
36
33
  },
37
34
  "peerDependencies": {
38
- "@faasjs/types": ">=8.0.0-beta.14"
35
+ "@faasjs/types": ">=8.0.0-beta.15"
39
36
  },
40
37
  "engines": {
41
38
  "node": ">=24.0.0",