remix-validated-form 3.4.2 → 4.0.1-beta.2
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/.turbo/turbo-build.log +2 -2
- package/README.md +1 -1
- package/browser/ValidatedForm.d.ts +2 -2
- package/browser/ValidatedForm.js +108 -57
- package/browser/components.d.ts +10 -0
- package/browser/components.js +10 -0
- package/browser/hooks.d.ts +4 -1
- package/browser/hooks.js +16 -5
- package/browser/internal/formContext.d.ts +2 -3
- package/browser/internal/formContext.js +1 -11
- package/browser/server.d.ts +13 -4
- package/browser/server.js +18 -13
- package/browser/types.d.ts +1 -0
- package/browser/types.js +1 -0
- package/browser/validation/createValidator.d.ts +2 -2
- package/browser/validation/createValidator.js +15 -8
- package/browser/validation/types.d.ts +35 -12
- package/build/ValidatedForm.d.ts +2 -2
- package/build/ValidatedForm.js +126 -56
- package/build/hooks.d.ts +4 -1
- package/build/hooks.js +18 -6
- package/build/internal/formContext.d.ts +2 -3
- package/build/internal/formContext.js +1 -11
- package/build/server.d.ts +13 -4
- package/build/server.js +18 -13
- package/build/types.d.ts +1 -0
- package/build/types.js +2 -0
- package/build/validation/createValidator.d.ts +2 -2
- package/build/validation/createValidator.js +15 -8
- package/build/validation/types.d.ts +35 -12
- package/package.json +1 -1
- package/src/ValidatedForm.tsx +138 -58
- package/src/hooks.ts +21 -5
- package/src/internal/formContext.ts +2 -13
- package/src/server.ts +28 -21
- package/src/validation/createValidator.ts +21 -10
- package/src/validation/types.ts +38 -7
package/src/ValidatedForm.tsx
CHANGED
@@ -3,8 +3,11 @@ import {
|
|
3
3
|
useActionData,
|
4
4
|
useFetcher,
|
5
5
|
useFormAction,
|
6
|
+
useSubmit,
|
6
7
|
useTransition,
|
7
8
|
} from "@remix-run/react";
|
9
|
+
import { Fetcher } from "@remix-run/react/transition";
|
10
|
+
import uniq from "lodash/uniq";
|
8
11
|
import React, {
|
9
12
|
ComponentProps,
|
10
13
|
useEffect,
|
@@ -20,8 +23,8 @@ import { omit, mergeRefs } from "./internal/util";
|
|
20
23
|
import {
|
21
24
|
FieldErrors,
|
22
25
|
Validator,
|
23
|
-
FieldErrorsWithData,
|
24
26
|
TouchedFields,
|
27
|
+
ValidationErrorResponseData,
|
25
28
|
} from "./validation/types";
|
26
29
|
|
27
30
|
export type FormProps<DataType> = {
|
@@ -33,7 +36,10 @@ export type FormProps<DataType> = {
|
|
33
36
|
* A submit callback that gets called when the form is submitted
|
34
37
|
* after all validations have been run.
|
35
38
|
*/
|
36
|
-
onSubmit?: (
|
39
|
+
onSubmit?: (
|
40
|
+
data: DataType,
|
41
|
+
event: React.FormEvent<HTMLFormElement>
|
42
|
+
) => Promise<void>;
|
37
43
|
/**
|
38
44
|
* Allows you to provide a `fetcher` from remix's `useFetcher` hook.
|
39
45
|
* The form will use the fetcher for loading states, action data, etc
|
@@ -68,25 +74,27 @@ export type FormProps<DataType> = {
|
|
68
74
|
disableFocusOnError?: boolean;
|
69
75
|
} & Omit<ComponentProps<typeof RemixForm>, "onSubmit">;
|
70
76
|
|
71
|
-
function
|
77
|
+
function useErrorResponseForThisForm(
|
72
78
|
fetcher?: ReturnType<typeof useFetcher>,
|
73
79
|
subaction?: string
|
74
|
-
):
|
80
|
+
): ValidationErrorResponseData | null {
|
75
81
|
const actionData = useActionData<any>();
|
76
|
-
if (fetcher)
|
77
|
-
|
78
|
-
|
79
|
-
const submittedData = actionData.fieldErrors?._submittedData;
|
80
|
-
const subactionsMatch = subaction
|
81
|
-
? subaction === submittedData?.subaction
|
82
|
-
: !submittedData?.subaction;
|
83
|
-
return subactionsMatch ? actionData.fieldErrors : null;
|
82
|
+
if (fetcher) {
|
83
|
+
if ((fetcher.data as any)?.fieldErrors) return fetcher.data as any;
|
84
|
+
return null;
|
84
85
|
}
|
86
|
+
|
87
|
+
if (!actionData?.fieldErrors) return null;
|
88
|
+
if (
|
89
|
+
(!subaction && !actionData.subaction) ||
|
90
|
+
actionData.subaction === subaction
|
91
|
+
)
|
92
|
+
return actionData;
|
85
93
|
return null;
|
86
94
|
}
|
87
95
|
|
88
96
|
function useFieldErrors(
|
89
|
-
fieldErrorsFromBackend?:
|
97
|
+
fieldErrorsFromBackend?: FieldErrors
|
90
98
|
): [FieldErrors, React.Dispatch<React.SetStateAction<FieldErrors>>] {
|
91
99
|
const [fieldErrors, setFieldErrors] = useState<FieldErrors>(
|
92
100
|
fieldErrorsFromBackend ?? {}
|
@@ -99,22 +107,19 @@ function useFieldErrors(
|
|
99
107
|
}
|
100
108
|
|
101
109
|
const useIsSubmitting = (
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
const
|
107
|
-
|
110
|
+
fetcher?: Fetcher
|
111
|
+
): [boolean, () => void, () => void] => {
|
112
|
+
const [isSubmitStarted, setSubmitStarted] = useState(false);
|
113
|
+
const transition = useTransition();
|
114
|
+
const hasActiveSubmission = fetcher
|
115
|
+
? fetcher.state === "submitting"
|
116
|
+
: !!transition.submission;
|
117
|
+
const isSubmitting = hasActiveSubmission && isSubmitStarted;
|
108
118
|
|
109
|
-
|
110
|
-
|
119
|
+
const startSubmit = () => setSubmitStarted(true);
|
120
|
+
const endSubmit = () => setSubmitStarted(false);
|
111
121
|
|
112
|
-
|
113
|
-
const pendingSubAction = formData.get("subaction");
|
114
|
-
const expectedAction = action ?? actionForCurrentPage;
|
115
|
-
if (subaction)
|
116
|
-
return expectedAction === pendingAction && subaction === pendingSubAction;
|
117
|
-
return expectedAction === pendingAction && !pendingSubAction;
|
122
|
+
return [isSubmitting, startSubmit, endSubmit];
|
118
123
|
};
|
119
124
|
|
120
125
|
const getDataFromForm = (el: HTMLFormElement) => new FormData(el);
|
@@ -136,11 +141,14 @@ const getDataFromForm = (el: HTMLFormElement) => new FormData(el);
|
|
136
141
|
* and only if JS is disabled.
|
137
142
|
*/
|
138
143
|
function useDefaultValues<DataType>(
|
139
|
-
|
144
|
+
repopulateFieldsFromBackend?: any,
|
140
145
|
defaultValues?: Partial<DataType>
|
141
146
|
) {
|
142
|
-
|
143
|
-
|
147
|
+
return repopulateFieldsFromBackend ?? defaultValues;
|
148
|
+
}
|
149
|
+
|
150
|
+
function nonNull<T>(value: T | null | undefined): value is T {
|
151
|
+
return value !== null;
|
144
152
|
}
|
145
153
|
|
146
154
|
const focusFirstInvalidInput = (
|
@@ -148,28 +156,48 @@ const focusFirstInvalidInput = (
|
|
148
156
|
customFocusHandlers: MultiValueMap<string, () => void>,
|
149
157
|
formElement: HTMLFormElement
|
150
158
|
) => {
|
151
|
-
const
|
152
|
-
.map((
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
159
|
+
const namesInOrder = [...formElement.elements]
|
160
|
+
.map((el) => {
|
161
|
+
const input = el instanceof RadioNodeList ? el[0] : el;
|
162
|
+
if (input instanceof HTMLInputElement) return input.name;
|
163
|
+
return null;
|
164
|
+
})
|
165
|
+
.filter(nonNull)
|
166
|
+
.filter((name) => name in fieldErrors);
|
167
|
+
const uniqueNamesInOrder = uniq(namesInOrder);
|
157
168
|
|
158
|
-
|
159
|
-
|
169
|
+
for (const fieldName of uniqueNamesInOrder) {
|
170
|
+
if (customFocusHandlers.has(fieldName)) {
|
171
|
+
customFocusHandlers.getAll(fieldName).forEach((handler) => {
|
160
172
|
handler();
|
161
173
|
});
|
162
174
|
break;
|
163
175
|
}
|
164
176
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
177
|
+
const elem = formElement.elements.namedItem(fieldName);
|
178
|
+
if (!elem) continue;
|
179
|
+
|
180
|
+
if (elem instanceof RadioNodeList) {
|
181
|
+
const selectedRadio =
|
182
|
+
[...elem]
|
183
|
+
.filter(
|
184
|
+
(item): item is HTMLInputElement => item instanceof HTMLInputElement
|
185
|
+
)
|
186
|
+
.find((item) => item.value === elem.value) ?? elem[0];
|
187
|
+
if (selectedRadio && selectedRadio instanceof HTMLInputElement) {
|
188
|
+
selectedRadio.focus();
|
189
|
+
break;
|
190
|
+
}
|
169
191
|
}
|
170
192
|
|
171
|
-
|
172
|
-
|
193
|
+
if (elem instanceof HTMLInputElement) {
|
194
|
+
if (elem.type === "hidden") {
|
195
|
+
continue;
|
196
|
+
}
|
197
|
+
|
198
|
+
elem.focus();
|
199
|
+
break;
|
200
|
+
}
|
173
201
|
}
|
174
202
|
};
|
175
203
|
|
@@ -188,17 +216,27 @@ export function ValidatedForm<DataType>({
|
|
188
216
|
subaction,
|
189
217
|
resetAfterSubmit,
|
190
218
|
disableFocusOnError,
|
219
|
+
method,
|
220
|
+
replace,
|
191
221
|
...rest
|
192
222
|
}: FormProps<DataType>) {
|
193
|
-
const
|
194
|
-
const [fieldErrors, setFieldErrors] = useFieldErrors(
|
195
|
-
|
196
|
-
|
223
|
+
const backendError = useErrorResponseForThisForm(fetcher, subaction);
|
224
|
+
const [fieldErrors, setFieldErrors] = useFieldErrors(
|
225
|
+
backendError?.fieldErrors
|
226
|
+
);
|
227
|
+
const [isSubmitting, startSubmit, endSubmit] = useIsSubmitting(fetcher);
|
228
|
+
|
229
|
+
const defaultsToUse = useDefaultValues(
|
230
|
+
backendError?.repopulateFields,
|
231
|
+
defaultValues
|
232
|
+
);
|
197
233
|
const [touchedFields, setTouchedFields] = useState<TouchedFields>({});
|
198
234
|
const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
|
235
|
+
const submit = useSubmit();
|
199
236
|
const formRef = useRef<HTMLFormElement>(null);
|
200
237
|
useSubmitComplete(isSubmitting, () => {
|
201
|
-
|
238
|
+
endSubmit();
|
239
|
+
if (!backendError && resetAfterSubmit) {
|
202
240
|
formRef.current?.reset();
|
203
241
|
}
|
204
242
|
});
|
@@ -209,7 +247,7 @@ export function ValidatedForm<DataType>({
|
|
209
247
|
fieldErrors,
|
210
248
|
action,
|
211
249
|
defaultValues: defaultsToUse,
|
212
|
-
isSubmitting
|
250
|
+
isSubmitting,
|
213
251
|
isValid: Object.keys(fieldErrors).length === 0,
|
214
252
|
touchedFields,
|
215
253
|
setFieldTouched: (fieldName: string, touched: boolean) =>
|
@@ -220,9 +258,9 @@ export function ValidatedForm<DataType>({
|
|
220
258
|
clearError: (fieldName) => {
|
221
259
|
setFieldErrors((prev) => omit(prev, fieldName));
|
222
260
|
},
|
223
|
-
validateField: (fieldName) => {
|
261
|
+
validateField: async (fieldName) => {
|
224
262
|
invariant(formRef.current, "Cannot find reference to form");
|
225
|
-
const { error } = validator.validateField(
|
263
|
+
const { error } = await validator.validateField(
|
226
264
|
getDataFromForm(formRef.current),
|
227
265
|
fieldName as any
|
228
266
|
);
|
@@ -237,11 +275,13 @@ export function ValidatedForm<DataType>({
|
|
237
275
|
[fieldName]: error,
|
238
276
|
};
|
239
277
|
});
|
278
|
+
return error;
|
240
279
|
} else {
|
241
280
|
setFieldErrors((prev) => {
|
242
281
|
if (!(fieldName in prev)) return prev;
|
243
282
|
return omit(prev, fieldName);
|
244
283
|
});
|
284
|
+
return null;
|
245
285
|
}
|
246
286
|
},
|
247
287
|
registerReceiveFocus: (fieldName, handler) => {
|
@@ -267,26 +307,66 @@ export function ValidatedForm<DataType>({
|
|
267
307
|
|
268
308
|
const Form = fetcher?.Form ?? RemixForm;
|
269
309
|
|
310
|
+
let clickedButtonRef = React.useRef<any>();
|
311
|
+
useEffect(() => {
|
312
|
+
let form = formRef.current;
|
313
|
+
if (!form) return;
|
314
|
+
|
315
|
+
function handleClick(event: MouseEvent) {
|
316
|
+
if (!(event.target instanceof HTMLElement)) return;
|
317
|
+
let submitButton = event.target.closest<
|
318
|
+
HTMLButtonElement | HTMLInputElement
|
319
|
+
>("button,input[type=submit]");
|
320
|
+
|
321
|
+
if (
|
322
|
+
submitButton &&
|
323
|
+
submitButton.form === form &&
|
324
|
+
submitButton.type === "submit"
|
325
|
+
) {
|
326
|
+
clickedButtonRef.current = submitButton;
|
327
|
+
}
|
328
|
+
}
|
329
|
+
|
330
|
+
window.addEventListener("click", handleClick);
|
331
|
+
return () => {
|
332
|
+
window.removeEventListener("click", handleClick);
|
333
|
+
};
|
334
|
+
}, []);
|
335
|
+
|
270
336
|
return (
|
271
337
|
<Form
|
272
338
|
ref={mergeRefs([formRef, formRefProp])}
|
273
339
|
{...rest}
|
274
340
|
action={action}
|
275
|
-
|
341
|
+
method={method}
|
342
|
+
replace={replace}
|
343
|
+
onSubmit={async (e) => {
|
344
|
+
e.preventDefault();
|
276
345
|
setHasBeenSubmitted(true);
|
277
|
-
|
346
|
+
startSubmit();
|
347
|
+
const result = await validator.validate(
|
348
|
+
getDataFromForm(e.currentTarget)
|
349
|
+
);
|
278
350
|
if (result.error) {
|
279
|
-
|
280
|
-
setFieldErrors(result.error);
|
351
|
+
endSubmit();
|
352
|
+
setFieldErrors(result.error.fieldErrors);
|
281
353
|
if (!disableFocusOnError) {
|
282
354
|
focusFirstInvalidInput(
|
283
|
-
result.error,
|
355
|
+
result.error.fieldErrors,
|
284
356
|
customFocusHandlers(),
|
285
357
|
formRef.current!
|
286
358
|
);
|
287
359
|
}
|
288
360
|
} else {
|
289
|
-
onSubmit
|
361
|
+
onSubmit && onSubmit(result.data, e);
|
362
|
+
if (fetcher)
|
363
|
+
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
364
|
+
else
|
365
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
366
|
+
method,
|
367
|
+
replace,
|
368
|
+
});
|
369
|
+
clickedButtonRef.current = null;
|
290
370
|
}
|
291
371
|
}}
|
292
372
|
onReset={(event) => {
|
package/src/hooks.ts
CHANGED
@@ -8,6 +8,15 @@ import {
|
|
8
8
|
ValidationBehaviorOptions,
|
9
9
|
} from "./internal/getInputProps";
|
10
10
|
|
11
|
+
const useInternalFormContext = (hookName: string) => {
|
12
|
+
const context = useContext(FormContext);
|
13
|
+
if (!context)
|
14
|
+
throw new Error(
|
15
|
+
`${hookName} must be used within a ValidatedForm component`
|
16
|
+
);
|
17
|
+
return context;
|
18
|
+
};
|
19
|
+
|
11
20
|
export type FieldProps = {
|
12
21
|
/**
|
13
22
|
* The validation error message if there is one.
|
@@ -66,7 +75,7 @@ export const useField = (
|
|
66
75
|
touchedFields,
|
67
76
|
setFieldTouched,
|
68
77
|
hasBeenSubmitted,
|
69
|
-
} =
|
78
|
+
} = useInternalFormContext("useField");
|
70
79
|
|
71
80
|
const isTouched = !!touchedFields[name];
|
72
81
|
const { handleReceiveFocus } = options ?? {};
|
@@ -82,7 +91,9 @@ export const useField = (
|
|
82
91
|
clearError: () => {
|
83
92
|
clearError(name);
|
84
93
|
},
|
85
|
-
validate: () =>
|
94
|
+
validate: () => {
|
95
|
+
validateField(name);
|
96
|
+
},
|
86
97
|
defaultValue: defaultValues
|
87
98
|
? get(defaultValues, toPath(name), undefined)
|
88
99
|
: undefined,
|
@@ -116,13 +127,18 @@ export const useField = (
|
|
116
127
|
|
117
128
|
/**
|
118
129
|
* Provides access to the entire form context.
|
119
|
-
* This is not usually necessary, but can be useful for advanced use cases.
|
120
130
|
*/
|
121
|
-
export const useFormContext = () =>
|
131
|
+
export const useFormContext = () => useInternalFormContext("useFormContext");
|
122
132
|
|
123
133
|
/**
|
124
134
|
* Returns whether or not the parent form is currently being submitted.
|
125
135
|
* This is different from remix's `useTransition().submission` in that it
|
126
136
|
* is aware of what form it's in and when _that_ form is being submitted.
|
127
137
|
*/
|
128
|
-
export const useIsSubmitting = () =>
|
138
|
+
export const useIsSubmitting = () =>
|
139
|
+
useInternalFormContext("useIsSubmitting").isSubmitting;
|
140
|
+
|
141
|
+
/**
|
142
|
+
* Returns whether or not the current form is valid.
|
143
|
+
*/
|
144
|
+
export const useIsValid = () => useInternalFormContext("useIsValid").isValid;
|
@@ -13,7 +13,7 @@ export type FormContextValue = {
|
|
13
13
|
/**
|
14
14
|
* Validate the specified field.
|
15
15
|
*/
|
16
|
-
validateField: (fieldName: string) =>
|
16
|
+
validateField: (fieldName: string) => Promise<string | null>;
|
17
17
|
/**
|
18
18
|
* The `action` prop of the form.
|
19
19
|
*/
|
@@ -30,7 +30,6 @@ export type FormContextValue = {
|
|
30
30
|
hasBeenSubmitted: boolean;
|
31
31
|
/**
|
32
32
|
* Whether or not the form is valid.
|
33
|
-
* This is a shortcut for `Object.keys(fieldErrors).length === 0`.
|
34
33
|
*/
|
35
34
|
isValid: boolean;
|
36
35
|
/**
|
@@ -52,14 +51,4 @@ export type FormContextValue = {
|
|
52
51
|
setFieldTouched: (fieldName: string, touched: boolean) => void;
|
53
52
|
};
|
54
53
|
|
55
|
-
export const FormContext = createContext<FormContextValue>(
|
56
|
-
fieldErrors: {},
|
57
|
-
clearError: () => {},
|
58
|
-
validateField: () => {},
|
59
|
-
isSubmitting: false,
|
60
|
-
hasBeenSubmitted: false,
|
61
|
-
isValid: true,
|
62
|
-
registerReceiveFocus: () => () => {},
|
63
|
-
touchedFields: {},
|
64
|
-
setFieldTouched: () => {},
|
65
|
-
});
|
54
|
+
export const FormContext = createContext<FormContextValue | null>(null);
|
package/src/server.ts
CHANGED
@@ -1,26 +1,33 @@
|
|
1
1
|
import { json } from "@remix-run/server-runtime";
|
2
|
-
import {
|
2
|
+
import {
|
3
|
+
ValidatorError,
|
4
|
+
ValidationErrorResponseData,
|
5
|
+
} from "./validation/types";
|
3
6
|
|
4
7
|
/**
|
5
8
|
* Takes the errors from a `Validator` and returns a `Response`.
|
6
|
-
*
|
7
|
-
*
|
9
|
+
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
10
|
+
* display the errors on the correct fields on the correct form.
|
11
|
+
*
|
12
|
+
* You can also provide a second argument to `validationError`
|
13
|
+
* to specify how to repopulate the form when JS is disabled.
|
14
|
+
*
|
15
|
+
* @example
|
16
|
+
* ```ts
|
17
|
+
* const result = validator.validate(await request.formData());
|
18
|
+
* if (result.error) return validationError(result.error, result.submittedData);
|
19
|
+
* ```
|
8
20
|
*/
|
9
|
-
export
|
10
|
-
|
11
|
-
|
12
|
-
)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
);
|
23
|
-
}
|
24
|
-
|
25
|
-
return json({ fieldErrors: errors }, { status: 422 });
|
26
|
-
};
|
21
|
+
export function validationError(
|
22
|
+
error: ValidatorError,
|
23
|
+
repopulateFields?: unknown
|
24
|
+
): Response {
|
25
|
+
return json<ValidationErrorResponseData>(
|
26
|
+
{
|
27
|
+
fieldErrors: error.fieldErrors,
|
28
|
+
subaction: error.subaction,
|
29
|
+
repopulateFields,
|
30
|
+
},
|
31
|
+
{ status: 422 }
|
32
|
+
);
|
33
|
+
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { GenericObject, Validator } from "..";
|
1
|
+
import { CreateValidatorArg, GenericObject, Validator } from "..";
|
2
2
|
import { objectFromPathEntries } from "../internal/flatten";
|
3
3
|
|
4
4
|
const preprocessFormData = (data: GenericObject | FormData): GenericObject => {
|
@@ -14,19 +14,30 @@ const preprocessFormData = (data: GenericObject | FormData): GenericObject => {
|
|
14
14
|
* It provides built-in handling for unflattening nested objects and
|
15
15
|
* extracting the values from FormData.
|
16
16
|
*/
|
17
|
-
export function createValidator<T>(
|
17
|
+
export function createValidator<T>(
|
18
|
+
validator: CreateValidatorArg<T>
|
19
|
+
): Validator<T> {
|
18
20
|
return {
|
19
|
-
validate: (value
|
21
|
+
validate: async (value) => {
|
20
22
|
const data = preprocessFormData(value);
|
21
|
-
const result = validator.validate(data);
|
23
|
+
const result = await validator.validate(data);
|
24
|
+
|
22
25
|
if (result.error) {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
return {
|
27
|
+
data: undefined,
|
28
|
+
error: {
|
29
|
+
fieldErrors: result.error,
|
30
|
+
subaction: data.subaction,
|
31
|
+
},
|
32
|
+
submittedData: data,
|
33
|
+
};
|
28
34
|
}
|
29
|
-
|
35
|
+
|
36
|
+
return {
|
37
|
+
data: result.data,
|
38
|
+
error: undefined,
|
39
|
+
submittedData: data,
|
40
|
+
};
|
30
41
|
},
|
31
42
|
validateField: (data: GenericObject | FormData, field: string) =>
|
32
43
|
validator.validateField(preprocessFormData(data), field),
|
package/src/validation/types.ts
CHANGED
@@ -2,16 +2,33 @@ export type FieldErrors = Record<string, string>;
|
|
2
2
|
|
3
3
|
export type TouchedFields = Record<string, boolean>;
|
4
4
|
|
5
|
-
export type FieldErrorsWithData = FieldErrors & { _submittedData: any };
|
6
|
-
|
7
5
|
export type GenericObject = { [key: string]: any };
|
8
6
|
|
7
|
+
export type ValidatorError = {
|
8
|
+
subaction?: string;
|
9
|
+
fieldErrors: FieldErrors;
|
10
|
+
};
|
11
|
+
|
12
|
+
export type ValidationErrorResponseData = {
|
13
|
+
subaction?: string;
|
14
|
+
fieldErrors: FieldErrors;
|
15
|
+
repopulateFields?: unknown;
|
16
|
+
};
|
17
|
+
|
18
|
+
export type BaseResult = { submittedData: GenericObject };
|
19
|
+
export type ErrorResult = BaseResult & {
|
20
|
+
error: ValidatorError;
|
21
|
+
data: undefined;
|
22
|
+
};
|
23
|
+
export type SuccessResult<DataType> = BaseResult & {
|
24
|
+
data: DataType;
|
25
|
+
error: undefined;
|
26
|
+
};
|
27
|
+
|
9
28
|
/**
|
10
29
|
* The result when validating a form.
|
11
30
|
*/
|
12
|
-
export type ValidationResult<DataType> =
|
13
|
-
| { data: DataType; error: undefined }
|
14
|
-
| { error: FieldErrors; data: undefined };
|
31
|
+
export type ValidationResult<DataType> = SuccessResult<DataType> | ErrorResult;
|
15
32
|
|
16
33
|
/**
|
17
34
|
* The result when validating an individual field in a form.
|
@@ -22,11 +39,25 @@ export type ValidateFieldResult = { error?: string };
|
|
22
39
|
* A `Validator` can be passed to the `validator` prop of a `ValidatedForm`.
|
23
40
|
*/
|
24
41
|
export type Validator<DataType> = {
|
25
|
-
validate: (
|
42
|
+
validate: (
|
43
|
+
unvalidatedData: GenericObject
|
44
|
+
) => Promise<ValidationResult<DataType>>;
|
45
|
+
validateField: (
|
46
|
+
unvalidatedData: GenericObject,
|
47
|
+
field: string
|
48
|
+
) => Promise<ValidateFieldResult>;
|
49
|
+
};
|
50
|
+
|
51
|
+
export type Valid<DataType> = { data: DataType; error: undefined };
|
52
|
+
export type Invalid = { error: FieldErrors; data: undefined };
|
53
|
+
export type CreateValidatorArg<DataType> = {
|
54
|
+
validate: (
|
55
|
+
unvalidatedData: GenericObject
|
56
|
+
) => Promise<Valid<DataType> | Invalid>;
|
26
57
|
validateField: (
|
27
58
|
unvalidatedData: GenericObject,
|
28
59
|
field: string
|
29
|
-
) => ValidateFieldResult
|
60
|
+
) => Promise<ValidateFieldResult>;
|
30
61
|
};
|
31
62
|
|
32
63
|
export type ValidatorData<T extends Validator<any>> = T extends Validator<
|