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

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/dist/index.mjs CHANGED
@@ -35,9 +35,6 @@ function generateId(prefix = "", length = 18) {
35
35
  * @property {T} [data] - The parsed JSON data from the response.
36
36
  * Optional property that contains the response payload when JSON is provided.
37
37
  *
38
- * @param {ResponseProps<T>} [props] - Response properties including status, headers, body, and data.
39
- * All properties are optional with sensible defaults.
40
- *
41
38
  * Notes:
42
39
  * - status defaults to 200 if data or body is present, 204 otherwise
43
40
  * - body is automatically populated from data if not explicitly provided
@@ -141,6 +138,12 @@ var Response = class {
141
138
  headers;
142
139
  body;
143
140
  data;
141
+ /**
142
+ * Create a wrapped response object.
143
+ *
144
+ * @param props - Response properties including status, headers, body, and data.
145
+ * @returns Wrapped response instance.
146
+ */
144
147
  constructor(props = {}) {
145
148
  this.status = props.status || (props.data || props.body ? 200 : 204);
146
149
  this.headers = props.headers || {};
@@ -155,7 +158,6 @@ var Response = class {
155
158
  * Extends the built-in Error class to provide additional information about failed requests,
156
159
  * including HTTP status code, response headers, response body, and the original error.
157
160
  *
158
- * @class ResponseError
159
161
  * @augments Error
160
162
  *
161
163
  * @property {number} status - The HTTP status code of the failed response. Defaults to 500 if not provided.
@@ -163,9 +165,6 @@ var Response = class {
163
165
  * @property {any} body - The response body containing error details or the original error if available.
164
166
  * @property {Error} [originalError] - The original Error object if this ResponseError was created from another Error.
165
167
  *
166
- * @param {string | Error | ResponseErrorProps} data - The error message, an Error object, or a ResponseErrorProps object.
167
- * @param {Omit<ResponseErrorProps, 'message' | 'originalError'>} [options] - Additional options for the error (status, headers, body).
168
- *
169
168
  * @example Basic error with message
170
169
  * ```ts
171
170
  * throw new ResponseError('User not found')
@@ -254,6 +253,13 @@ var ResponseError = class extends Error {
254
253
  headers;
255
254
  body;
256
255
  originalError;
256
+ /**
257
+ * Create a ResponseError from a message, Error, or structured response error payload.
258
+ *
259
+ * @param data - Error message, Error object, or structured response error props.
260
+ * @param options - Additional options such as status, headers, and body.
261
+ * @returns ResponseError instance.
262
+ */
257
263
  constructor(data, options) {
258
264
  let props;
259
265
  if (typeof data === "string") props = {
@@ -405,11 +411,8 @@ var FaasBrowserClient = class {
405
411
  /**
406
412
  * Creates a new FaasBrowserClient instance.
407
413
  *
408
- * @param baseUrl - Base URL for all API requests. Must end with '/'. Defaults to '/' for relative requests.
409
- * @throws {Error} If baseUrl does not end with '/'
410
- * @param options - Configuration options for the client.
411
- * Supports default headers, beforeRequest hook, custom request function,
412
- * baseUrl override, and streaming mode.
414
+ * @param baseUrl - Base URL for all API requests. Must end with `/`. Defaults to `/` for relative requests.
415
+ * @param options - Default request options such as headers, hooks, request override, or stream mode.
413
416
  *
414
417
  * @example Basic initialization
415
418
  * ```ts
@@ -460,7 +463,7 @@ var FaasBrowserClient = class {
460
463
  * })
461
464
  * ```
462
465
  *
463
- * @throws {Error} When baseUrl does not end with '/'
466
+ * @throws {Error} When `baseUrl` does not end with `/`
464
467
  */
465
468
  constructor(baseUrl = "/", options = Object.create(null)) {
466
469
  if (baseUrl && !baseUrl.endsWith("/")) throw Error("[FaasJS] baseUrl should end with /");
@@ -485,7 +488,7 @@ var FaasBrowserClient = class {
485
488
  *
486
489
  * @throws {Error} When action is not provided or is empty
487
490
  * @throws {ResponseError} When the server returns an error response (status >= 400 or body.error exists)
488
- * @throws {NetworkError} When network request fails
491
+ * @throws {Error} When the request fails before a response is received
489
492
  *
490
493
  * Notes:
491
494
  * - All requests are POST requests by default
@@ -539,11 +542,7 @@ var FaasBrowserClient = class {
539
542
  * email: string
540
543
  * }
541
544
  *
542
- * const response = await client.action<{
543
- * action: 'user'
544
- * params: { id: number }
545
- * data: UserData
546
- * }>('user', { id: 123 })
545
+ * const response = await client.action<UserData>('user', { id: 123 })
547
546
  * console.log(response.data.name) // TypeScript knows it's a string
548
547
  * ```
549
548
  *
@@ -555,7 +554,7 @@ var FaasBrowserClient = class {
555
554
  * } catch (error) {
556
555
  * if (error instanceof ResponseError) {
557
556
  * console.error(`Server error: ${error.message}`, error.status)
558
- * if (error.data) console.error('Error details:', error.data)
557
+ * if (error.body) console.error('Error details:', error.body)
559
558
  * } else {
560
559
  * console.error('Network error:', error)
561
560
  * }
@@ -661,17 +660,20 @@ var FaasBrowserClient = class {
661
660
  //#endregion
662
661
  //#region src/faas.ts
663
662
  /**
664
- * Request faas server
663
+ * Call the currently configured FaasReactClient.
665
664
  *
666
- * @param action {string} action name
667
- * @param params {object} action params
668
- * @returns {Promise<Response<any>>}
665
+ * @param action - Action path to invoke.
666
+ * @param params - Parameters sent to the action.
667
+ * @param options - Optional per-request overrides such as headers or base URL.
668
+ * @returns Response returned by the active browser client.
669
669
  *
670
670
  * @example
671
671
  * ```ts
672
- * faas<{ title: string }>('post/get', { id: 1 }).then(res => {
673
- * console.log(res.data.title)
674
- * })
672
+ * import { faas } from '@faasjs/react'
673
+ *
674
+ * const response = await faas<{ title: string }>('post/get', { id: 1 })
675
+ *
676
+ * console.log(response.data.title)
675
677
  * ```
676
678
  */
677
679
  async function faas(action, params, options) {
@@ -827,16 +829,23 @@ function withFaasData(Component, faasProps) {
827
829
  //#endregion
828
830
  //#region src/useFaas.tsx
829
831
  /**
830
- * Request faas server with React hook
832
+ * Request FaasJS data and keep request state in React state.
831
833
  *
832
- * @param action {string} action name
833
- * @param defaultParams {object} initial action params
834
- * @returns {FaasDataInjection<any>}
834
+ * `useFaas` sends an initial request unless `skip` is enabled, and returns
835
+ * request state plus helpers for reloading, updating data, and handling errors.
836
+ *
837
+ * @param action - Action path to invoke.
838
+ * @param defaultParams - Params used for the initial request and future reloads.
839
+ * @param options - Optional hook configuration such as controlled data, debounce, and skip logic.
840
+ * @returns Request state and helper methods for the action.
835
841
  *
836
842
  * @example
837
843
  * ```tsx
838
- * function Post ({ id }) {
844
+ * import { useFaas } from '@faasjs/react'
845
+ *
846
+ * function Post({ id }: { id: number }) {
839
847
  * const { data } = useFaas<{ title: string }>('post/get', { id })
848
+ *
840
849
  * return <h1>{data.title}</h1>
841
850
  * }
842
851
  * ```
@@ -956,14 +965,20 @@ function useFaas(action, defaultParams, options = {}) {
956
965
  //#region src/client.tsx
957
966
  const clients = {};
958
967
  /**
959
- * Before use faas, you should initialize a FaasReactClient.
968
+ * Create and register a FaasReactClient instance.
960
969
  *
961
- * @returns FaasReactClient instance.
970
+ * The returned client is stored by `baseUrl` and becomes the default client
971
+ * used by helpers such as {@link faas} and {@link useFaas}.
972
+ *
973
+ * @param options - Client configuration including base URL, default request options, and error hooks.
974
+ * @returns Registered FaasReactClient instance.
962
975
  *
963
976
  * @example
964
977
  * ```ts
978
+ * import { FaasReactClient } from '@faasjs/react'
979
+ *
965
980
  * const client = FaasReactClient({
966
- * baseUrl: 'localhost:8080/api/'
981
+ * baseUrl: 'http://localhost:8080/api/',
967
982
  * })
968
983
  * ```
969
984
  */
@@ -992,16 +1007,20 @@ function FaasReactClient({ baseUrl, options: clientOptions, onError } = { baseUr
992
1007
  return reactClient;
993
1008
  }
994
1009
  /**
995
- * Get FaasReactClient instance
1010
+ * Get a registered FaasReactClient instance.
996
1011
  *
997
- * @param host {string} empty string for default host
998
- * @returns {FaasReactClientInstance}
1012
+ * When `host` is omitted, the first registered client is returned. If no client
1013
+ * has been created yet, a default client is initialized automatically.
1014
+ *
1015
+ * @param host - Registered base URL to look up. Omit it to use the default client.
1016
+ * @returns Registered or newly created FaasReactClient instance.
999
1017
  *
1000
1018
  * @example
1001
1019
  * ```ts
1020
+ * import { getClient } from '@faasjs/react'
1021
+ *
1002
1022
  * getClient()
1003
- * // or
1004
- * getClient('another-host')
1023
+ * getClient('http://localhost:8080/api/')
1005
1024
  * ```
1006
1025
  */
1007
1026
  function getClient(host) {
@@ -1057,53 +1076,46 @@ var ErrorBoundary = class extends Component {
1057
1076
  }
1058
1077
  };
1059
1078
  //#endregion
1060
- //#region src/useStateRef.ts
1079
+ //#region src/OptionalWrapper.tsx
1061
1080
  /**
1062
- * Custom hook that returns a stateful value and a ref to that value.
1063
- *
1064
- * @template T - The type of the value.
1065
- * @param {T} initialValue - The initial value of the state.
1066
- * @returns {[T, (value: T) => void, RefObject<T>]} - The stateful value, a function to set the value, and a ref to the value.
1081
+ * A wrapper component that conditionally wraps its children with a provided wrapper component.
1067
1082
  *
1068
1083
  * @example
1069
1084
  * ```tsx
1070
- * import { useStateRef } from '@faasjs/react'
1085
+ * import { OptionalWrapper } from '@faasjs/react'
1071
1086
  *
1072
- * function MyComponent() {
1073
- * const [value, setValue, ref] = useStateRef(0)
1074
- *
1075
- * return (
1076
- * <div>
1077
- * <p>Value: {value}</p>
1078
- * <button onClick={() => setValue(value + 1)}>Increment</button>
1079
- * <button onClick={() => console.log(ref.current)}>Submit</button>
1080
- * </div>
1087
+ * const Wrapper = ({ children }: { children: React.ReactNode }) => (
1088
+ * <div className='wrapper'>{children}</div>
1089
+ * )
1090
+ *
1091
+ * const App = () => (
1092
+ * <OptionalWrapper condition={true} Wrapper={Wrapper}>
1093
+ * <span>Test</span>
1094
+ * </OptionalWrapper>
1081
1095
  * )
1096
+ * ```
1082
1097
  */
1083
- function useStateRef(initialValue) {
1084
- const [state, setState] = useState(initialValue ?? null);
1085
- const ref = useRef(state);
1086
- useEffect(() => {
1087
- ref.current = state;
1088
- }, [state]);
1089
- return [
1090
- state,
1091
- setState,
1092
- ref
1093
- ];
1098
+ function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
1099
+ if (condition) return /* @__PURE__ */ jsx(Wrapper, {
1100
+ ...wrapperProps,
1101
+ children
1102
+ });
1103
+ return children;
1094
1104
  }
1105
+ OptionalWrapper.displayName = "OptionalWrapper";
1095
1106
  //#endregion
1096
1107
  //#region src/splittingState.tsx
1097
1108
  /**
1098
- * A hook that initializes and splits state variables and their corresponding setters.
1109
+ * Create local state entries and matching setters for each key in an object.
1099
1110
  *
1100
1111
  * @template T - A generic type that extends a record with string keys and any values.
1101
- * @param {T} initialStates - An object containing the initial states.
1112
+ * @param initialStates - Object whose keys become state values and `setXxx` setters.
1113
+ * @returns Object containing the original keys plus generated setter functions.
1102
1114
  *
1103
1115
  * @example
1104
1116
  * ```tsx
1105
1117
  * function Counter() {
1106
- * const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' });
1118
+ * const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' })
1107
1119
  *
1108
1120
  * return <>{name}: {count}</>
1109
1121
  * }
@@ -1203,291 +1215,23 @@ function createSplittingContext(defaultValue) {
1203
1215
  };
1204
1216
  }
1205
1217
  //#endregion
1206
- //#region src/Form/context.tsx
1207
- const FormContext = createSplittingContext([
1208
- "items",
1209
- "onSubmit",
1210
- "Elements",
1211
- "lang",
1212
- "rules",
1213
- "submitting",
1214
- "setSubmitting",
1215
- "values",
1216
- "setValues",
1217
- "errors",
1218
- "setErrors",
1219
- "valuesRef"
1220
- ]);
1221
- const FormContextProvider = FormContext.Provider;
1222
- const useFormContext = FormContext.use;
1223
- //#endregion
1224
- //#region src/Form/Input.tsx
1225
- function processValue(input, rules) {
1226
- switch (rules?.type) {
1227
- case "number": return Number(input);
1228
- case "string": return String(input);
1229
- default: return input;
1230
- }
1231
- }
1232
- function FormInput({ name, rules, ...rest }) {
1233
- const { Elements, values, setValues } = useFormContext();
1234
- const value = values?.[name];
1235
- if (rest.Input) return /* @__PURE__ */ jsx(rest.Input, {
1236
- name,
1237
- value,
1238
- onChange: (v) => setValues((prev) => ({
1239
- ...prev,
1240
- [name]: processValue(v, rules)
1241
- })),
1242
- ...rest.props
1243
- });
1244
- return /* @__PURE__ */ jsx(Elements.Input, {
1245
- name,
1246
- value,
1247
- onChange: (v) => setValues((prev) => ({
1248
- ...prev,
1249
- [name]: processValue(v, rules)
1250
- })),
1251
- ...rest.props
1252
- });
1253
- }
1254
- FormInput.displayName = "FormInput";
1255
- //#endregion
1256
- //#region src/Form/Item.tsx
1257
- function FormItem(props) {
1258
- const { Elements, errors } = useFormContext();
1259
- return /* @__PURE__ */ jsx(props.label?.Label ?? Elements.Label, {
1260
- name: props.name,
1261
- ...props.label,
1262
- error: errors[props.name],
1263
- children: /* @__PURE__ */ jsx(FormInput, {
1264
- name: props.name,
1265
- ...props.input,
1266
- ...props.rules ? { rules: props.rules } : {}
1267
- })
1268
- });
1269
- }
1270
- FormItem.displayName = "FormItem";
1271
- //#endregion
1272
- //#region src/Form/Body.tsx
1273
- function FormBody() {
1274
- const { items } = useFormContext();
1275
- return items.map((item) => /* @__PURE__ */ jsx(FormItem, { ...item }, item.name));
1276
- }
1277
- FormBody.displayName = "FormBody";
1278
- //#endregion
1279
- //#region src/Form/elements/Button.tsx
1280
- const FormButtonElement = forwardRef(({ children, submit, submitting, ...props }, ref) => /* @__PURE__ */ jsx("button", {
1281
- type: "button",
1282
- disabled: submitting,
1283
- onClick: submit,
1284
- ...props,
1285
- ref,
1286
- children
1287
- }));
1288
- FormButtonElement.displayName = "FormButtonElement";
1289
- //#endregion
1290
- //#region src/Form/elements/Input.tsx
1291
- const FormInputElement = forwardRef(({ onChange, ...props }, ref) => /* @__PURE__ */ jsx("input", {
1292
- ...props,
1293
- onChange: (e) => onChange(e.target.value),
1294
- ref
1295
- }));
1296
- FormInputElement.displayName = "FormInputElement";
1297
- //#endregion
1298
- //#region src/Form/elements/Label.tsx
1299
- const FormLabelElement = ({ name, title, description, error, children }) => {
1300
- return /* @__PURE__ */ jsxs("label", { children: [
1301
- title ?? name,
1302
- children,
1303
- description,
1304
- error && /* @__PURE__ */ jsx("div", {
1305
- style: { color: "red" },
1306
- children: error.message
1307
- })
1308
- ] });
1309
- };
1310
- FormLabelElement.displayName = "FormLabelElement";
1311
- //#endregion
1312
- //#region src/Form/elements/index.ts
1313
- const FormDefaultElements = {
1314
- Label: FormLabelElement,
1315
- Input: FormInputElement,
1316
- Button: FormButtonElement
1317
- };
1318
- //#endregion
1319
- //#region src/Form/rules.ts
1320
- /**
1321
- * Default validation rules for a form.
1322
- */
1323
- const FormDefaultRules = {
1324
- required: async (value, _, lang) => {
1325
- if (value === null || value === void 0 || value === "" || Number.isNaN(value)) throw Error(lang?.required);
1326
- },
1327
- type: async (value, options, lang) => {
1328
- switch (options) {
1329
- case "string":
1330
- if (typeof value !== "string") throw Error(lang?.string);
1331
- break;
1332
- case "number":
1333
- if (Number.isNaN(Number(value))) throw Error(lang?.number);
1334
- break;
1335
- }
1336
- },
1337
- custom: async (value, options) => {
1338
- return options(value);
1339
- }
1340
- };
1341
- async function validValues(rules, items, values, lang) {
1342
- const errors = {};
1343
- for (const item of items) {
1344
- const value = values[item.name];
1345
- const rulesOptions = item.rules;
1346
- if (rulesOptions) for (const [name, options] of Object.entries(rulesOptions)) try {
1347
- await rules[name](value, options, lang);
1348
- } catch (error) {
1349
- errors[item.name] = error;
1350
- break;
1351
- }
1352
- }
1353
- return errors;
1354
- }
1355
- //#endregion
1356
- //#region src/Form/Footer.tsx
1357
- function FormFooter() {
1358
- const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
1359
- const Button = Elements.Button;
1360
- return /* @__PURE__ */ jsx(Button, {
1361
- submitting,
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
- ]),
1381
- children: lang.submit
1382
- });
1383
- }
1384
- FormFooter.displayName = "FormFooter";
1385
- //#endregion
1386
- //#region src/Form/lang.ts
1387
- const FormDefaultLang = {
1388
- submit: "Submit",
1389
- required: "This field is required",
1390
- string: "This field must be a string",
1391
- number: "This field must be a number"
1392
- };
1393
- //#endregion
1394
- //#region src/Form/Container.tsx
1395
- function mergeValues(items, defaultValues = {}) {
1396
- const values = {};
1397
- for (const item of items) values[item.name] = defaultValues[item.name] ?? "";
1398
- return values;
1399
- }
1218
+ //#region src/useFaasStream.tsx
1400
1219
  /**
1401
- * FormContainer component is a wrapper that provides context and state management for form elements.
1402
- * It initializes form states such as values, errors, submitting status, elements, language, and rules.
1403
- *
1404
- * @template Values - The type of form values, defaults to Record<string, any>.
1405
- * @template FormElements - The type of form elements, defaults to FormElementTypes.
1406
- * @template Rules - The type of form rules, defaults to FormDefaultRules.
1220
+ * Stream a FaasJS response into React state.
1407
1221
  *
1408
- * @param {FormProps<Values, FormElements, Rules>} props - The properties for the FormContainer component.
1409
- * @param {Values} props.defaultValues - The default values for the form fields.
1410
- * @param {FormElements} props.Elements - The form elements to be used in the form.
1411
- * @param {Rules} props.rules - The validation rules for the form fields.
1412
- * @param {FormLang} props.lang - The language settings for the form.
1413
- * @param {Partial<FormContextProps>} props - Additional properties for the form context.
1222
+ * The hook sends a streaming request, appends decoded text chunks to `data`,
1223
+ * and exposes reload helpers for retrying the same action.
1414
1224
  *
1415
- * @returns {JSX.Element} The FormContainer component.
1225
+ * @param action - Action path to invoke.
1226
+ * @param defaultParams - Params used for the initial request and future reloads.
1227
+ * @param options - Optional hook configuration such as controlled data, debounce, and skip logic.
1228
+ * @returns Streaming request state and helper methods.
1416
1229
  *
1417
1230
  * @example
1418
1231
  * ```tsx
1419
- * import { Form } from '@faasjs/react'
1420
- *
1421
- * function MyForm() {
1422
- * return <Form
1423
- * items={[
1424
- * { name: 'name' },
1425
- * ]}
1426
- * />
1427
- * }
1428
- * ```
1429
- */
1430
- function FormContainer({ defaultValues, Elements, rules, lang, items, ...props }) {
1431
- const [values, setValues, valuesRef] = useStateRef(mergeValues(items, defaultValues));
1432
- return /* @__PURE__ */ jsxs(FormContextProvider, {
1433
- initializeStates: {
1434
- errors: {},
1435
- submitting: false
1436
- },
1437
- value: {
1438
- Elements: Object.assign(FormDefaultElements, Elements),
1439
- lang: Object.assign(FormDefaultLang, lang),
1440
- rules: Object.assign(FormDefaultRules, rules),
1441
- items,
1442
- values,
1443
- setValues,
1444
- valuesRef,
1445
- ...props
1446
- },
1447
- memo: true,
1448
- children: [/* @__PURE__ */ jsx(FormBody, {}), /* @__PURE__ */ jsx(FormFooter, {})]
1449
- });
1450
- }
1451
- FormContainer.displayName = "FormContainer";
1452
- //#endregion
1453
- //#region src/OptionalWrapper.tsx
1454
- /**
1455
- * A wrapper component that conditionally wraps its children with a provided wrapper component.
1456
- *
1457
- * @example
1458
- * ```tsx
1459
- * import { OptionalWrapper } from '@faasjs/react'
1460
- *
1461
- * const Wrapper = ({ children }: { children: React.ReactNode }) => (
1462
- * <div className='wrapper'>{children}</div>
1463
- * )
1464
- *
1465
- * const App = () => (
1466
- * <OptionalWrapper condition={true} Wrapper={Wrapper}>
1467
- * <span>Test</span>
1468
- * </OptionalWrapper>
1469
- * )
1470
- * ```
1471
- */
1472
- function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
1473
- if (condition) return /* @__PURE__ */ jsx(Wrapper, {
1474
- ...wrapperProps,
1475
- children
1476
- });
1477
- return children;
1478
- }
1479
- OptionalWrapper.displayName = "OptionalWrapper";
1480
- //#endregion
1481
- //#region src/useFaasStream.tsx
1482
- /**
1483
- * Stream faas server response with React hook
1232
+ * import { useState } from 'react'
1233
+ * import { useFaasStream } from '@faasjs/react'
1484
1234
  *
1485
- * @param action {string} action name
1486
- * @param defaultParams {object} initial action params
1487
- * @returns {UseFaasStreamResult}
1488
- *
1489
- * @example
1490
- * ```tsx
1491
1235
  * function Chat() {
1492
1236
  * const [prompt, setPrompt] = useState('')
1493
1237
  * const { data, loading, reload } = useFaasStream('chat', { prompt })
@@ -1628,8 +1372,8 @@ function useFaasStream(action, defaultParams, options = {}) {
1628
1372
  * Hook to store the previous value of a state or prop.
1629
1373
  *
1630
1374
  * @template T - The type of the value.
1631
- * @param {T} value - The current value to be stored.
1632
- * @returns {T | undefined} - The previous value, or undefined if there is no previous value.
1375
+ * @param value - The current value to track.
1376
+ * @returns Previous value from the prior render, or `undefined` on the first render.
1633
1377
  */
1634
1378
  function usePrevious(value) {
1635
1379
  const ref = useRef(void 0);
@@ -1639,4 +1383,42 @@ function usePrevious(value) {
1639
1383
  return ref.current;
1640
1384
  }
1641
1385
  //#endregion
1642
- export { ErrorBoundary, FaasBrowserClient, FaasDataWrapper, FaasReactClient, FormContainer as Form, FormContextProvider, FormDefaultElements, FormDefaultLang, FormDefaultRules, FormInput, FormItem, OptionalWrapper, Response, ResponseError, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, useFormContext, usePrevious, useSplittingState, useStateRef, validValues, withFaasData };
1386
+ //#region src/useStateRef.ts
1387
+ /**
1388
+ * Custom hook that returns a stateful value and a ref to that value.
1389
+ *
1390
+ * @template T - The type of the value.
1391
+ * @param initialValue - Initial state value. When omitted, state starts as `null`.
1392
+ * @returns Tuple containing the current state, the state setter, and a ref that always points at the latest state.
1393
+ *
1394
+ * @example
1395
+ * ```tsx
1396
+ * import { useStateRef } from '@faasjs/react'
1397
+ *
1398
+ * function MyComponent() {
1399
+ * const [value, setValue, ref] = useStateRef(0)
1400
+ *
1401
+ * return (
1402
+ * <div>
1403
+ * <p>Value: {value}</p>
1404
+ * <button onClick={() => setValue(value + 1)}>Increment</button>
1405
+ * <button onClick={() => console.log(ref.current)}>Submit</button>
1406
+ * </div>
1407
+ * )
1408
+ * }
1409
+ * ```
1410
+ */
1411
+ function useStateRef(initialValue) {
1412
+ const [state, setState] = useState(initialValue ?? null);
1413
+ const ref = useRef(state);
1414
+ useEffect(() => {
1415
+ ref.current = state;
1416
+ }, [state]);
1417
+ return [
1418
+ state,
1419
+ setState,
1420
+ ref
1421
+ ];
1422
+ }
1423
+ //#endregion
1424
+ export { ErrorBoundary, FaasBrowserClient, FaasDataWrapper, FaasReactClient, OptionalWrapper, Response, ResponseError, createSplittingContext, equal, faas, generateId, getClient, setMock, useConstant, useEqualCallback, useEqualEffect, useEqualMemo, useEqualMemoize, useFaas, useFaasStream, usePrevious, useSplittingState, useStateRef, withFaasData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@faasjs/react",
3
- "version": "8.0.0-beta.15",
3
+ "version": "8.0.0-beta.17",
4
4
  "homepage": "https://faasjs.com/doc/react/",
5
5
  "bugs": {
6
6
  "url": "https://github.com/faasjs/faasjs/issues"
@@ -27,12 +27,12 @@
27
27
  }
28
28
  },
29
29
  "devDependencies": {
30
- "@faasjs/types": ">=8.0.0-beta.15",
30
+ "@faasjs/types": ">=8.0.0-beta.17",
31
31
  "@types/react": "^19.0.0",
32
32
  "react": "^19.0.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@faasjs/types": ">=8.0.0-beta.15"
35
+ "@faasjs/types": ">=8.0.0-beta.17"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=24.0.0",