@faasjs/react 8.0.0-beta.6 → 8.0.0-beta.7
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.cjs +938 -674
- package/dist/index.d.ts +356 -258
- package/dist/index.mjs +937 -672
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,745 +1,1009 @@
|
|
|
1
|
-
'
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
let _faasjs_browser = require("@faasjs/browser");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
//#region src/equal.ts
|
|
7
|
+
const AsyncFunction = (async () => {}).constructor;
|
|
8
|
+
/**
|
|
9
|
+
* Compares two values for deep equality.
|
|
10
|
+
*
|
|
11
|
+
* This function checks if two values are deeply equal by comparing their types and contents.
|
|
12
|
+
* It handles various data types including primitives, arrays, dates, regular expressions, functions,
|
|
13
|
+
* maps, sets, and promises.
|
|
14
|
+
*
|
|
15
|
+
* @param a - The first value to compare.
|
|
16
|
+
* @param b - The second value to compare.
|
|
17
|
+
* @returns `true` if the values are deeply equal, `false` otherwise.
|
|
18
|
+
*/
|
|
10
19
|
function equal(a, b) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return a.toString() === b.toString();
|
|
38
|
-
case Map:
|
|
39
|
-
case Set:
|
|
40
|
-
return equal(Array.from(a), Array.from(b));
|
|
41
|
-
case Promise:
|
|
42
|
-
return a === b;
|
|
43
|
-
case Object: {
|
|
44
|
-
for (const key of /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]))
|
|
45
|
-
if (!equal(a[key], b[key])) return false;
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
default:
|
|
49
|
-
throw Error(`Unsupported type: ${ctor}`);
|
|
50
|
-
}
|
|
20
|
+
if (a === b) return true;
|
|
21
|
+
if ((a === null || a === void 0) && (b === null || b === void 0)) return true;
|
|
22
|
+
if (typeof a !== typeof b) return false;
|
|
23
|
+
if (a === null || a === void 0 || b === null || b === void 0) return false;
|
|
24
|
+
const ctor = a.constructor;
|
|
25
|
+
if (ctor !== b.constructor) return false;
|
|
26
|
+
switch (ctor) {
|
|
27
|
+
case String:
|
|
28
|
+
case Boolean: return a === b;
|
|
29
|
+
case Number: return Number.isNaN(a) && Number.isNaN(b) || a === b;
|
|
30
|
+
case Array:
|
|
31
|
+
if (a.length !== b.length) return false;
|
|
32
|
+
for (let i = 0; i < a.length; i++) if (!equal(a[i], b[i])) return false;
|
|
33
|
+
return true;
|
|
34
|
+
case Date: return a.getTime() === b.getTime();
|
|
35
|
+
case RegExp:
|
|
36
|
+
case Function:
|
|
37
|
+
case AsyncFunction: return a.toString() === b.toString();
|
|
38
|
+
case Map:
|
|
39
|
+
case Set: return equal(Array.from(a), Array.from(b));
|
|
40
|
+
case Promise: return a === b;
|
|
41
|
+
case Object:
|
|
42
|
+
for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) if (!equal(a[key], b[key])) return false;
|
|
43
|
+
return true;
|
|
44
|
+
default: throw Error(`Unsupported type: ${ctor}`);
|
|
45
|
+
}
|
|
51
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Custom hook that memoizes a value using deep equality comparison.
|
|
49
|
+
*
|
|
50
|
+
* @param value - The value to be memoized.
|
|
51
|
+
* @returns The memoized value.
|
|
52
|
+
*/
|
|
52
53
|
function useEqualMemoize(value) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
const ref = (0, react.useRef)(value);
|
|
55
|
+
const signalRef = (0, react.useRef)(0);
|
|
56
|
+
if (!equal(value, ref.current)) {
|
|
57
|
+
ref.current = value;
|
|
58
|
+
signalRef.current += 1;
|
|
59
|
+
}
|
|
60
|
+
return (0, react.useMemo)(() => ref.current, [signalRef.current]);
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Custom hook that works like `useEffect` but uses deep comparison on dependencies.
|
|
64
|
+
*
|
|
65
|
+
* @param callback - The effect callback function to run.
|
|
66
|
+
* @param dependencies - The list of dependencies for the effect.
|
|
67
|
+
* @returns The result of the `useEffect` hook with memoized dependencies.
|
|
68
|
+
*/
|
|
61
69
|
function useEqualEffect(callback, dependencies) {
|
|
62
|
-
|
|
70
|
+
return (0, react.useEffect)(callback, useEqualMemoize(dependencies));
|
|
63
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Custom hook that works like `useMemo` but uses deep comparison on dependencies.
|
|
74
|
+
*
|
|
75
|
+
* @param callback - The callback function to run.
|
|
76
|
+
* @param dependencies - The list of dependencies.
|
|
77
|
+
* @returns The result of the `useMemo` hook with memoized dependencies.
|
|
78
|
+
*/
|
|
64
79
|
function useEqualMemo(callback, dependencies) {
|
|
65
|
-
|
|
80
|
+
return (0, react.useMemo)(callback, useEqualMemoize(dependencies));
|
|
66
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Custom hook that works like `useCallback` but uses deep comparison on dependencies.
|
|
84
|
+
*
|
|
85
|
+
* @param callback - The callback function to run.
|
|
86
|
+
* @param dependencies - The list of dependencies.
|
|
87
|
+
* @returns The result of the `useCallback` hook with memoized dependencies.
|
|
88
|
+
*/
|
|
67
89
|
function useEqualCallback(callback, dependencies) {
|
|
68
|
-
|
|
69
|
-
(...args) => callback(...args),
|
|
70
|
-
useEqualMemoize(dependencies)
|
|
71
|
-
);
|
|
90
|
+
return (0, react.useCallback)((...args) => callback(...args), useEqualMemoize(dependencies));
|
|
72
91
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
]);
|
|
106
|
-
return child;
|
|
107
|
-
}
|
|
108
|
-
);
|
|
109
|
-
Object.assign(FaasDataWrapper, {
|
|
110
|
-
displayName: "FaasDataWrapper"
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/FaasDataWrapper.tsx
|
|
95
|
+
const fixedForwardRef = react.forwardRef;
|
|
96
|
+
const FaasDataWrapper = fixedForwardRef((props, ref) => {
|
|
97
|
+
const requestOptions = {
|
|
98
|
+
...props.data !== void 0 ? { data: props.data } : {},
|
|
99
|
+
...props.setData ? { setData: props.setData } : {}
|
|
100
|
+
};
|
|
101
|
+
const request = getClient(props.baseUrl).useFaas(props.action, props.params ?? {}, requestOptions);
|
|
102
|
+
const [loaded, setLoaded] = (0, react.useState)(false);
|
|
103
|
+
(0, react.useImperativeHandle)(ref, () => request, [request]);
|
|
104
|
+
useEqualEffect(() => {
|
|
105
|
+
if (!request.loading) setLoaded((prev) => prev === false ? true : prev);
|
|
106
|
+
}, [request.loading]);
|
|
107
|
+
useEqualEffect(() => {
|
|
108
|
+
if (props.onDataChange) props.onDataChange(request);
|
|
109
|
+
}, [request.data]);
|
|
110
|
+
return useEqualMemo(() => {
|
|
111
|
+
if (loaded) {
|
|
112
|
+
if (props.children) return (0, react.cloneElement)(props.children, request);
|
|
113
|
+
if (props.render) return props.render(request);
|
|
114
|
+
}
|
|
115
|
+
return props.fallback || null;
|
|
116
|
+
}, [
|
|
117
|
+
loaded,
|
|
118
|
+
request.action,
|
|
119
|
+
request.params,
|
|
120
|
+
request.data,
|
|
121
|
+
request.error,
|
|
122
|
+
request.loading
|
|
123
|
+
]);
|
|
111
124
|
});
|
|
112
|
-
|
|
113
|
-
|
|
125
|
+
Object.assign(FaasDataWrapper, { displayName: "FaasDataWrapper" });
|
|
126
|
+
/**
|
|
127
|
+
* HOC to wrap a component with FaasDataWrapper
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```tsx
|
|
131
|
+
* const MyComponent = withFaasData(({ data }) => <div>{data.name}</div>, { action: 'test', params: { a: 1 } })
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
function withFaasData(Component, faasProps) {
|
|
135
|
+
return (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FaasDataWrapper, {
|
|
136
|
+
...faasProps,
|
|
137
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...props })
|
|
138
|
+
});
|
|
114
139
|
}
|
|
115
140
|
|
|
116
|
-
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/faas.ts
|
|
143
|
+
/**
|
|
144
|
+
* Request faas server
|
|
145
|
+
*
|
|
146
|
+
* @param action {string} action name
|
|
147
|
+
* @param params {object} action params
|
|
148
|
+
* @returns {Promise<Response<any>>}
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* faas<{ title: string }>('post/get', { id: 1 }).then(res => {
|
|
153
|
+
* console.log(res.data.title)
|
|
154
|
+
* })
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
117
157
|
async function faas(action, params, options) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
const client = getClient(options?.baseUrl);
|
|
159
|
+
const onError = client.onError;
|
|
160
|
+
if (onError) return client.browserClient.action(action, params, options).catch(async (res) => {
|
|
161
|
+
await onError(action, params)(res);
|
|
162
|
+
return Promise.reject(res);
|
|
163
|
+
});
|
|
164
|
+
return client.browserClient.action(action, params, options);
|
|
125
165
|
}
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
//#region src/useFaas.tsx
|
|
169
|
+
/**
|
|
170
|
+
* Request faas server with React hook
|
|
171
|
+
*
|
|
172
|
+
* @param action {string} action name
|
|
173
|
+
* @param defaultParams {object} initial action params
|
|
174
|
+
* @returns {FaasDataInjection<any>}
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```tsx
|
|
178
|
+
* function Post ({ id }) {
|
|
179
|
+
* const { data } = useFaas<{ title: string }>('post/get', { id })
|
|
180
|
+
* return <h1>{data.title}</h1>
|
|
181
|
+
* }
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
126
184
|
function useFaas(action, defaultParams, options = {}) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
setError
|
|
236
|
-
};
|
|
185
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
186
|
+
const [data, setData] = (0, react.useState)();
|
|
187
|
+
const [error, setError] = (0, react.useState)();
|
|
188
|
+
const [params, setParams] = (0, react.useState)(defaultParams);
|
|
189
|
+
const [reloadTimes, setReloadTimes] = (0, react.useState)(0);
|
|
190
|
+
const [fails, setFails] = (0, react.useState)(0);
|
|
191
|
+
const [skip, setSkip] = (0, react.useState)(typeof options.skip === "function" ? options.skip(defaultParams) : options.skip);
|
|
192
|
+
const promiseRef = (0, react.useRef)(null);
|
|
193
|
+
const controllerRef = (0, react.useRef)(null);
|
|
194
|
+
const localSetData = setData;
|
|
195
|
+
const pendingReloadsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
196
|
+
const reloadCounterRef = (0, react.useRef)(0);
|
|
197
|
+
useEqualEffect(() => {
|
|
198
|
+
setSkip(typeof options.skip === "function" ? options.skip(params) : options.skip);
|
|
199
|
+
}, [typeof options.skip === "function" ? params : options.skip]);
|
|
200
|
+
useEqualEffect(() => {
|
|
201
|
+
if (!equal(defaultParams, params)) setParams(defaultParams);
|
|
202
|
+
}, [defaultParams]);
|
|
203
|
+
useEqualEffect(() => {
|
|
204
|
+
if (!action || skip) {
|
|
205
|
+
setLoading(false);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
setLoading(true);
|
|
209
|
+
const controller = new AbortController();
|
|
210
|
+
controllerRef.current = controller;
|
|
211
|
+
const client = getClient(options.baseUrl);
|
|
212
|
+
const requestParams = options.params ?? params;
|
|
213
|
+
function send() {
|
|
214
|
+
const request = client.faas(action, requestParams, { signal: controller.signal });
|
|
215
|
+
promiseRef.current = request;
|
|
216
|
+
request.then((r) => {
|
|
217
|
+
const nextData = r.data;
|
|
218
|
+
setFails(0);
|
|
219
|
+
setError(null);
|
|
220
|
+
options.setData ? options.setData(nextData) : localSetData(nextData);
|
|
221
|
+
setLoading(false);
|
|
222
|
+
for (const { resolve } of pendingReloadsRef.current.values()) resolve(nextData);
|
|
223
|
+
pendingReloadsRef.current.clear();
|
|
224
|
+
}).catch(async (e) => {
|
|
225
|
+
if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0) return;
|
|
226
|
+
if (!fails && typeof e?.message === "string" && e.message.indexOf("Failed to fetch") >= 0) {
|
|
227
|
+
console.warn(`FaasReactClient: ${e.message} retry...`);
|
|
228
|
+
setFails(1);
|
|
229
|
+
return send();
|
|
230
|
+
}
|
|
231
|
+
let error = e;
|
|
232
|
+
if (client.onError) try {
|
|
233
|
+
await client.onError(action, requestParams)(e);
|
|
234
|
+
} catch (newError) {
|
|
235
|
+
error = newError;
|
|
236
|
+
}
|
|
237
|
+
setError(error);
|
|
238
|
+
setLoading(false);
|
|
239
|
+
for (const { reject } of pendingReloadsRef.current.values()) reject(error);
|
|
240
|
+
pendingReloadsRef.current.clear();
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (options.debounce) {
|
|
244
|
+
const timeout = setTimeout(send, options.debounce);
|
|
245
|
+
return () => {
|
|
246
|
+
clearTimeout(timeout);
|
|
247
|
+
controllerRef.current?.abort();
|
|
248
|
+
setLoading(false);
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
send();
|
|
252
|
+
return () => {
|
|
253
|
+
controllerRef.current?.abort();
|
|
254
|
+
setLoading(false);
|
|
255
|
+
};
|
|
256
|
+
}, [
|
|
257
|
+
action,
|
|
258
|
+
options.params || params,
|
|
259
|
+
reloadTimes,
|
|
260
|
+
skip
|
|
261
|
+
]);
|
|
262
|
+
const reload = useEqualCallback((params) => {
|
|
263
|
+
if (skip) setSkip(false);
|
|
264
|
+
if (params) setParams(params);
|
|
265
|
+
const reloadCounter = ++reloadCounterRef.current;
|
|
266
|
+
setReloadTimes((prev) => prev + 1);
|
|
267
|
+
return new Promise((resolve, reject) => {
|
|
268
|
+
pendingReloadsRef.current.set(reloadCounter, {
|
|
269
|
+
resolve,
|
|
270
|
+
reject
|
|
271
|
+
});
|
|
272
|
+
setReloadTimes((prev) => prev + 1);
|
|
273
|
+
});
|
|
274
|
+
}, [params, skip]);
|
|
275
|
+
const currentData = options.data ?? data;
|
|
276
|
+
const currentPromise = promiseRef.current ?? Promise.resolve({});
|
|
277
|
+
return {
|
|
278
|
+
action,
|
|
279
|
+
params,
|
|
280
|
+
loading,
|
|
281
|
+
data: currentData,
|
|
282
|
+
reloadTimes,
|
|
283
|
+
error,
|
|
284
|
+
promise: currentPromise,
|
|
285
|
+
reload,
|
|
286
|
+
setData: options.setData ?? localSetData,
|
|
287
|
+
setLoading,
|
|
288
|
+
setPromise: (newPromise) => {
|
|
289
|
+
promiseRef.current = typeof newPromise === "function" ? newPromise(currentPromise) : newPromise;
|
|
290
|
+
},
|
|
291
|
+
setError
|
|
292
|
+
};
|
|
237
293
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
294
|
+
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/client.tsx
|
|
297
|
+
const clients = {};
|
|
298
|
+
/**
|
|
299
|
+
* Before use faas, you should initialize a FaasReactClient.
|
|
300
|
+
*
|
|
301
|
+
* @returns FaasReactClient instance.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* const client = FaasReactClient({
|
|
306
|
+
* baseUrl: 'localhost:8080/api/'
|
|
307
|
+
* })
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
function FaasReactClient({ baseUrl, options: clientOptions, onError } = { baseUrl: "/" }) {
|
|
311
|
+
const resolvedBaseUrl = baseUrl ?? "/";
|
|
312
|
+
const client = new _faasjs_browser.FaasBrowserClient(resolvedBaseUrl, clientOptions);
|
|
313
|
+
function withBaseUrl(options) {
|
|
314
|
+
if (options?.baseUrl) return options;
|
|
315
|
+
return {
|
|
316
|
+
...options ?? {},
|
|
317
|
+
baseUrl: resolvedBaseUrl
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
const reactClient = {
|
|
321
|
+
id: client.id,
|
|
322
|
+
faas: async (action, params, requestOptions) => faas(action, params, withBaseUrl(requestOptions)),
|
|
323
|
+
useFaas: (action, defaultParams, requestOptions) => useFaas(action, defaultParams, withBaseUrl(requestOptions)),
|
|
324
|
+
FaasDataWrapper: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FaasDataWrapper, {
|
|
325
|
+
...props,
|
|
326
|
+
baseUrl: resolvedBaseUrl
|
|
327
|
+
}),
|
|
328
|
+
...onError ? { onError } : {},
|
|
329
|
+
browserClient: client
|
|
330
|
+
};
|
|
331
|
+
clients[resolvedBaseUrl] = reactClient;
|
|
332
|
+
return reactClient;
|
|
253
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Get FaasReactClient instance
|
|
336
|
+
*
|
|
337
|
+
* @param host {string} empty string for default host
|
|
338
|
+
* @returns {FaasReactClientInstance}
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* ```ts
|
|
342
|
+
* getClient()
|
|
343
|
+
* // or
|
|
344
|
+
* getClient('another-host')
|
|
345
|
+
* ```
|
|
346
|
+
*/
|
|
254
347
|
function getClient(host) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
348
|
+
const client = clients[host || Object.keys(clients)[0]];
|
|
349
|
+
if (!client) {
|
|
350
|
+
console.warn("FaasReactClient is not initialized manually, use default.");
|
|
351
|
+
return FaasReactClient();
|
|
352
|
+
}
|
|
353
|
+
return client;
|
|
261
354
|
}
|
|
355
|
+
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/constant.ts
|
|
358
|
+
/**
|
|
359
|
+
* Returns a constant value that is created by the given function.
|
|
360
|
+
*/
|
|
262
361
|
function useConstant(fn) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
return ref.current.v;
|
|
362
|
+
const ref = (0, react.useRef)(null);
|
|
363
|
+
if (!ref.current) ref.current = { v: fn() };
|
|
364
|
+
return ref.current.v;
|
|
268
365
|
}
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region src/ErrorBoundary.tsx
|
|
269
369
|
var ErrorBoundary = class extends react.Component {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
] });
|
|
301
|
-
}
|
|
302
|
-
return this.props.children;
|
|
303
|
-
}
|
|
370
|
+
static displayName = "ErrorBoundary";
|
|
371
|
+
constructor(props) {
|
|
372
|
+
super(props);
|
|
373
|
+
this.state = {
|
|
374
|
+
error: null,
|
|
375
|
+
info: { componentStack: "" }
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
componentDidCatch(error, info) {
|
|
379
|
+
this.setState({
|
|
380
|
+
error,
|
|
381
|
+
info
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
render() {
|
|
385
|
+
const { error, info } = this.state;
|
|
386
|
+
const errorMessage = String(error ?? "");
|
|
387
|
+
const errorDescription = info.componentStack || void 0;
|
|
388
|
+
if (error) {
|
|
389
|
+
if (this.props.onError) this.props.onError(error, info);
|
|
390
|
+
if (this.props.errorChildren) return (0, react.cloneElement)(this.props.errorChildren, {
|
|
391
|
+
error,
|
|
392
|
+
info,
|
|
393
|
+
errorMessage,
|
|
394
|
+
...errorDescription ? { errorDescription } : {}
|
|
395
|
+
});
|
|
396
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: errorMessage }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: errorDescription })] });
|
|
397
|
+
}
|
|
398
|
+
return this.props.children ?? null;
|
|
399
|
+
}
|
|
304
400
|
};
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/useStateRef.ts
|
|
404
|
+
/**
|
|
405
|
+
* Custom hook that returns a stateful value and a ref to that value.
|
|
406
|
+
*
|
|
407
|
+
* @template T - The type of the value.
|
|
408
|
+
* @param {T} initialValue - The initial value of the state.
|
|
409
|
+
* @returns {[T, (value: T) => void, RefObject<T>]} - The stateful value, a function to set the value, and a ref to the value.
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```tsx
|
|
413
|
+
* import { useStateRef } from '@faasjs/react'
|
|
414
|
+
*
|
|
415
|
+
* function MyComponent() {
|
|
416
|
+
* const [value, setValue, ref] = useStateRef(0)
|
|
417
|
+
*
|
|
418
|
+
* return (
|
|
419
|
+
* <div>
|
|
420
|
+
* <p>Value: {value}</p>
|
|
421
|
+
* <button onClick={() => setValue(value + 1)}>Increment</button>
|
|
422
|
+
* <button onClick={() => console.log(ref.current)}>Submit</button>
|
|
423
|
+
* </div>
|
|
424
|
+
* )
|
|
425
|
+
*/
|
|
305
426
|
function useStateRef(initialValue) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
427
|
+
const [state, setState] = (0, react.useState)(initialValue ?? null);
|
|
428
|
+
const ref = (0, react.useRef)(state);
|
|
429
|
+
(0, react.useEffect)(() => {
|
|
430
|
+
ref.current = state;
|
|
431
|
+
}, [state]);
|
|
432
|
+
return [
|
|
433
|
+
state,
|
|
434
|
+
setState,
|
|
435
|
+
ref
|
|
436
|
+
];
|
|
312
437
|
}
|
|
438
|
+
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/splittingState.tsx
|
|
441
|
+
/**
|
|
442
|
+
* A hook that initializes and splits state variables and their corresponding setters.
|
|
443
|
+
*
|
|
444
|
+
* @template T - A generic type that extends a record with string keys and any values.
|
|
445
|
+
* @param {T} initialStates - An object containing the initial states.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```tsx
|
|
449
|
+
* function Counter() {
|
|
450
|
+
* const { count, setCount, name, setName } = useSplittingState({ count: 0, name: 'John' });
|
|
451
|
+
*
|
|
452
|
+
* return <>{name}: {count}</>
|
|
453
|
+
* }
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
313
456
|
function useSplittingState(initialStates) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
457
|
+
const states = {};
|
|
458
|
+
for (const key of Object.keys(initialStates)) {
|
|
459
|
+
const state = (0, react.useState)(initialStates[key]);
|
|
460
|
+
Object.assign(states, {
|
|
461
|
+
[key]: state[0],
|
|
462
|
+
[`set${String(key).charAt(0).toUpperCase()}${String(key).slice(1)}`]: state[1]
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return states;
|
|
323
466
|
}
|
|
467
|
+
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/splittingContext.tsx
|
|
470
|
+
/**
|
|
471
|
+
* Creates a splitting context with the given default value.
|
|
472
|
+
*
|
|
473
|
+
* @param defaultValue The default value of the splitting context.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```tsx
|
|
477
|
+
* const { Provider, use } = createSplittingContext<{
|
|
478
|
+
* value: number
|
|
479
|
+
* setValue: React.Dispatch<React.SetStateAction<number>>
|
|
480
|
+
* }>({
|
|
481
|
+
* value: 0,
|
|
482
|
+
* setValue: null,
|
|
483
|
+
* })
|
|
484
|
+
*
|
|
485
|
+
* function ReaderComponent() {
|
|
486
|
+
* const { value } = use()
|
|
487
|
+
*
|
|
488
|
+
* return <div>{value}</div>
|
|
489
|
+
* }
|
|
490
|
+
*
|
|
491
|
+
* function WriterComponent() {
|
|
492
|
+
* const { setValue } = use()
|
|
493
|
+
*
|
|
494
|
+
* return (
|
|
495
|
+
* <button type='button' onClick={() => setValue((p: number) => p + 1)}>
|
|
496
|
+
* Change
|
|
497
|
+
* </button>
|
|
498
|
+
* )
|
|
499
|
+
* }
|
|
500
|
+
*
|
|
501
|
+
* function App() {
|
|
502
|
+
* const [value, setValue] = useState(0)
|
|
503
|
+
*
|
|
504
|
+
* return (
|
|
505
|
+
* <Provider value={{ value, setValue }}>
|
|
506
|
+
* <ReaderComponent />
|
|
507
|
+
* <WriterComponent />
|
|
508
|
+
* </Provider>
|
|
509
|
+
* )
|
|
510
|
+
* }
|
|
511
|
+
* ```
|
|
512
|
+
*/
|
|
324
513
|
function createSplittingContext(defaultValue) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
return Object.freeze(obj);
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
Provider,
|
|
367
|
-
use
|
|
368
|
-
};
|
|
514
|
+
const keys = Array.isArray(defaultValue) ? defaultValue : Object.keys(defaultValue);
|
|
515
|
+
const defaultValues = Array.isArray(defaultValue) ? keys.reduce((prev, cur) => {
|
|
516
|
+
prev[cur] = null;
|
|
517
|
+
return prev;
|
|
518
|
+
}, {}) : defaultValue;
|
|
519
|
+
const contexts = {};
|
|
520
|
+
for (const key of keys) contexts[key] = (0, react.createContext)(defaultValues[key]);
|
|
521
|
+
function Provider(props) {
|
|
522
|
+
const states = props.initializeStates ? useSplittingState(props.initializeStates) : {};
|
|
523
|
+
let children = props.memo ? useEqualMemo(() => props.children, props.memo === true ? [] : props.memo) : props.children;
|
|
524
|
+
for (const key of keys) {
|
|
525
|
+
const Context = contexts[key];
|
|
526
|
+
const value = props.value?.[key] ?? states[key] ?? defaultValues[key];
|
|
527
|
+
children = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Context.Provider, {
|
|
528
|
+
value,
|
|
529
|
+
children
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
return children;
|
|
533
|
+
}
|
|
534
|
+
Provider.displayName = "SplittingContextProvider";
|
|
535
|
+
function use() {
|
|
536
|
+
return useConstant(() => {
|
|
537
|
+
const obj = Object.create(null);
|
|
538
|
+
for (const key of Object.keys(contexts)) Object.defineProperty(obj, key, { get: () => {
|
|
539
|
+
if (!contexts[key]) throw new Error(`Context for key "${key}" is undefined`);
|
|
540
|
+
return (0, react.useContext)(contexts[key]);
|
|
541
|
+
} });
|
|
542
|
+
return Object.freeze(obj);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
Provider,
|
|
547
|
+
use
|
|
548
|
+
};
|
|
369
549
|
}
|
|
370
550
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
551
|
+
//#endregion
|
|
552
|
+
//#region src/Form/context.tsx
|
|
553
|
+
const FormContext = createSplittingContext([
|
|
554
|
+
"items",
|
|
555
|
+
"onSubmit",
|
|
556
|
+
"Elements",
|
|
557
|
+
"lang",
|
|
558
|
+
"rules",
|
|
559
|
+
"submitting",
|
|
560
|
+
"setSubmitting",
|
|
561
|
+
"values",
|
|
562
|
+
"setValues",
|
|
563
|
+
"errors",
|
|
564
|
+
"setErrors",
|
|
565
|
+
"valuesRef"
|
|
385
566
|
]);
|
|
386
|
-
|
|
387
|
-
|
|
567
|
+
const FormContextProvider = FormContext.Provider;
|
|
568
|
+
const useFormContext = FormContext.use;
|
|
569
|
+
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/Form/Input.tsx
|
|
388
572
|
function processValue(input, rules) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
default:
|
|
395
|
-
return input;
|
|
396
|
-
}
|
|
573
|
+
switch (rules?.type) {
|
|
574
|
+
case "number": return Number(input);
|
|
575
|
+
case "string": return String(input);
|
|
576
|
+
default: return input;
|
|
577
|
+
}
|
|
397
578
|
}
|
|
398
|
-
function FormInput({
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
420
|
-
Elements.Input,
|
|
421
|
-
{
|
|
422
|
-
name,
|
|
423
|
-
value,
|
|
424
|
-
onChange: (v) => setValues((prev) => ({
|
|
425
|
-
...prev,
|
|
426
|
-
[name]: processValue(v, rules)
|
|
427
|
-
})),
|
|
428
|
-
...rest.props
|
|
429
|
-
}
|
|
430
|
-
);
|
|
579
|
+
function FormInput({ name, rules, ...rest }) {
|
|
580
|
+
const { Elements, values, setValues } = useFormContext();
|
|
581
|
+
const value = values?.[name];
|
|
582
|
+
if (rest.Input) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(rest.Input, {
|
|
583
|
+
name,
|
|
584
|
+
value,
|
|
585
|
+
onChange: (v) => setValues((prev) => ({
|
|
586
|
+
...prev,
|
|
587
|
+
[name]: processValue(v, rules)
|
|
588
|
+
})),
|
|
589
|
+
...rest.props
|
|
590
|
+
});
|
|
591
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Elements.Input, {
|
|
592
|
+
name,
|
|
593
|
+
value,
|
|
594
|
+
onChange: (v) => setValues((prev) => ({
|
|
595
|
+
...prev,
|
|
596
|
+
[name]: processValue(v, rules)
|
|
597
|
+
})),
|
|
598
|
+
...rest.props
|
|
599
|
+
});
|
|
431
600
|
}
|
|
432
601
|
FormInput.displayName = "FormInput";
|
|
602
|
+
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/Form/Item.tsx
|
|
433
605
|
function FormItem(props) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
606
|
+
const { Elements, errors } = useFormContext();
|
|
607
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(props.label?.Label ?? Elements.Label, {
|
|
608
|
+
name: props.name,
|
|
609
|
+
...props.label,
|
|
610
|
+
error: errors[props.name],
|
|
611
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormInput, {
|
|
612
|
+
name: props.name,
|
|
613
|
+
...props.input,
|
|
614
|
+
...props.rules ? { rules: props.rules } : {}
|
|
615
|
+
})
|
|
616
|
+
});
|
|
437
617
|
}
|
|
438
618
|
FormItem.displayName = "FormItem";
|
|
619
|
+
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/Form/Body.tsx
|
|
439
622
|
function FormBody() {
|
|
440
|
-
|
|
441
|
-
|
|
623
|
+
const { items } = useFormContext();
|
|
624
|
+
return items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormItem, { ...item }, item.name));
|
|
442
625
|
}
|
|
443
626
|
FormBody.displayName = "FormBody";
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
));
|
|
627
|
+
|
|
628
|
+
//#endregion
|
|
629
|
+
//#region src/Form/elements/Button.tsx
|
|
630
|
+
const FormButtonElement = (0, react.forwardRef)(({ children, submit, submitting, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
631
|
+
type: "button",
|
|
632
|
+
disabled: submitting,
|
|
633
|
+
onClick: submit,
|
|
634
|
+
...props,
|
|
635
|
+
ref,
|
|
636
|
+
children
|
|
637
|
+
}));
|
|
455
638
|
FormButtonElement.displayName = "FormButtonElement";
|
|
456
|
-
|
|
639
|
+
|
|
640
|
+
//#endregion
|
|
641
|
+
//#region src/Form/elements/Input.tsx
|
|
642
|
+
const FormInputElement = (0, react.forwardRef)(({ onChange, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
643
|
+
...props,
|
|
644
|
+
onChange: (e) => onChange(e.target.value),
|
|
645
|
+
ref
|
|
646
|
+
}));
|
|
457
647
|
FormInputElement.displayName = "FormInputElement";
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
648
|
+
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/Form/elements/Label.tsx
|
|
651
|
+
const FormLabelElement = ({ name, title, description, error, children }) => {
|
|
652
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [
|
|
653
|
+
title ?? name,
|
|
654
|
+
children,
|
|
655
|
+
description,
|
|
656
|
+
error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
657
|
+
style: { color: "red" },
|
|
658
|
+
children: error.message
|
|
659
|
+
})
|
|
660
|
+
] });
|
|
471
661
|
};
|
|
472
662
|
FormLabelElement.displayName = "FormLabelElement";
|
|
473
663
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
664
|
+
//#endregion
|
|
665
|
+
//#region src/Form/elements/index.ts
|
|
666
|
+
const FormDefaultElements = {
|
|
667
|
+
Label: FormLabelElement,
|
|
668
|
+
Input: FormInputElement,
|
|
669
|
+
Button: FormButtonElement
|
|
479
670
|
};
|
|
480
671
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
672
|
+
//#endregion
|
|
673
|
+
//#region src/Form/rules.ts
|
|
674
|
+
/**
|
|
675
|
+
* Default validation rules for a form.
|
|
676
|
+
*/
|
|
677
|
+
const FormDefaultRules = {
|
|
678
|
+
required: async (value, _, lang) => {
|
|
679
|
+
if (value === null || value === void 0 || value === "" || Number.isNaN(value)) throw Error(lang?.required);
|
|
680
|
+
},
|
|
681
|
+
type: async (value, options, lang) => {
|
|
682
|
+
switch (options) {
|
|
683
|
+
case "string":
|
|
684
|
+
if (typeof value !== "string") throw Error(lang?.string);
|
|
685
|
+
break;
|
|
686
|
+
case "number":
|
|
687
|
+
if (Number.isNaN(Number(value))) throw Error(lang?.number);
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
custom: async (value, options) => {
|
|
692
|
+
return options(value);
|
|
693
|
+
}
|
|
501
694
|
};
|
|
502
695
|
async function validValues(rules, items, values, lang) {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
return errors;
|
|
696
|
+
const errors = {};
|
|
697
|
+
for (const item of items) {
|
|
698
|
+
const value = values[item.name];
|
|
699
|
+
const rulesOptions = item.rules;
|
|
700
|
+
if (rulesOptions) for (const [name, options] of Object.entries(rulesOptions)) try {
|
|
701
|
+
await rules[name](value, options, lang);
|
|
702
|
+
} catch (error) {
|
|
703
|
+
errors[item.name] = error;
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return errors;
|
|
519
708
|
}
|
|
709
|
+
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region src/Form/Footer.tsx
|
|
520
712
|
function FormFooter() {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
713
|
+
const { submitting, setSubmitting, onSubmit, valuesRef, Elements, items, setErrors, lang, rules } = useFormContext();
|
|
714
|
+
const handleSubmit = (0, react.useCallback)(async () => {
|
|
715
|
+
setSubmitting(true);
|
|
716
|
+
setErrors({});
|
|
717
|
+
const errors = await validValues(rules, items, valuesRef.current, lang);
|
|
718
|
+
if (Object.keys(errors).length) {
|
|
719
|
+
setErrors(errors);
|
|
720
|
+
setSubmitting(false);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
onSubmit(valuesRef.current).finally(() => setSubmitting(false));
|
|
724
|
+
}, [
|
|
725
|
+
setSubmitting,
|
|
726
|
+
setErrors,
|
|
727
|
+
rules,
|
|
728
|
+
items,
|
|
729
|
+
lang,
|
|
730
|
+
onSubmit
|
|
731
|
+
]);
|
|
732
|
+
return (0, react.useMemo)(() => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Elements.Button, {
|
|
733
|
+
submitting,
|
|
734
|
+
submit: handleSubmit,
|
|
735
|
+
children: lang.submit
|
|
736
|
+
}), [
|
|
737
|
+
submitting,
|
|
738
|
+
handleSubmit,
|
|
739
|
+
lang.submit,
|
|
740
|
+
Elements.Button
|
|
741
|
+
]);
|
|
548
742
|
}
|
|
549
743
|
FormFooter.displayName = "FormFooter";
|
|
550
744
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
745
|
+
//#endregion
|
|
746
|
+
//#region src/Form/lang.ts
|
|
747
|
+
const FormDefaultLang = {
|
|
748
|
+
submit: "Submit",
|
|
749
|
+
required: "This field is required",
|
|
750
|
+
string: "This field must be a string",
|
|
751
|
+
number: "This field must be a number"
|
|
557
752
|
};
|
|
753
|
+
|
|
754
|
+
//#endregion
|
|
755
|
+
//#region src/Form/Container.tsx
|
|
558
756
|
function mergeValues(items, defaultValues = {}) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
return values;
|
|
757
|
+
const values = {};
|
|
758
|
+
for (const item of items) values[item.name] = defaultValues[item.name] ?? "";
|
|
759
|
+
return values;
|
|
563
760
|
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
761
|
+
/**
|
|
762
|
+
* FormContainer component is a wrapper that provides context and state management for form elements.
|
|
763
|
+
* It initializes form states such as values, errors, submitting status, elements, language, and rules.
|
|
764
|
+
*
|
|
765
|
+
* @template Values - The type of form values, defaults to Record<string, any>.
|
|
766
|
+
* @template FormElements - The type of form elements, defaults to FormElementTypes.
|
|
767
|
+
* @template Rules - The type of form rules, defaults to FormDefaultRules.
|
|
768
|
+
*
|
|
769
|
+
* @param {FormProps<Values, FormElements, Rules>} props - The properties for the FormContainer component.
|
|
770
|
+
* @param {Values} props.defaultValues - The default values for the form fields.
|
|
771
|
+
* @param {FormElements} props.Elements - The form elements to be used in the form.
|
|
772
|
+
* @param {Rules} props.rules - The validation rules for the form fields.
|
|
773
|
+
* @param {FormLang} props.lang - The language settings for the form.
|
|
774
|
+
* @param {Partial<FormContextProps>} props - Additional properties for the form context.
|
|
775
|
+
*
|
|
776
|
+
* @returns {JSX.Element} The FormContainer component.
|
|
777
|
+
*
|
|
778
|
+
* @example
|
|
779
|
+
* ```tsx
|
|
780
|
+
* import { Form } from '@faasjs/react'
|
|
781
|
+
*
|
|
782
|
+
* function MyForm() {
|
|
783
|
+
* return <Form
|
|
784
|
+
* items={[
|
|
785
|
+
* { name: 'name' },
|
|
786
|
+
* ]}
|
|
787
|
+
* />
|
|
788
|
+
* }
|
|
789
|
+
* ```
|
|
790
|
+
*/
|
|
791
|
+
function FormContainer({ defaultValues, Elements, rules, lang, items, ...props }) {
|
|
792
|
+
const [values, setValues, valuesRef] = useStateRef(mergeValues(items, defaultValues));
|
|
793
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(FormContextProvider, {
|
|
794
|
+
initializeStates: {
|
|
795
|
+
errors: {},
|
|
796
|
+
submitting: false
|
|
797
|
+
},
|
|
798
|
+
value: {
|
|
799
|
+
Elements: Object.assign(FormDefaultElements, Elements),
|
|
800
|
+
lang: Object.assign(FormDefaultLang, lang),
|
|
801
|
+
rules: Object.assign(FormDefaultRules, rules),
|
|
802
|
+
items,
|
|
803
|
+
values,
|
|
804
|
+
setValues,
|
|
805
|
+
valuesRef,
|
|
806
|
+
...props
|
|
807
|
+
},
|
|
808
|
+
memo: true,
|
|
809
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormBody, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormFooter, {})]
|
|
810
|
+
});
|
|
599
811
|
}
|
|
600
812
|
FormContainer.displayName = "FormContainer";
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
813
|
+
|
|
814
|
+
//#endregion
|
|
815
|
+
//#region src/OptionalWrapper.tsx
|
|
816
|
+
/**
|
|
817
|
+
* A wrapper component that conditionally wraps its children with a provided wrapper component.
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* ```tsx
|
|
821
|
+
* import { OptionalWrapper } from '@faasjs/react'
|
|
822
|
+
*
|
|
823
|
+
* const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
824
|
+
* <div className='wrapper'>{children}</div>
|
|
825
|
+
* )
|
|
826
|
+
*
|
|
827
|
+
* const App = () => (
|
|
828
|
+
* <OptionalWrapper condition={true} Wrapper={Wrapper}>
|
|
829
|
+
* <span>Test</span>
|
|
830
|
+
* </OptionalWrapper>
|
|
831
|
+
* )
|
|
832
|
+
* ```
|
|
833
|
+
*/
|
|
834
|
+
function OptionalWrapper({ condition, Wrapper, wrapperProps, children }) {
|
|
835
|
+
if (condition) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Wrapper, {
|
|
836
|
+
...wrapperProps,
|
|
837
|
+
children
|
|
838
|
+
});
|
|
839
|
+
return children;
|
|
609
840
|
}
|
|
610
841
|
OptionalWrapper.displayName = "OptionalWrapper";
|
|
842
|
+
|
|
843
|
+
//#endregion
|
|
844
|
+
//#region src/useFaasStream.tsx
|
|
845
|
+
/**
|
|
846
|
+
* Stream faas server response with React hook
|
|
847
|
+
*
|
|
848
|
+
* @param action {string} action name
|
|
849
|
+
* @param defaultParams {object} initial action params
|
|
850
|
+
* @returns {UseFaasStreamResult}
|
|
851
|
+
*
|
|
852
|
+
* @example
|
|
853
|
+
* ```tsx
|
|
854
|
+
* function Chat() {
|
|
855
|
+
* const [prompt, setPrompt] = useState('')
|
|
856
|
+
* const { data, loading, reload } = useFaasStream('chat', { prompt })
|
|
857
|
+
*
|
|
858
|
+
* return (
|
|
859
|
+
* <div>
|
|
860
|
+
* <textarea value={prompt} onChange={e => setPrompt(e.target.value)} />
|
|
861
|
+
* <button onClick={reload} disabled={loading}>Send</button>
|
|
862
|
+
* <div>{data}</div>
|
|
863
|
+
* </div>
|
|
864
|
+
* )
|
|
865
|
+
* }
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
611
868
|
function useFaasStream(action, defaultParams, options = {}) {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
setData: options.setData || setData,
|
|
731
|
-
setLoading,
|
|
732
|
-
setError
|
|
733
|
-
};
|
|
869
|
+
const [loading, setLoading] = (0, react.useState)(true);
|
|
870
|
+
const [data, setData] = (0, react.useState)(options.data || "");
|
|
871
|
+
const [error, setError] = (0, react.useState)();
|
|
872
|
+
const [params, setParams] = (0, react.useState)(defaultParams);
|
|
873
|
+
const [reloadTimes, setReloadTimes] = (0, react.useState)(0);
|
|
874
|
+
const [fails, setFails] = (0, react.useState)(0);
|
|
875
|
+
const [skip, setSkip] = (0, react.useState)(typeof options.skip === "function" ? options.skip(defaultParams) : options.skip);
|
|
876
|
+
const controllerRef = (0, react.useRef)(null);
|
|
877
|
+
const pendingReloadsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
878
|
+
const reloadCounterRef = (0, react.useRef)(0);
|
|
879
|
+
useEqualEffect(() => {
|
|
880
|
+
setSkip(typeof options.skip === "function" ? options.skip(params) : options.skip);
|
|
881
|
+
}, [typeof options.skip === "function" ? params : options.skip]);
|
|
882
|
+
useEqualEffect(() => {
|
|
883
|
+
if (!equal(defaultParams, params)) setParams(defaultParams);
|
|
884
|
+
}, [defaultParams]);
|
|
885
|
+
useEqualEffect(() => {
|
|
886
|
+
if (!action || skip) {
|
|
887
|
+
setLoading(false);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
setLoading(true);
|
|
891
|
+
setData("");
|
|
892
|
+
const controller = new AbortController();
|
|
893
|
+
controllerRef.current = controller;
|
|
894
|
+
const client = getClient(options.baseUrl);
|
|
895
|
+
const requestParams = options.params ?? params;
|
|
896
|
+
function send() {
|
|
897
|
+
client.browserClient.action(action, requestParams, {
|
|
898
|
+
signal: controller.signal,
|
|
899
|
+
stream: true
|
|
900
|
+
}).then(async (response) => {
|
|
901
|
+
if (!response.body) {
|
|
902
|
+
setError(/* @__PURE__ */ new Error("Response body is null"));
|
|
903
|
+
setLoading(false);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
const reader = response.body.getReader();
|
|
907
|
+
const decoder = new TextDecoder();
|
|
908
|
+
let accumulatedText = "";
|
|
909
|
+
try {
|
|
910
|
+
while (true) {
|
|
911
|
+
const { done, value } = await reader.read();
|
|
912
|
+
if (done) break;
|
|
913
|
+
accumulatedText += decoder.decode(value, { stream: true });
|
|
914
|
+
setData(accumulatedText);
|
|
915
|
+
}
|
|
916
|
+
setFails(0);
|
|
917
|
+
setError(null);
|
|
918
|
+
setLoading(false);
|
|
919
|
+
for (const { resolve } of pendingReloadsRef.current.values()) resolve(accumulatedText);
|
|
920
|
+
pendingReloadsRef.current.clear();
|
|
921
|
+
} catch (readError) {
|
|
922
|
+
reader.releaseLock();
|
|
923
|
+
throw readError;
|
|
924
|
+
}
|
|
925
|
+
}).catch(async (e) => {
|
|
926
|
+
if (typeof e?.message === "string" && e.message.toLowerCase().indexOf("aborted") >= 0) return;
|
|
927
|
+
if (!fails && typeof e?.message === "string" && e.message.indexOf("Failed to fetch") >= 0) {
|
|
928
|
+
console.warn(`FaasReactClient: ${e.message} retry...`);
|
|
929
|
+
setFails(1);
|
|
930
|
+
return send();
|
|
931
|
+
}
|
|
932
|
+
let error = e;
|
|
933
|
+
if (client.onError) try {
|
|
934
|
+
await client.onError(action, requestParams)(e);
|
|
935
|
+
} catch (newError) {
|
|
936
|
+
error = newError;
|
|
937
|
+
}
|
|
938
|
+
setError(error);
|
|
939
|
+
setLoading(false);
|
|
940
|
+
for (const { reject } of pendingReloadsRef.current.values()) reject(error);
|
|
941
|
+
pendingReloadsRef.current.clear();
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
if (options.debounce) {
|
|
945
|
+
const timeout = setTimeout(send, options.debounce);
|
|
946
|
+
return () => {
|
|
947
|
+
clearTimeout(timeout);
|
|
948
|
+
controllerRef.current?.abort();
|
|
949
|
+
setLoading(false);
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
send();
|
|
953
|
+
return () => {
|
|
954
|
+
controllerRef.current?.abort();
|
|
955
|
+
setLoading(false);
|
|
956
|
+
};
|
|
957
|
+
}, [
|
|
958
|
+
action,
|
|
959
|
+
options.params || params,
|
|
960
|
+
reloadTimes,
|
|
961
|
+
skip
|
|
962
|
+
]);
|
|
963
|
+
const reload = useEqualCallback((params) => {
|
|
964
|
+
if (skip) setSkip(false);
|
|
965
|
+
if (params) setParams(params);
|
|
966
|
+
const reloadCounter = ++reloadCounterRef.current;
|
|
967
|
+
return new Promise((resolve, reject) => {
|
|
968
|
+
pendingReloadsRef.current.set(reloadCounter, {
|
|
969
|
+
resolve,
|
|
970
|
+
reject
|
|
971
|
+
});
|
|
972
|
+
setReloadTimes((prev) => prev + 1);
|
|
973
|
+
});
|
|
974
|
+
}, [params, skip]);
|
|
975
|
+
return {
|
|
976
|
+
action,
|
|
977
|
+
params,
|
|
978
|
+
loading,
|
|
979
|
+
data: options.data || data,
|
|
980
|
+
reloadTimes,
|
|
981
|
+
error,
|
|
982
|
+
reload,
|
|
983
|
+
setData: options.setData || setData,
|
|
984
|
+
setLoading,
|
|
985
|
+
setError
|
|
986
|
+
};
|
|
734
987
|
}
|
|
988
|
+
|
|
989
|
+
//#endregion
|
|
990
|
+
//#region src/usePrevious.ts
|
|
991
|
+
/**
|
|
992
|
+
* Hook to store the previous value of a state or prop.
|
|
993
|
+
*
|
|
994
|
+
* @template T - The type of the value.
|
|
995
|
+
* @param {T} value - The current value to be stored.
|
|
996
|
+
* @returns {T | undefined} - The previous value, or undefined if there is no previous value.
|
|
997
|
+
*/
|
|
735
998
|
function usePrevious(value) {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
999
|
+
const ref = (0, react.useRef)(void 0);
|
|
1000
|
+
(0, react.useEffect)(() => {
|
|
1001
|
+
ref.current = value;
|
|
1002
|
+
});
|
|
1003
|
+
return ref.current;
|
|
741
1004
|
}
|
|
742
1005
|
|
|
1006
|
+
//#endregion
|
|
743
1007
|
exports.ErrorBoundary = ErrorBoundary;
|
|
744
1008
|
exports.FaasDataWrapper = FaasDataWrapper;
|
|
745
1009
|
exports.FaasReactClient = FaasReactClient;
|
|
@@ -767,4 +1031,4 @@ exports.usePrevious = usePrevious;
|
|
|
767
1031
|
exports.useSplittingState = useSplittingState;
|
|
768
1032
|
exports.useStateRef = useStateRef;
|
|
769
1033
|
exports.validValues = validValues;
|
|
770
|
-
exports.withFaasData = withFaasData;
|
|
1034
|
+
exports.withFaasData = withFaasData;
|