@octanejs/inertia 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +33 -0
- package/package.json +64 -0
- package/src/HeadContext.ts +5 -0
- package/src/PageContext.ts +5 -0
- package/src/index.ts +26 -0
- package/src/layoutProps.ts +25 -0
- package/src/server.ts +1 -0
- package/src/useForm.ts +318 -0
- package/src/useFormState.ts +585 -0
- package/src/useHttp.ts +457 -0
- package/src/usePage.ts +15 -0
- package/src/usePoll.ts +49 -0
- package/src/usePrefetch.ts +59 -0
- package/src/useRemember.ts +58 -0
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Errors,
|
|
3
|
+
ErrorValue,
|
|
4
|
+
FormDataErrors,
|
|
5
|
+
FormDataKeys,
|
|
6
|
+
FormDataValues,
|
|
7
|
+
Progress,
|
|
8
|
+
UrlMethodPair,
|
|
9
|
+
UseFormTransformCallback,
|
|
10
|
+
UseFormUtils,
|
|
11
|
+
UseFormWithPrecognitionArguments,
|
|
12
|
+
} from '@inertiajs/core';
|
|
13
|
+
import { cloneDeep, isEqual } from 'es-toolkit';
|
|
14
|
+
import { get, has, set } from 'es-toolkit/compat';
|
|
15
|
+
import {
|
|
16
|
+
createValidator,
|
|
17
|
+
NamedInputEvent,
|
|
18
|
+
resolveName,
|
|
19
|
+
toSimpleValidationErrors,
|
|
20
|
+
ValidationConfig,
|
|
21
|
+
Validator,
|
|
22
|
+
} from 'laravel-precognition';
|
|
23
|
+
import { useCallback, useEffect, useRef, useState } from 'octane';
|
|
24
|
+
import { config } from '.';
|
|
25
|
+
|
|
26
|
+
type StateSetter<Value> = (value: Value | ((previousValue: Value) => Value)) => void;
|
|
27
|
+
type MutableRefObject<Value> = { current: Value };
|
|
28
|
+
const slots = Object.fromEntries(
|
|
29
|
+
[
|
|
30
|
+
'isMounted',
|
|
31
|
+
'precognitionEndpoint',
|
|
32
|
+
'defaults',
|
|
33
|
+
'data',
|
|
34
|
+
'errors',
|
|
35
|
+
'processing',
|
|
36
|
+
'progress',
|
|
37
|
+
'wasSuccessful',
|
|
38
|
+
'recentlySuccessful',
|
|
39
|
+
'recentlySuccessfulTimeout',
|
|
40
|
+
'transform',
|
|
41
|
+
'defaultsCalledInOnSuccess',
|
|
42
|
+
'validator',
|
|
43
|
+
'validating',
|
|
44
|
+
'touchedFields',
|
|
45
|
+
'validFields',
|
|
46
|
+
'withAllErrors',
|
|
47
|
+
'dataRef',
|
|
48
|
+
'syncDataRef',
|
|
49
|
+
'mountedEffect',
|
|
50
|
+
'commitData',
|
|
51
|
+
'setData',
|
|
52
|
+
'setDefaults',
|
|
53
|
+
'reset',
|
|
54
|
+
'setError',
|
|
55
|
+
'clearErrors',
|
|
56
|
+
'resetAndClearErrors',
|
|
57
|
+
'markAsSuccessful',
|
|
58
|
+
'resetBeforeSubmit',
|
|
59
|
+
'finishProcessing',
|
|
60
|
+
'transformFunction',
|
|
61
|
+
'valid',
|
|
62
|
+
'invalid',
|
|
63
|
+
'touched',
|
|
64
|
+
].map((name) => [name, Symbol(`Inertia.useFormState.${name}`)]),
|
|
65
|
+
) as Record<string, symbol>;
|
|
66
|
+
|
|
67
|
+
export type SetDataByObject<TForm> = (data: Partial<TForm>) => void;
|
|
68
|
+
export type SetDataByMethod<TForm> = (data: (previousData: TForm) => TForm) => void;
|
|
69
|
+
export type SetDataByKeyValuePair<TForm> = <K extends FormDataKeys<TForm>>(
|
|
70
|
+
key: K,
|
|
71
|
+
value: FormDataValues<TForm, K>,
|
|
72
|
+
) => void;
|
|
73
|
+
export type SetDataAction<TForm extends Record<any, any>> = SetDataByObject<TForm> &
|
|
74
|
+
SetDataByMethod<TForm> &
|
|
75
|
+
SetDataByKeyValuePair<TForm>;
|
|
76
|
+
|
|
77
|
+
type PrecognitionValidationConfig<TKeys> = ValidationConfig & {
|
|
78
|
+
only?: TKeys[] | Iterable<TKeys> | ArrayLike<TKeys>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export interface FormStateProps<TForm extends object> {
|
|
82
|
+
data: TForm;
|
|
83
|
+
isDirty: boolean;
|
|
84
|
+
errors: FormDataErrors<TForm>;
|
|
85
|
+
hasErrors: boolean;
|
|
86
|
+
processing: boolean;
|
|
87
|
+
progress: Progress | null;
|
|
88
|
+
wasSuccessful: boolean;
|
|
89
|
+
recentlySuccessful: boolean;
|
|
90
|
+
setData: SetDataAction<TForm>;
|
|
91
|
+
transform: (callback: UseFormTransformCallback<TForm>) => void;
|
|
92
|
+
setDefaults: {
|
|
93
|
+
(): void;
|
|
94
|
+
<T extends FormDataKeys<TForm>>(field: T, value: FormDataValues<TForm, T>): void;
|
|
95
|
+
(fields: Partial<TForm>): void;
|
|
96
|
+
};
|
|
97
|
+
reset: <K extends FormDataKeys<TForm>>(...fields: K[]) => void;
|
|
98
|
+
clearErrors: <K extends FormDataKeys<TForm>>(...fields: K[]) => void;
|
|
99
|
+
resetAndClearErrors: <K extends FormDataKeys<TForm>>(...fields: K[]) => void;
|
|
100
|
+
setError: {
|
|
101
|
+
<K extends FormDataKeys<TForm>>(field: K, value: ErrorValue): void;
|
|
102
|
+
(errors: FormDataErrors<TForm>): void;
|
|
103
|
+
};
|
|
104
|
+
withPrecognition: (...args: UseFormWithPrecognitionArguments) => FormStateWithPrecognition<TForm>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface FormStateValidationProps<TForm extends object> {
|
|
108
|
+
invalid: <K extends FormDataKeys<TForm>>(field: K) => boolean;
|
|
109
|
+
setValidationTimeout: (duration: number) => FormStateWithPrecognition<TForm>;
|
|
110
|
+
touch: <K extends FormDataKeys<TForm>>(
|
|
111
|
+
field: K | NamedInputEvent | Array<K>,
|
|
112
|
+
...fields: K[]
|
|
113
|
+
) => FormStateWithPrecognition<TForm>;
|
|
114
|
+
touched: <K extends FormDataKeys<TForm>>(field?: K) => boolean;
|
|
115
|
+
valid: <K extends FormDataKeys<TForm>>(field: K) => boolean;
|
|
116
|
+
validate: <K extends FormDataKeys<TForm>>(
|
|
117
|
+
field?: K | NamedInputEvent | PrecognitionValidationConfig<K>,
|
|
118
|
+
config?: PrecognitionValidationConfig<K>,
|
|
119
|
+
) => FormStateWithPrecognition<TForm>;
|
|
120
|
+
validateFiles: () => FormStateWithPrecognition<TForm>;
|
|
121
|
+
validating: boolean;
|
|
122
|
+
validator: () => Validator;
|
|
123
|
+
withAllErrors: () => FormStateWithPrecognition<TForm>;
|
|
124
|
+
withoutFileValidation: () => FormStateWithPrecognition<TForm>;
|
|
125
|
+
setErrors: (errors: FormDataErrors<TForm>) => FormStateWithPrecognition<TForm>;
|
|
126
|
+
forgetError: <K extends FormDataKeys<TForm> | NamedInputEvent>(
|
|
127
|
+
field: K,
|
|
128
|
+
) => FormStateWithPrecognition<TForm>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type FormState<TForm extends object> = FormStateProps<TForm>;
|
|
132
|
+
export type FormStateWithPrecognition<TForm extends object> = FormStateProps<TForm> &
|
|
133
|
+
FormStateValidationProps<TForm>;
|
|
134
|
+
|
|
135
|
+
export interface UseFormStateOptions<TForm extends object> {
|
|
136
|
+
data: TForm | (() => TForm);
|
|
137
|
+
precognitionEndpoint?: (() => UrlMethodPair) | null;
|
|
138
|
+
useDataState?: () => [TForm, StateSetter<TForm>];
|
|
139
|
+
useErrorsState?: () => [FormDataErrors<TForm>, StateSetter<FormDataErrors<TForm>>];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface UseFormStateReturn<TForm extends object> {
|
|
143
|
+
form: FormState<TForm>;
|
|
144
|
+
setDefaultsState: StateSetter<TForm>;
|
|
145
|
+
transformRef: MutableRefObject<UseFormTransformCallback<TForm>>;
|
|
146
|
+
precognitionEndpointRef: MutableRefObject<(() => UrlMethodPair) | null>;
|
|
147
|
+
dataRef: MutableRefObject<TForm>;
|
|
148
|
+
isMounted: MutableRefObject<boolean>;
|
|
149
|
+
setProcessing: StateSetter<boolean>;
|
|
150
|
+
setProgress: StateSetter<Progress | null>;
|
|
151
|
+
markAsSuccessful: () => void;
|
|
152
|
+
clearErrors: (...fields: string[]) => void;
|
|
153
|
+
setError: (
|
|
154
|
+
fieldOrFields: FormDataKeys<TForm> | FormDataErrors<TForm>,
|
|
155
|
+
maybeValue?: ErrorValue,
|
|
156
|
+
) => void;
|
|
157
|
+
defaultsCalledInOnSuccessRef: MutableRefObject<boolean>;
|
|
158
|
+
resetBeforeSubmit: () => void;
|
|
159
|
+
finishProcessing: () => void;
|
|
160
|
+
withAllErrors: { enabled: () => boolean; enable: () => void };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export default function useFormState<TForm extends object>(
|
|
164
|
+
options: UseFormStateOptions<TForm>,
|
|
165
|
+
): UseFormStateReturn<TForm> {
|
|
166
|
+
const { data: dataOption, useDataState, useErrorsState } = options;
|
|
167
|
+
|
|
168
|
+
const isDataFunction = typeof dataOption === 'function';
|
|
169
|
+
const resolveData = () => (isDataFunction ? (dataOption as () => TForm)() : dataOption);
|
|
170
|
+
|
|
171
|
+
const initialData = cloneDeep(resolveData());
|
|
172
|
+
|
|
173
|
+
const isMounted = useRef(false, slots.isMounted);
|
|
174
|
+
const precognitionEndpointRef = useRef(
|
|
175
|
+
options.precognitionEndpoint ?? null,
|
|
176
|
+
slots.precognitionEndpoint,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const [defaults, setDefaultsState] = useState(cloneDeep(initialData), slots.defaults);
|
|
180
|
+
|
|
181
|
+
const localDataState = useState(cloneDeep(initialData), slots.data);
|
|
182
|
+
const localErrorsState = useState({} as FormDataErrors<TForm>, slots.errors);
|
|
183
|
+
const [data, setData] = useDataState?.() ?? localDataState;
|
|
184
|
+
const [errors, setErrors] = useErrorsState?.() ?? localErrorsState;
|
|
185
|
+
const [processing, setProcessing] = useState(false, slots.processing);
|
|
186
|
+
const [progress, setProgress] = useState<Progress | null>(null, slots.progress);
|
|
187
|
+
const [wasSuccessful, setWasSuccessful] = useState(false, slots.wasSuccessful);
|
|
188
|
+
const [recentlySuccessful, setRecentlySuccessful] = useState(false, slots.recentlySuccessful);
|
|
189
|
+
|
|
190
|
+
const recentlySuccessfulTimeoutId = useRef<number | undefined>(
|
|
191
|
+
undefined,
|
|
192
|
+
slots.recentlySuccessfulTimeout,
|
|
193
|
+
);
|
|
194
|
+
const transformRef = useRef<UseFormTransformCallback<TForm>>((data) => data, slots.transform);
|
|
195
|
+
const defaultsCalledInOnSuccessRef = useRef(false, slots.defaultsCalledInOnSuccess);
|
|
196
|
+
|
|
197
|
+
const validatorRef = useRef<Validator | null>(null, slots.validator);
|
|
198
|
+
const [validating, setValidating] = useState(false, slots.validating);
|
|
199
|
+
const [touchedFields, setTouchedFields] = useState<string[]>([], slots.touchedFields);
|
|
200
|
+
const [validFields, setValidFields] = useState<string[]>([], slots.validFields);
|
|
201
|
+
const withAllErrorsRef = useRef<boolean | null>(null, slots.withAllErrors);
|
|
202
|
+
const withAllErrorsEnabled = () => withAllErrorsRef.current ?? config.get('form.withAllErrors');
|
|
203
|
+
|
|
204
|
+
const dataRef = useRef(data, slots.dataRef);
|
|
205
|
+
|
|
206
|
+
useEffect(
|
|
207
|
+
() => {
|
|
208
|
+
dataRef.current = data;
|
|
209
|
+
},
|
|
210
|
+
undefined,
|
|
211
|
+
slots.syncDataRef,
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
useEffect(
|
|
215
|
+
() => {
|
|
216
|
+
isMounted.current = true;
|
|
217
|
+
return () => {
|
|
218
|
+
isMounted.current = false;
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
[],
|
|
222
|
+
slots.mountedEffect,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const commitData = useCallback(
|
|
226
|
+
(next: TForm) => {
|
|
227
|
+
dataRef.current = next;
|
|
228
|
+
setData(next);
|
|
229
|
+
},
|
|
230
|
+
[setData],
|
|
231
|
+
slots.commitData,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const setDataFunction = useCallback(
|
|
235
|
+
(keyOrData: FormDataKeys<TForm> | Function | Partial<TForm>, maybeValue?: any) => {
|
|
236
|
+
if (typeof keyOrData === 'string') {
|
|
237
|
+
commitData(set(cloneDeep(dataRef.current), keyOrData, maybeValue));
|
|
238
|
+
} else if (typeof keyOrData === 'function') {
|
|
239
|
+
commitData(keyOrData(dataRef.current));
|
|
240
|
+
} else {
|
|
241
|
+
commitData(keyOrData as TForm);
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
[commitData],
|
|
245
|
+
slots.setData,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const setDefaultsFunction = useCallback(
|
|
249
|
+
(fieldOrFields?: FormDataKeys<TForm> | Partial<TForm>, maybeValue?: unknown) => {
|
|
250
|
+
if (isDataFunction) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
'You cannot call `defaults()` when using a function to define your form data.',
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
defaultsCalledInOnSuccessRef.current = true;
|
|
257
|
+
|
|
258
|
+
let newDefaults = {} as TForm;
|
|
259
|
+
|
|
260
|
+
if (typeof fieldOrFields === 'undefined') {
|
|
261
|
+
newDefaults = { ...dataRef.current };
|
|
262
|
+
setDefaultsState(dataRef.current);
|
|
263
|
+
} else {
|
|
264
|
+
setDefaultsState((defaults) => {
|
|
265
|
+
newDefaults =
|
|
266
|
+
typeof fieldOrFields === 'string'
|
|
267
|
+
? set(cloneDeep(defaults), fieldOrFields, maybeValue)
|
|
268
|
+
: Object.assign(cloneDeep(defaults), fieldOrFields);
|
|
269
|
+
|
|
270
|
+
return newDefaults as TForm;
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
validatorRef.current?.defaults(newDefaults as Record<string, unknown>);
|
|
275
|
+
},
|
|
276
|
+
[setDefaultsState],
|
|
277
|
+
slots.setDefaults,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const reset = useCallback(
|
|
281
|
+
(...fields: string[]) => {
|
|
282
|
+
const resolvedData = isDataFunction ? cloneDeep(resolveData()) : defaults;
|
|
283
|
+
const clonedData = cloneDeep(resolvedData);
|
|
284
|
+
|
|
285
|
+
if (fields.length === 0) {
|
|
286
|
+
if (isDataFunction) {
|
|
287
|
+
setDefaultsState(clonedData);
|
|
288
|
+
}
|
|
289
|
+
commitData(clonedData);
|
|
290
|
+
} else {
|
|
291
|
+
if (isDataFunction) {
|
|
292
|
+
setDefaultsState((currentDefaults) => {
|
|
293
|
+
const newDefaults = cloneDeep(currentDefaults);
|
|
294
|
+
(fields as Array<FormDataKeys<TForm>>)
|
|
295
|
+
.filter((key) => has(clonedData, key))
|
|
296
|
+
.forEach((key) => {
|
|
297
|
+
set(newDefaults, key, get(clonedData, key));
|
|
298
|
+
});
|
|
299
|
+
return newDefaults;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const next = (fields as Array<FormDataKeys<TForm>>)
|
|
304
|
+
.filter((key) => has(clonedData, key))
|
|
305
|
+
.reduce(
|
|
306
|
+
(carry, key) => {
|
|
307
|
+
return set(carry, key, get(clonedData, key));
|
|
308
|
+
},
|
|
309
|
+
{ ...dataRef.current } as TForm,
|
|
310
|
+
);
|
|
311
|
+
commitData(next);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
validatorRef.current?.reset(...fields);
|
|
315
|
+
},
|
|
316
|
+
[commitData, defaults],
|
|
317
|
+
slots.reset,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
const setError = useCallback(
|
|
321
|
+
(fieldOrFields: FormDataKeys<TForm> | FormDataErrors<TForm>, maybeValue?: ErrorValue) => {
|
|
322
|
+
setErrors((errors) => {
|
|
323
|
+
const newErrors = {
|
|
324
|
+
...errors,
|
|
325
|
+
...(typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : fieldOrFields),
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
validatorRef.current?.setErrors(newErrors);
|
|
329
|
+
|
|
330
|
+
return newErrors;
|
|
331
|
+
});
|
|
332
|
+
},
|
|
333
|
+
[setErrors],
|
|
334
|
+
slots.setError,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const clearErrors = useCallback(
|
|
338
|
+
(...fields: string[]) => {
|
|
339
|
+
setErrors((errors) => {
|
|
340
|
+
const newErrors = Object.keys(errors).reduce(
|
|
341
|
+
(carry, field) => ({
|
|
342
|
+
...carry,
|
|
343
|
+
...(fields.length > 0 && !fields.includes(field)
|
|
344
|
+
? { [field]: (errors as Errors)[field] }
|
|
345
|
+
: {}),
|
|
346
|
+
}),
|
|
347
|
+
{},
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
if (validatorRef.current) {
|
|
351
|
+
if (fields.length === 0) {
|
|
352
|
+
validatorRef.current.setErrors({});
|
|
353
|
+
} else {
|
|
354
|
+
fields.forEach(validatorRef.current.forgetError);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return newErrors as FormDataErrors<TForm>;
|
|
359
|
+
});
|
|
360
|
+
},
|
|
361
|
+
[setErrors],
|
|
362
|
+
slots.clearErrors,
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const resetAndClearErrors = useCallback(
|
|
366
|
+
(...fields: string[]) => {
|
|
367
|
+
reset(...fields);
|
|
368
|
+
clearErrors(...fields);
|
|
369
|
+
},
|
|
370
|
+
[reset, clearErrors],
|
|
371
|
+
slots.resetAndClearErrors,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const markAsSuccessful = useCallback(
|
|
375
|
+
() => {
|
|
376
|
+
clearErrors();
|
|
377
|
+
setWasSuccessful(true);
|
|
378
|
+
setRecentlySuccessful(true);
|
|
379
|
+
|
|
380
|
+
recentlySuccessfulTimeoutId.current = window.setTimeout(() => {
|
|
381
|
+
if (isMounted.current) {
|
|
382
|
+
setRecentlySuccessful(false);
|
|
383
|
+
}
|
|
384
|
+
}, config.get('form.recentlySuccessfulDuration'));
|
|
385
|
+
},
|
|
386
|
+
[clearErrors, setWasSuccessful, setRecentlySuccessful],
|
|
387
|
+
slots.markAsSuccessful,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const resetBeforeSubmit = useCallback(
|
|
391
|
+
() => {
|
|
392
|
+
setWasSuccessful(false);
|
|
393
|
+
setRecentlySuccessful(false);
|
|
394
|
+
clearTimeout(recentlySuccessfulTimeoutId.current);
|
|
395
|
+
},
|
|
396
|
+
[setWasSuccessful, setRecentlySuccessful],
|
|
397
|
+
slots.resetBeforeSubmit,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const finishProcessing = useCallback(
|
|
401
|
+
() => {
|
|
402
|
+
setProcessing(false);
|
|
403
|
+
setProgress(null);
|
|
404
|
+
},
|
|
405
|
+
[setProcessing, setProgress],
|
|
406
|
+
slots.finishProcessing,
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
const transformFunction = useCallback(
|
|
410
|
+
(callback: UseFormTransformCallback<TForm>) => {
|
|
411
|
+
transformRef.current = callback;
|
|
412
|
+
},
|
|
413
|
+
[],
|
|
414
|
+
slots.transformFunction,
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const tap = <T>(value: T, callback: (value: T) => unknown): T => {
|
|
418
|
+
callback(value);
|
|
419
|
+
return value;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const valid = useCallback(
|
|
423
|
+
<K extends FormDataKeys<TForm>>(field: K) => validFields.includes(field as string),
|
|
424
|
+
[validFields],
|
|
425
|
+
slots.valid,
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
const invalid = useCallback(
|
|
429
|
+
<K extends FormDataKeys<TForm>>(field: K) => field in errors,
|
|
430
|
+
[errors],
|
|
431
|
+
slots.invalid,
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const touched = useCallback(
|
|
435
|
+
<K extends FormDataKeys<TForm>>(field?: K) =>
|
|
436
|
+
typeof field === 'string'
|
|
437
|
+
? touchedFields.includes(field as string)
|
|
438
|
+
: touchedFields.length > 0,
|
|
439
|
+
[touchedFields],
|
|
440
|
+
slots.touched,
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const form = {
|
|
444
|
+
data,
|
|
445
|
+
isDirty: !isEqual(data, defaults),
|
|
446
|
+
errors,
|
|
447
|
+
hasErrors: Object.keys(errors).length > 0,
|
|
448
|
+
processing,
|
|
449
|
+
progress,
|
|
450
|
+
wasSuccessful,
|
|
451
|
+
recentlySuccessful,
|
|
452
|
+
setData: setDataFunction,
|
|
453
|
+
transform: transformFunction,
|
|
454
|
+
setDefaults: setDefaultsFunction,
|
|
455
|
+
reset,
|
|
456
|
+
setError,
|
|
457
|
+
clearErrors,
|
|
458
|
+
resetAndClearErrors,
|
|
459
|
+
} as FormState<TForm>;
|
|
460
|
+
|
|
461
|
+
const validate = (
|
|
462
|
+
field?: string | NamedInputEvent | ValidationConfig,
|
|
463
|
+
config?: ValidationConfig,
|
|
464
|
+
) => {
|
|
465
|
+
if (typeof field === 'object' && !('target' in field)) {
|
|
466
|
+
config = field;
|
|
467
|
+
field = undefined;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (field === undefined) {
|
|
471
|
+
validatorRef.current!.validate(config);
|
|
472
|
+
} else {
|
|
473
|
+
const fieldName = resolveName(field);
|
|
474
|
+
const transformedData = transformRef.current(dataRef.current) as Record<string, unknown>;
|
|
475
|
+
validatorRef.current!.validate(fieldName, get(transformedData, fieldName), config);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return form;
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
const withPrecognition = (
|
|
482
|
+
...args: UseFormWithPrecognitionArguments
|
|
483
|
+
): FormStateWithPrecognition<TForm> => {
|
|
484
|
+
precognitionEndpointRef.current = UseFormUtils.createWayfinderCallback(...args);
|
|
485
|
+
|
|
486
|
+
if (!validatorRef.current) {
|
|
487
|
+
const validator = createValidator(
|
|
488
|
+
(client) => {
|
|
489
|
+
const { method, url } = precognitionEndpointRef.current!();
|
|
490
|
+
const currentData = dataRef.current;
|
|
491
|
+
const transformedData = transformRef.current(currentData) as Record<string, unknown>;
|
|
492
|
+
return client[method](url, transformedData);
|
|
493
|
+
},
|
|
494
|
+
cloneDeep(defaults as Record<string, unknown>),
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
validatorRef.current = validator;
|
|
498
|
+
|
|
499
|
+
validator
|
|
500
|
+
.on('validatingChanged', () => {
|
|
501
|
+
setValidating(validator.validating());
|
|
502
|
+
})
|
|
503
|
+
.on('validatedChanged', () => {
|
|
504
|
+
setValidFields(validator.valid());
|
|
505
|
+
})
|
|
506
|
+
.on('touchedChanged', () => {
|
|
507
|
+
setTouchedFields(validator.touched());
|
|
508
|
+
})
|
|
509
|
+
.on('errorsChanged', () => {
|
|
510
|
+
const validationErrors = withAllErrorsEnabled()
|
|
511
|
+
? validator.errors()
|
|
512
|
+
: toSimpleValidationErrors(validator.errors());
|
|
513
|
+
|
|
514
|
+
setErrors(validationErrors as FormDataErrors<TForm>);
|
|
515
|
+
setValidFields(validator.valid());
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const precognitiveForm = Object.assign(form, {
|
|
520
|
+
validating,
|
|
521
|
+
validator: () => validatorRef.current!,
|
|
522
|
+
valid,
|
|
523
|
+
invalid,
|
|
524
|
+
touched,
|
|
525
|
+
withoutFileValidation: () =>
|
|
526
|
+
tap(precognitiveForm, () => validatorRef.current?.withoutFileValidation()),
|
|
527
|
+
touch: (
|
|
528
|
+
field: FormDataKeys<TForm> | NamedInputEvent | Array<FormDataKeys<TForm>>,
|
|
529
|
+
...fields: FormDataKeys<TForm>[]
|
|
530
|
+
) => {
|
|
531
|
+
if (Array.isArray(field)) {
|
|
532
|
+
validatorRef.current?.touch(field);
|
|
533
|
+
} else if (typeof field === 'string') {
|
|
534
|
+
validatorRef.current?.touch([field, ...fields]);
|
|
535
|
+
} else {
|
|
536
|
+
validatorRef.current?.touch(field);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return precognitiveForm;
|
|
540
|
+
},
|
|
541
|
+
withAllErrors: () => tap(precognitiveForm, () => (withAllErrorsRef.current = true)),
|
|
542
|
+
setValidationTimeout: (duration: number) =>
|
|
543
|
+
tap(precognitiveForm, () => validatorRef.current?.setTimeout(duration)),
|
|
544
|
+
validateFiles: () => tap(precognitiveForm, () => validatorRef.current?.validateFiles()),
|
|
545
|
+
validate,
|
|
546
|
+
setErrors: (errors: FormDataErrors<TForm>) =>
|
|
547
|
+
tap(precognitiveForm, () => form.setError(errors)),
|
|
548
|
+
forgetError: (field: FormDataKeys<TForm> | NamedInputEvent) =>
|
|
549
|
+
tap(precognitiveForm, () =>
|
|
550
|
+
form.clearErrors(resolveName(field as string | NamedInputEvent) as FormDataKeys<TForm>),
|
|
551
|
+
),
|
|
552
|
+
}) as FormStateWithPrecognition<TForm>;
|
|
553
|
+
|
|
554
|
+
return precognitiveForm;
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
form.withPrecognition = withPrecognition;
|
|
558
|
+
|
|
559
|
+
if (precognitionEndpointRef.current) {
|
|
560
|
+
form.withPrecognition(precognitionEndpointRef.current);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
form,
|
|
565
|
+
setDefaultsState,
|
|
566
|
+
transformRef,
|
|
567
|
+
precognitionEndpointRef,
|
|
568
|
+
dataRef,
|
|
569
|
+
isMounted,
|
|
570
|
+
setProcessing,
|
|
571
|
+
setProgress,
|
|
572
|
+
markAsSuccessful,
|
|
573
|
+
clearErrors,
|
|
574
|
+
setError,
|
|
575
|
+
defaultsCalledInOnSuccessRef,
|
|
576
|
+
resetBeforeSubmit,
|
|
577
|
+
finishProcessing,
|
|
578
|
+
withAllErrors: {
|
|
579
|
+
enabled: withAllErrorsEnabled,
|
|
580
|
+
enable: () => {
|
|
581
|
+
withAllErrorsRef.current = true;
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
};
|
|
585
|
+
}
|