remix-validated-form 3.4.2 → 4.0.0-beta.0
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 +1 -1
- package/browser/ValidatedForm.js +89 -41
- package/browser/components.d.ts +10 -0
- package/browser/components.js +10 -0
- package/browser/hooks.d.ts +13 -1
- package/browser/hooks.js +32 -7
- 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 +1 -1
- package/build/ValidatedForm.js +107 -40
- package/build/hooks.d.ts +13 -1
- package/build/hooks.js +33 -7
- 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 +112 -42
- package/src/hooks.ts +43 -7
- package/src/internal/formContext.ts +2 -13
- package/src/server.ts +28 -21
- package/src/types.ts +1 -0
- package/src/validation/createValidator.ts +21 -10
- package/src/validation/types.ts +38 -7
package/build/ValidatedForm.js
CHANGED
@@ -1,4 +1,23 @@
|
|
1
1
|
"use strict";
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3
|
+
if (k2 === undefined) k2 = k;
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
5
|
+
}) : (function(o, m, k, k2) {
|
6
|
+
if (k2 === undefined) k2 = k;
|
7
|
+
o[k2] = m[k];
|
8
|
+
}));
|
9
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
10
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
11
|
+
}) : function(o, v) {
|
12
|
+
o["default"] = v;
|
13
|
+
});
|
14
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
15
|
+
if (mod && mod.__esModule) return mod;
|
16
|
+
var result = {};
|
17
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
18
|
+
__setModuleDefault(result, mod);
|
19
|
+
return result;
|
20
|
+
};
|
2
21
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
22
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
23
|
};
|
@@ -6,26 +25,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
25
|
exports.ValidatedForm = void 0;
|
7
26
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
8
27
|
const react_1 = require("@remix-run/react");
|
9
|
-
const
|
28
|
+
const uniq_1 = __importDefault(require("lodash/uniq"));
|
29
|
+
const react_2 = __importStar(require("react"));
|
10
30
|
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
|
11
31
|
const formContext_1 = require("./internal/formContext");
|
12
32
|
const MultiValueMap_1 = require("./internal/MultiValueMap");
|
13
33
|
const submissionCallbacks_1 = require("./internal/submissionCallbacks");
|
14
34
|
const util_1 = require("./internal/util");
|
15
|
-
function
|
16
|
-
var _a
|
35
|
+
function useErrorResponseForThisForm(fetcher, subaction) {
|
36
|
+
var _a;
|
17
37
|
const actionData = (0, react_1.useActionData)();
|
18
|
-
if (fetcher)
|
19
|
-
|
20
|
-
|
38
|
+
if (fetcher) {
|
39
|
+
if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
|
40
|
+
return fetcher.data;
|
21
41
|
return null;
|
22
|
-
if (actionData.fieldErrors) {
|
23
|
-
const submittedData = (_b = actionData.fieldErrors) === null || _b === void 0 ? void 0 : _b._submittedData;
|
24
|
-
const subactionsMatch = subaction
|
25
|
-
? subaction === (submittedData === null || submittedData === void 0 ? void 0 : submittedData.subaction)
|
26
|
-
: !(submittedData === null || submittedData === void 0 ? void 0 : submittedData.subaction);
|
27
|
-
return subactionsMatch ? actionData.fieldErrors : null;
|
28
42
|
}
|
43
|
+
if (!(actionData === null || actionData === void 0 ? void 0 : actionData.fieldErrors))
|
44
|
+
return null;
|
45
|
+
if ((!subaction && !actionData.subaction) ||
|
46
|
+
actionData.subaction === subaction)
|
47
|
+
return actionData;
|
29
48
|
return null;
|
30
49
|
}
|
31
50
|
function useFieldErrors(fieldErrorsFromBackend) {
|
@@ -67,30 +86,50 @@ const getDataFromForm = (el) => new FormData(el);
|
|
67
86
|
* It will only ever be a problem if the form includes a `<button type="reset" />`
|
68
87
|
* and only if JS is disabled.
|
69
88
|
*/
|
70
|
-
function useDefaultValues(
|
71
|
-
|
72
|
-
|
89
|
+
function useDefaultValues(repopulateFieldsFromBackend, defaultValues) {
|
90
|
+
return repopulateFieldsFromBackend !== null && repopulateFieldsFromBackend !== void 0 ? repopulateFieldsFromBackend : defaultValues;
|
91
|
+
}
|
92
|
+
function nonNull(value) {
|
93
|
+
return value !== null;
|
73
94
|
}
|
74
95
|
const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) => {
|
75
|
-
|
76
|
-
|
77
|
-
.
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
96
|
+
var _a;
|
97
|
+
const namesInOrder = [...formElement.elements]
|
98
|
+
.map((el) => {
|
99
|
+
const input = el instanceof RadioNodeList ? el[0] : el;
|
100
|
+
if (input instanceof HTMLInputElement)
|
101
|
+
return input.name;
|
102
|
+
return null;
|
103
|
+
})
|
104
|
+
.filter(nonNull)
|
105
|
+
.filter((name) => name in fieldErrors);
|
106
|
+
const uniqueNamesInOrder = (0, uniq_1.default)(namesInOrder);
|
107
|
+
for (const fieldName of uniqueNamesInOrder) {
|
108
|
+
if (customFocusHandlers.has(fieldName)) {
|
109
|
+
customFocusHandlers.getAll(fieldName).forEach((handler) => {
|
83
110
|
handler();
|
84
111
|
});
|
85
112
|
break;
|
86
113
|
}
|
87
|
-
|
88
|
-
|
89
|
-
if (input.type === "hidden") {
|
114
|
+
const elem = formElement.elements.namedItem(fieldName);
|
115
|
+
if (!elem)
|
90
116
|
continue;
|
117
|
+
if (elem instanceof RadioNodeList) {
|
118
|
+
const selectedRadio = (_a = [...elem]
|
119
|
+
.filter((item) => item instanceof HTMLInputElement)
|
120
|
+
.find((item) => item.value === elem.value)) !== null && _a !== void 0 ? _a : elem[0];
|
121
|
+
if (selectedRadio && selectedRadio instanceof HTMLInputElement) {
|
122
|
+
selectedRadio.focus();
|
123
|
+
break;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
if (elem instanceof HTMLInputElement) {
|
127
|
+
if (elem.type === "hidden") {
|
128
|
+
continue;
|
129
|
+
}
|
130
|
+
elem.focus();
|
131
|
+
break;
|
91
132
|
}
|
92
|
-
input.focus();
|
93
|
-
break;
|
94
133
|
}
|
95
134
|
};
|
96
135
|
/**
|
@@ -98,16 +137,17 @@ const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) =
|
|
98
137
|
*/
|
99
138
|
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, ...rest }) {
|
100
139
|
var _a;
|
101
|
-
const
|
102
|
-
const [fieldErrors, setFieldErrors] = useFieldErrors(
|
140
|
+
const backendError = useErrorResponseForThisForm(fetcher, subaction);
|
141
|
+
const [fieldErrors, setFieldErrors] = useFieldErrors(backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors);
|
103
142
|
const isSubmitting = useIsSubmitting(action, subaction, fetcher);
|
104
|
-
const defaultsToUse = useDefaultValues(
|
143
|
+
const defaultsToUse = useDefaultValues(backendError === null || backendError === void 0 ? void 0 : backendError.repopulateFields, defaultValues);
|
105
144
|
const [touchedFields, setTouchedFields] = (0, react_2.useState)({});
|
106
145
|
const [hasBeenSubmitted, setHasBeenSubmitted] = (0, react_2.useState)(false);
|
146
|
+
const submit = (0, react_1.useSubmit)();
|
107
147
|
const formRef = (0, react_2.useRef)(null);
|
108
148
|
(0, submissionCallbacks_1.useSubmitComplete)(isSubmitting, () => {
|
109
149
|
var _a;
|
110
|
-
if (!
|
150
|
+
if (!backendError && resetAfterSubmit) {
|
111
151
|
(_a = formRef.current) === null || _a === void 0 ? void 0 : _a.reset();
|
112
152
|
}
|
113
153
|
});
|
@@ -126,9 +166,9 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
126
166
|
clearError: (fieldName) => {
|
127
167
|
setFieldErrors((prev) => (0, util_1.omit)(prev, fieldName));
|
128
168
|
},
|
129
|
-
validateField: (fieldName) => {
|
169
|
+
validateField: async (fieldName) => {
|
130
170
|
(0, tiny_invariant_1.default)(formRef.current, "Cannot find reference to form");
|
131
|
-
const { error } = validator.validateField(getDataFromForm(formRef.current), fieldName);
|
171
|
+
const { error } = await validator.validateField(getDataFromForm(formRef.current), fieldName);
|
132
172
|
// By checking and returning `prev` here, we can avoid a re-render
|
133
173
|
// if the validation state is the same.
|
134
174
|
if (error) {
|
@@ -140,6 +180,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
140
180
|
[fieldName]: error,
|
141
181
|
};
|
142
182
|
});
|
183
|
+
return error;
|
143
184
|
}
|
144
185
|
else {
|
145
186
|
setFieldErrors((prev) => {
|
@@ -147,6 +188,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
147
188
|
return prev;
|
148
189
|
return (0, util_1.omit)(prev, fieldName);
|
149
190
|
});
|
191
|
+
return null;
|
150
192
|
}
|
151
193
|
},
|
152
194
|
registerReceiveFocus: (fieldName, handler) => {
|
@@ -168,18 +210,43 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
168
210
|
customFocusHandlers,
|
169
211
|
]);
|
170
212
|
const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : react_1.Form;
|
171
|
-
|
213
|
+
let clickedButtonRef = react_2.default.useRef();
|
214
|
+
(0, react_2.useEffect)(() => {
|
215
|
+
let form = formRef.current;
|
216
|
+
if (!form)
|
217
|
+
return;
|
218
|
+
function handleClick(event) {
|
219
|
+
if (!(event.target instanceof HTMLElement))
|
220
|
+
return;
|
221
|
+
let submitButton = event.target.closest("button,input[type=submit]");
|
222
|
+
if (submitButton &&
|
223
|
+
submitButton.form === form &&
|
224
|
+
submitButton.type === "submit") {
|
225
|
+
clickedButtonRef.current = submitButton;
|
226
|
+
}
|
227
|
+
}
|
228
|
+
window.addEventListener("click", handleClick);
|
229
|
+
return () => {
|
230
|
+
window.removeEventListener("click", handleClick);
|
231
|
+
};
|
232
|
+
}, []);
|
233
|
+
return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, action: action, onSubmit: async (e) => {
|
234
|
+
e.preventDefault();
|
172
235
|
setHasBeenSubmitted(true);
|
173
|
-
const result = validator.validate(getDataFromForm(
|
236
|
+
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
174
237
|
if (result.error) {
|
175
|
-
|
176
|
-
setFieldErrors(result.error);
|
238
|
+
setFieldErrors(result.error.fieldErrors);
|
177
239
|
if (!disableFocusOnError) {
|
178
|
-
focusFirstInvalidInput(result.error, customFocusHandlers(), formRef.current);
|
240
|
+
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
179
241
|
}
|
180
242
|
}
|
181
243
|
else {
|
182
|
-
onSubmit
|
244
|
+
onSubmit && onSubmit(result.data, e);
|
245
|
+
if (fetcher)
|
246
|
+
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
247
|
+
else
|
248
|
+
submit(clickedButtonRef.current || e.currentTarget);
|
249
|
+
clickedButtonRef.current = null;
|
183
250
|
}
|
184
251
|
}, onReset: (event) => {
|
185
252
|
onReset === null || onReset === void 0 ? void 0 : onReset(event);
|
package/build/hooks.d.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import { GetInputProps, ValidationBehaviorOptions } from "./internal/getInputProps";
|
2
|
+
import { ValidationState } from "./types";
|
2
3
|
export declare type FieldProps = {
|
3
4
|
/**
|
4
5
|
* The validation error message if there is one.
|
@@ -12,6 +13,14 @@ export declare type FieldProps = {
|
|
12
13
|
* Validates the field.
|
13
14
|
*/
|
14
15
|
validate: () => void;
|
16
|
+
/**
|
17
|
+
* The validation state of the field.
|
18
|
+
* - idle: the field has not been validated yet.
|
19
|
+
* - validating: the field is currently being validated.
|
20
|
+
* - valid: the field is valid.
|
21
|
+
* - invalid: the field is invalid.
|
22
|
+
*/
|
23
|
+
validationState: ValidationState;
|
15
24
|
/**
|
16
25
|
* The default value of the field, if there is one.
|
17
26
|
*/
|
@@ -46,7 +55,6 @@ export declare const useField: (name: string, options?: {
|
|
46
55
|
} | undefined) => FieldProps;
|
47
56
|
/**
|
48
57
|
* Provides access to the entire form context.
|
49
|
-
* This is not usually necessary, but can be useful for advanced use cases.
|
50
58
|
*/
|
51
59
|
export declare const useFormContext: () => import("./internal/formContext").FormContextValue;
|
52
60
|
/**
|
@@ -55,3 +63,7 @@ export declare const useFormContext: () => import("./internal/formContext").Form
|
|
55
63
|
* is aware of what form it's in and when _that_ form is being submitted.
|
56
64
|
*/
|
57
65
|
export declare const useIsSubmitting: () => boolean;
|
66
|
+
/**
|
67
|
+
* Returns whether or not the current form is valid.
|
68
|
+
*/
|
69
|
+
export declare const useIsValid: () => boolean;
|
package/build/hooks.js
CHANGED
@@ -3,35 +3,56 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.useIsSubmitting = exports.useFormContext = exports.useField = void 0;
|
6
|
+
exports.useIsValid = exports.useIsSubmitting = exports.useFormContext = exports.useField = void 0;
|
7
7
|
const get_1 = __importDefault(require("lodash/get"));
|
8
8
|
const toPath_1 = __importDefault(require("lodash/toPath"));
|
9
9
|
const react_1 = require("react");
|
10
10
|
const formContext_1 = require("./internal/formContext");
|
11
11
|
const getInputProps_1 = require("./internal/getInputProps");
|
12
|
+
const useInternalFormContext = (hookName) => {
|
13
|
+
const context = (0, react_1.useContext)(formContext_1.FormContext);
|
14
|
+
if (!context)
|
15
|
+
throw new Error(`${hookName} must be used within a ValidatedForm component`);
|
16
|
+
return context;
|
17
|
+
};
|
12
18
|
/**
|
13
19
|
* Provides the data and helpers necessary to set up a field.
|
14
20
|
*/
|
15
21
|
const useField = (name, options) => {
|
16
|
-
const { fieldErrors, clearError, validateField, defaultValues, registerReceiveFocus, touchedFields, setFieldTouched, hasBeenSubmitted, } = (
|
22
|
+
const { fieldErrors, clearError, validateField, defaultValues, registerReceiveFocus, touchedFields, setFieldTouched, hasBeenSubmitted, } = useInternalFormContext("useField");
|
17
23
|
const isTouched = !!touchedFields[name];
|
18
24
|
const { handleReceiveFocus } = options !== null && options !== void 0 ? options : {};
|
25
|
+
const [isValidating, setValidating] = (0, react_1.useState)(false);
|
19
26
|
(0, react_1.useEffect)(() => {
|
20
27
|
if (handleReceiveFocus)
|
21
28
|
return registerReceiveFocus(name, handleReceiveFocus);
|
22
29
|
}, [handleReceiveFocus, name, registerReceiveFocus]);
|
23
30
|
const field = (0, react_1.useMemo)(() => {
|
31
|
+
const error = fieldErrors[name];
|
32
|
+
const getValidationState = () => {
|
33
|
+
if (isValidating)
|
34
|
+
return "validating";
|
35
|
+
if (error)
|
36
|
+
return "invalid";
|
37
|
+
if (!isTouched && !hasBeenSubmitted)
|
38
|
+
return "idle";
|
39
|
+
return "valid";
|
40
|
+
};
|
24
41
|
const helpers = {
|
25
|
-
error
|
42
|
+
error,
|
26
43
|
clearError: () => {
|
27
44
|
clearError(name);
|
28
45
|
},
|
29
|
-
validate: () =>
|
46
|
+
validate: () => {
|
47
|
+
setValidating(true);
|
48
|
+
validateField(name).then((error) => setValidating(false));
|
49
|
+
},
|
30
50
|
defaultValue: defaultValues
|
31
51
|
? (0, get_1.default)(defaultValues, (0, toPath_1.default)(name), undefined)
|
32
52
|
: undefined,
|
33
53
|
touched: isTouched,
|
34
54
|
setTouched: (touched) => setFieldTouched(name, touched),
|
55
|
+
validationState: getValidationState(),
|
35
56
|
};
|
36
57
|
const getInputProps = (0, getInputProps_1.createGetInputProps)({
|
37
58
|
...helpers,
|
@@ -50,6 +71,7 @@ const useField = (name, options) => {
|
|
50
71
|
isTouched,
|
51
72
|
hasBeenSubmitted,
|
52
73
|
options === null || options === void 0 ? void 0 : options.validationBehavior,
|
74
|
+
isValidating,
|
53
75
|
clearError,
|
54
76
|
validateField,
|
55
77
|
setFieldTouched,
|
@@ -59,14 +81,18 @@ const useField = (name, options) => {
|
|
59
81
|
exports.useField = useField;
|
60
82
|
/**
|
61
83
|
* Provides access to the entire form context.
|
62
|
-
* This is not usually necessary, but can be useful for advanced use cases.
|
63
84
|
*/
|
64
|
-
const useFormContext = () => (
|
85
|
+
const useFormContext = () => useInternalFormContext("useFormContext");
|
65
86
|
exports.useFormContext = useFormContext;
|
66
87
|
/**
|
67
88
|
* Returns whether or not the parent form is currently being submitted.
|
68
89
|
* This is different from remix's `useTransition().submission` in that it
|
69
90
|
* is aware of what form it's in and when _that_ form is being submitted.
|
70
91
|
*/
|
71
|
-
const useIsSubmitting = () => (
|
92
|
+
const useIsSubmitting = () => useInternalFormContext("useIsSubmitting").isSubmitting;
|
72
93
|
exports.useIsSubmitting = useIsSubmitting;
|
94
|
+
/**
|
95
|
+
* Returns whether or not the current form is valid.
|
96
|
+
*/
|
97
|
+
const useIsValid = () => useInternalFormContext("useIsValid").isValid;
|
98
|
+
exports.useIsValid = useIsValid;
|
@@ -12,7 +12,7 @@ export declare type FormContextValue = {
|
|
12
12
|
/**
|
13
13
|
* Validate the specified field.
|
14
14
|
*/
|
15
|
-
validateField: (fieldName: string) =>
|
15
|
+
validateField: (fieldName: string) => Promise<string | null>;
|
16
16
|
/**
|
17
17
|
* The `action` prop of the form.
|
18
18
|
*/
|
@@ -29,7 +29,6 @@ export declare type FormContextValue = {
|
|
29
29
|
hasBeenSubmitted: boolean;
|
30
30
|
/**
|
31
31
|
* Whether or not the form is valid.
|
32
|
-
* This is a shortcut for `Object.keys(fieldErrors).length === 0`.
|
33
32
|
*/
|
34
33
|
isValid: boolean;
|
35
34
|
/**
|
@@ -52,4 +51,4 @@ export declare type FormContextValue = {
|
|
52
51
|
*/
|
53
52
|
setFieldTouched: (fieldName: string, touched: boolean) => void;
|
54
53
|
};
|
55
|
-
export declare const FormContext: import("react").Context<FormContextValue>;
|
54
|
+
export declare const FormContext: import("react").Context<FormContextValue | null>;
|
@@ -2,14 +2,4 @@
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.FormContext = void 0;
|
4
4
|
const react_1 = require("react");
|
5
|
-
exports.FormContext = (0, react_1.createContext)(
|
6
|
-
fieldErrors: {},
|
7
|
-
clearError: () => { },
|
8
|
-
validateField: () => { },
|
9
|
-
isSubmitting: false,
|
10
|
-
hasBeenSubmitted: false,
|
11
|
-
isValid: true,
|
12
|
-
registerReceiveFocus: () => () => { },
|
13
|
-
touchedFields: {},
|
14
|
-
setFieldTouched: () => { },
|
15
|
-
});
|
5
|
+
exports.FormContext = (0, react_1.createContext)(null);
|
package/build/server.d.ts
CHANGED
@@ -1,7 +1,16 @@
|
|
1
|
-
import {
|
1
|
+
import { ValidatorError } from "./validation/types";
|
2
2
|
/**
|
3
3
|
* Takes the errors from a `Validator` and returns a `Response`.
|
4
|
-
*
|
5
|
-
*
|
4
|
+
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
5
|
+
* display the errors on the correct fields on the correct form.
|
6
|
+
*
|
7
|
+
* _Recommended_: You can also provide a second argument to `validationError`
|
8
|
+
* to specify how to repopulate the form when JS is disabled.
|
9
|
+
*
|
10
|
+
* @example
|
11
|
+
* ```ts
|
12
|
+
* const result = validator.validate(await request.formData());
|
13
|
+
* if (result.error) return validationError(result.error, result.submittedData);
|
14
|
+
* ```
|
6
15
|
*/
|
7
|
-
export declare
|
16
|
+
export declare function validationError(error: ValidatorError, repopulateFields?: unknown): Response;
|
package/build/server.js
CHANGED
@@ -4,18 +4,23 @@ exports.validationError = void 0;
|
|
4
4
|
const server_runtime_1 = require("@remix-run/server-runtime");
|
5
5
|
/**
|
6
6
|
* Takes the errors from a `Validator` and returns a `Response`.
|
7
|
-
*
|
8
|
-
*
|
7
|
+
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
8
|
+
* display the errors on the correct fields on the correct form.
|
9
|
+
*
|
10
|
+
* _Recommended_: You can also provide a second argument to `validationError`
|
11
|
+
* to specify how to repopulate the form when JS is disabled.
|
12
|
+
*
|
13
|
+
* @example
|
14
|
+
* ```ts
|
15
|
+
* const result = validator.validate(await request.formData());
|
16
|
+
* if (result.error) return validationError(result.error, result.submittedData);
|
17
|
+
* ```
|
9
18
|
*/
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
}, { status: 422 });
|
18
|
-
}
|
19
|
-
return (0, server_runtime_1.json)({ fieldErrors: errors }, { status: 422 });
|
20
|
-
};
|
19
|
+
function validationError(error, repopulateFields) {
|
20
|
+
return (0, server_runtime_1.json)({
|
21
|
+
fieldErrors: error.fieldErrors,
|
22
|
+
subaction: error.subaction,
|
23
|
+
repopulateFields,
|
24
|
+
}, { status: 422 });
|
25
|
+
}
|
21
26
|
exports.validationError = validationError;
|
package/build/types.d.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export declare type ValidationState = "idle" | "validating" | "valid" | "invalid";
|
package/build/types.js
ADDED
@@ -1,7 +1,7 @@
|
|
1
|
-
import { Validator } from "..";
|
1
|
+
import { CreateValidatorArg, Validator } from "..";
|
2
2
|
/**
|
3
3
|
* Used to create a validator for a form.
|
4
4
|
* It provides built-in handling for unflattening nested objects and
|
5
5
|
* extracting the values from FormData.
|
6
6
|
*/
|
7
|
-
export declare function createValidator<T>(validator:
|
7
|
+
export declare function createValidator<T>(validator: CreateValidatorArg<T>): Validator<T>;
|
@@ -16,17 +16,24 @@ const preprocessFormData = (data) => {
|
|
16
16
|
*/
|
17
17
|
function createValidator(validator) {
|
18
18
|
return {
|
19
|
-
validate: (value) => {
|
19
|
+
validate: async (value) => {
|
20
20
|
const data = preprocessFormData(value);
|
21
|
-
const result = validator.validate(data);
|
21
|
+
const result = await validator.validate(data);
|
22
22
|
if (result.error) {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
return {
|
24
|
+
data: undefined,
|
25
|
+
error: {
|
26
|
+
fieldErrors: result.error,
|
27
|
+
subaction: data.subaction,
|
28
|
+
},
|
29
|
+
submittedData: data,
|
30
|
+
};
|
28
31
|
}
|
29
|
-
return
|
32
|
+
return {
|
33
|
+
data: result.data,
|
34
|
+
error: undefined,
|
35
|
+
submittedData: data,
|
36
|
+
};
|
30
37
|
},
|
31
38
|
validateField: (data, field) => validator.validateField(preprocessFormData(data), field),
|
32
39
|
};
|
@@ -1,21 +1,32 @@
|
|
1
1
|
export declare type FieldErrors = Record<string, string>;
|
2
2
|
export declare type TouchedFields = Record<string, boolean>;
|
3
|
-
export declare type FieldErrorsWithData = FieldErrors & {
|
4
|
-
_submittedData: any;
|
5
|
-
};
|
6
3
|
export declare type GenericObject = {
|
7
4
|
[key: string]: any;
|
8
5
|
};
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
export declare type ValidatorError = {
|
7
|
+
subaction?: string;
|
8
|
+
fieldErrors: FieldErrors;
|
9
|
+
};
|
10
|
+
export declare type ValidationErrorResponseData = {
|
11
|
+
subaction?: string;
|
12
|
+
fieldErrors: FieldErrors;
|
13
|
+
repopulateFields?: unknown;
|
14
|
+
};
|
15
|
+
export declare type BaseResult = {
|
16
|
+
submittedData: GenericObject;
|
17
|
+
};
|
18
|
+
export declare type ErrorResult = BaseResult & {
|
19
|
+
error: ValidatorError;
|
20
|
+
data: undefined;
|
21
|
+
};
|
22
|
+
export declare type SuccessResult<DataType> = BaseResult & {
|
13
23
|
data: DataType;
|
14
24
|
error: undefined;
|
15
|
-
} | {
|
16
|
-
error: FieldErrors;
|
17
|
-
data: undefined;
|
18
25
|
};
|
26
|
+
/**
|
27
|
+
* The result when validating a form.
|
28
|
+
*/
|
29
|
+
export declare type ValidationResult<DataType> = SuccessResult<DataType> | ErrorResult;
|
19
30
|
/**
|
20
31
|
* The result when validating an individual field in a form.
|
21
32
|
*/
|
@@ -26,7 +37,19 @@ export declare type ValidateFieldResult = {
|
|
26
37
|
* A `Validator` can be passed to the `validator` prop of a `ValidatedForm`.
|
27
38
|
*/
|
28
39
|
export declare type Validator<DataType> = {
|
29
|
-
validate: (unvalidatedData: GenericObject) => ValidationResult<DataType
|
30
|
-
validateField: (unvalidatedData: GenericObject, field: string) => ValidateFieldResult
|
40
|
+
validate: (unvalidatedData: GenericObject) => Promise<ValidationResult<DataType>>;
|
41
|
+
validateField: (unvalidatedData: GenericObject, field: string) => Promise<ValidateFieldResult>;
|
42
|
+
};
|
43
|
+
export declare type Valid<DataType> = {
|
44
|
+
data: DataType;
|
45
|
+
error: undefined;
|
46
|
+
};
|
47
|
+
export declare type Invalid = {
|
48
|
+
error: FieldErrors;
|
49
|
+
data: undefined;
|
50
|
+
};
|
51
|
+
export declare type CreateValidatorArg<DataType> = {
|
52
|
+
validate: (unvalidatedData: GenericObject) => Promise<Valid<DataType> | Invalid>;
|
53
|
+
validateField: (unvalidatedData: GenericObject, field: string) => Promise<ValidateFieldResult>;
|
31
54
|
};
|
32
55
|
export declare type ValidatorData<T extends Validator<any>> = T extends Validator<infer U> ? U : never;
|