remix-validated-form 3.4.1 → 4.0.1-beta.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/.turbo/turbo-build.log +2 -2
- package/README.md +1 -1
- package/browser/ValidatedForm.d.ts +2 -2
- package/browser/ValidatedForm.js +99 -43
- 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/internal/getInputProps.js +1 -1
- 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 +117 -42
- 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/internal/getInputProps.js +1 -1
- 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 +126 -43
- package/src/hooks.ts +21 -5
- package/src/internal/formContext.ts +2 -13
- package/src/internal/getInputProps.ts +1 -1
- package/src/server.ts +28 -21
- package/src/validation/createValidator.ts +21 -10
- package/src/validation/types.ts +38 -7
package/.turbo/turbo-build.log
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
[2K[1G[2m$ npm run build:browser && npm run build:main[22m
|
2
2
|
|
3
|
-
> remix-validated-form@
|
3
|
+
> remix-validated-form@4.0.0 build:browser
|
4
4
|
> tsc --module ESNext --outDir ./browser
|
5
5
|
|
6
6
|
|
7
|
-
> remix-validated-form@
|
7
|
+
> remix-validated-form@4.0.0 build:main
|
8
8
|
> tsc --module CommonJS --outDir ./build
|
9
9
|
|
package/README.md
CHANGED
@@ -120,7 +120,7 @@ const validator = withYup(
|
|
120
120
|
);
|
121
121
|
|
122
122
|
export const action: ActionFunction = async ({ request }) => {
|
123
|
-
const fieldValues = validator.validate(await request.formData());
|
123
|
+
const fieldValues = await validator.validate(await request.formData());
|
124
124
|
if (fieldValues.error) return validationError(fieldValues.error);
|
125
125
|
const { firstName, lastName, email } = fieldValues.data;
|
126
126
|
|
@@ -10,7 +10,7 @@ export declare type FormProps<DataType> = {
|
|
10
10
|
* A submit callback that gets called when the form is submitted
|
11
11
|
* after all validations have been run.
|
12
12
|
*/
|
13
|
-
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void
|
13
|
+
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => Promise<void>;
|
14
14
|
/**
|
15
15
|
* Allows you to provide a `fetcher` from remix's `useFetcher` hook.
|
16
16
|
* The form will use the fetcher for loading states, action data, etc
|
@@ -47,4 +47,4 @@ export declare type FormProps<DataType> = {
|
|
47
47
|
/**
|
48
48
|
* The primary form component of `remix-validated-form`.
|
49
49
|
*/
|
50
|
-
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, ...rest }: FormProps<DataType>): JSX.Element;
|
50
|
+
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, ...rest }: FormProps<DataType>): JSX.Element;
|
package/browser/ValidatedForm.js
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
2
|
-
import { Form as RemixForm, useActionData, useFormAction, useTransition, } from "@remix-run/react";
|
3
|
-
import
|
2
|
+
import { Form as RemixForm, useActionData, useFormAction, useSubmit, useTransition, } from "@remix-run/react";
|
3
|
+
import uniq from "lodash/uniq";
|
4
|
+
import React, { useEffect, useMemo, useRef, useState, } from "react";
|
4
5
|
import invariant from "tiny-invariant";
|
5
6
|
import { FormContext } from "./internal/formContext";
|
6
7
|
import { useMultiValueMap } from "./internal/MultiValueMap";
|
7
8
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
8
9
|
import { omit, mergeRefs } from "./internal/util";
|
9
|
-
function
|
10
|
-
var _a
|
10
|
+
function useErrorResponseForThisForm(fetcher, subaction) {
|
11
|
+
var _a;
|
11
12
|
const actionData = useActionData();
|
12
|
-
if (fetcher)
|
13
|
-
|
14
|
-
|
13
|
+
if (fetcher) {
|
14
|
+
if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
|
15
|
+
return fetcher.data;
|
15
16
|
return null;
|
16
|
-
if (actionData.fieldErrors) {
|
17
|
-
const submittedData = (_b = actionData.fieldErrors) === null || _b === void 0 ? void 0 : _b._submittedData;
|
18
|
-
const subactionsMatch = subaction
|
19
|
-
? subaction === (submittedData === null || submittedData === void 0 ? void 0 : submittedData.subaction)
|
20
|
-
: !(submittedData === null || submittedData === void 0 ? void 0 : submittedData.subaction);
|
21
|
-
return subactionsMatch ? actionData.fieldErrors : null;
|
22
17
|
}
|
18
|
+
if (!(actionData === null || actionData === void 0 ? void 0 : actionData.fieldErrors))
|
19
|
+
return null;
|
20
|
+
if ((!subaction && !actionData.subaction) ||
|
21
|
+
actionData.subaction === subaction)
|
22
|
+
return actionData;
|
23
23
|
return null;
|
24
24
|
}
|
25
25
|
function useFieldErrors(fieldErrorsFromBackend) {
|
@@ -61,47 +61,70 @@ const getDataFromForm = (el) => new FormData(el);
|
|
61
61
|
* It will only ever be a problem if the form includes a `<button type="reset" />`
|
62
62
|
* and only if JS is disabled.
|
63
63
|
*/
|
64
|
-
function useDefaultValues(
|
65
|
-
|
66
|
-
|
64
|
+
function useDefaultValues(repopulateFieldsFromBackend, defaultValues) {
|
65
|
+
return repopulateFieldsFromBackend !== null && repopulateFieldsFromBackend !== void 0 ? repopulateFieldsFromBackend : defaultValues;
|
66
|
+
}
|
67
|
+
function nonNull(value) {
|
68
|
+
return value !== null;
|
67
69
|
}
|
68
70
|
const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) => {
|
69
|
-
|
70
|
-
|
71
|
-
.
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
71
|
+
var _a;
|
72
|
+
const namesInOrder = [...formElement.elements]
|
73
|
+
.map((el) => {
|
74
|
+
const input = el instanceof RadioNodeList ? el[0] : el;
|
75
|
+
if (input instanceof HTMLInputElement)
|
76
|
+
return input.name;
|
77
|
+
return null;
|
78
|
+
})
|
79
|
+
.filter(nonNull)
|
80
|
+
.filter((name) => name in fieldErrors);
|
81
|
+
const uniqueNamesInOrder = uniq(namesInOrder);
|
82
|
+
for (const fieldName of uniqueNamesInOrder) {
|
83
|
+
if (customFocusHandlers.has(fieldName)) {
|
84
|
+
customFocusHandlers.getAll(fieldName).forEach((handler) => {
|
77
85
|
handler();
|
78
86
|
});
|
79
87
|
break;
|
80
88
|
}
|
81
|
-
|
82
|
-
|
83
|
-
if (input.type === "hidden") {
|
89
|
+
const elem = formElement.elements.namedItem(fieldName);
|
90
|
+
if (!elem)
|
84
91
|
continue;
|
92
|
+
if (elem instanceof RadioNodeList) {
|
93
|
+
const selectedRadio = (_a = [...elem]
|
94
|
+
.filter((item) => item instanceof HTMLInputElement)
|
95
|
+
.find((item) => item.value === elem.value)) !== null && _a !== void 0 ? _a : elem[0];
|
96
|
+
if (selectedRadio && selectedRadio instanceof HTMLInputElement) {
|
97
|
+
selectedRadio.focus();
|
98
|
+
break;
|
99
|
+
}
|
100
|
+
}
|
101
|
+
if (elem instanceof HTMLInputElement) {
|
102
|
+
if (elem.type === "hidden") {
|
103
|
+
continue;
|
104
|
+
}
|
105
|
+
elem.focus();
|
106
|
+
break;
|
85
107
|
}
|
86
|
-
input.focus();
|
87
|
-
break;
|
88
108
|
}
|
89
109
|
};
|
90
110
|
/**
|
91
111
|
* The primary form component of `remix-validated-form`.
|
92
112
|
*/
|
93
|
-
export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, ...rest }) {
|
113
|
+
export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, ...rest }) {
|
94
114
|
var _a;
|
95
|
-
const
|
96
|
-
const [fieldErrors, setFieldErrors] = useFieldErrors(
|
115
|
+
const backendError = useErrorResponseForThisForm(fetcher, subaction);
|
116
|
+
const [fieldErrors, setFieldErrors] = useFieldErrors(backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors);
|
97
117
|
const isSubmitting = useIsSubmitting(action, subaction, fetcher);
|
98
|
-
const
|
118
|
+
const [isValidating, setIsValidating] = useState(false);
|
119
|
+
const defaultsToUse = useDefaultValues(backendError === null || backendError === void 0 ? void 0 : backendError.repopulateFields, defaultValues);
|
99
120
|
const [touchedFields, setTouchedFields] = useState({});
|
100
121
|
const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
|
122
|
+
const submit = useSubmit();
|
101
123
|
const formRef = useRef(null);
|
102
124
|
useSubmitComplete(isSubmitting, () => {
|
103
125
|
var _a;
|
104
|
-
|
126
|
+
setIsValidating(false);
|
127
|
+
if (!backendError && resetAfterSubmit) {
|
105
128
|
(_a = formRef.current) === null || _a === void 0 ? void 0 : _a.reset();
|
106
129
|
}
|
107
130
|
});
|
@@ -110,7 +133,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
110
133
|
fieldErrors,
|
111
134
|
action,
|
112
135
|
defaultValues: defaultsToUse,
|
113
|
-
isSubmitting:
|
136
|
+
isSubmitting: isValidating || isSubmitting,
|
114
137
|
isValid: Object.keys(fieldErrors).length === 0,
|
115
138
|
touchedFields,
|
116
139
|
setFieldTouched: (fieldName, touched) => setTouchedFields((prev) => ({
|
@@ -120,9 +143,9 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
120
143
|
clearError: (fieldName) => {
|
121
144
|
setFieldErrors((prev) => omit(prev, fieldName));
|
122
145
|
},
|
123
|
-
validateField: (fieldName) => {
|
146
|
+
validateField: async (fieldName) => {
|
124
147
|
invariant(formRef.current, "Cannot find reference to form");
|
125
|
-
const { error } = validator.validateField(getDataFromForm(formRef.current), fieldName);
|
148
|
+
const { error } = await validator.validateField(getDataFromForm(formRef.current), fieldName);
|
126
149
|
// By checking and returning `prev` here, we can avoid a re-render
|
127
150
|
// if the validation state is the same.
|
128
151
|
if (error) {
|
@@ -134,6 +157,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
134
157
|
[fieldName]: error,
|
135
158
|
};
|
136
159
|
});
|
160
|
+
return error;
|
137
161
|
}
|
138
162
|
else {
|
139
163
|
setFieldErrors((prev) => {
|
@@ -141,6 +165,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
141
165
|
return prev;
|
142
166
|
return omit(prev, fieldName);
|
143
167
|
});
|
168
|
+
return null;
|
144
169
|
}
|
145
170
|
},
|
146
171
|
registerReceiveFocus: (fieldName, handler) => {
|
@@ -154,6 +179,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
154
179
|
fieldErrors,
|
155
180
|
action,
|
156
181
|
defaultsToUse,
|
182
|
+
isValidating,
|
157
183
|
isSubmitting,
|
158
184
|
touchedFields,
|
159
185
|
hasBeenSubmitted,
|
@@ -162,18 +188,48 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
162
188
|
customFocusHandlers,
|
163
189
|
]);
|
164
190
|
const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : RemixForm;
|
165
|
-
|
191
|
+
let clickedButtonRef = React.useRef();
|
192
|
+
useEffect(() => {
|
193
|
+
let form = formRef.current;
|
194
|
+
if (!form)
|
195
|
+
return;
|
196
|
+
function handleClick(event) {
|
197
|
+
if (!(event.target instanceof HTMLElement))
|
198
|
+
return;
|
199
|
+
let submitButton = event.target.closest("button,input[type=submit]");
|
200
|
+
if (submitButton &&
|
201
|
+
submitButton.form === form &&
|
202
|
+
submitButton.type === "submit") {
|
203
|
+
clickedButtonRef.current = submitButton;
|
204
|
+
}
|
205
|
+
}
|
206
|
+
window.addEventListener("click", handleClick);
|
207
|
+
return () => {
|
208
|
+
window.removeEventListener("click", handleClick);
|
209
|
+
};
|
210
|
+
}, []);
|
211
|
+
return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, action: action, method: method, replace: replace, onSubmit: async (e) => {
|
212
|
+
e.preventDefault();
|
166
213
|
setHasBeenSubmitted(true);
|
167
|
-
|
214
|
+
setIsValidating(true);
|
215
|
+
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
168
216
|
if (result.error) {
|
169
|
-
|
170
|
-
setFieldErrors(result.error);
|
217
|
+
setIsValidating(false);
|
218
|
+
setFieldErrors(result.error.fieldErrors);
|
171
219
|
if (!disableFocusOnError) {
|
172
|
-
focusFirstInvalidInput(result.error, customFocusHandlers(), formRef.current);
|
220
|
+
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
173
221
|
}
|
174
222
|
}
|
175
223
|
else {
|
176
|
-
onSubmit
|
224
|
+
onSubmit && onSubmit(result.data, e);
|
225
|
+
if (fetcher)
|
226
|
+
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
227
|
+
else
|
228
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
229
|
+
method,
|
230
|
+
replace,
|
231
|
+
});
|
232
|
+
clickedButtonRef.current = null;
|
177
233
|
}
|
178
234
|
}, onReset: (event) => {
|
179
235
|
onReset === null || onReset === void 0 ? void 0 : onReset(event);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { HTMLProps } from "react";
|
2
|
+
declare type ValidatedInputProps = HTMLProps<HTMLInputElement> & {
|
3
|
+
name: string;
|
4
|
+
};
|
5
|
+
export declare const ValidatedInput: ({ name, ...rest }: ValidatedInputProps) => JSX.Element;
|
6
|
+
declare type ErrorMessageProps = {
|
7
|
+
name: string;
|
8
|
+
};
|
9
|
+
export declare const ErrorMessage: ({ name }: ErrorMessageProps) => string | undefined;
|
10
|
+
export {};
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useField } from ".";
|
3
|
+
export const ValidatedInput = ({ name, ...rest }) => {
|
4
|
+
const { getInputProps } = useField(name);
|
5
|
+
return _jsx("input", { ...getInputProps(rest) }, void 0);
|
6
|
+
};
|
7
|
+
export const ErrorMessage = ({ name }) => {
|
8
|
+
const { error } = useField(name);
|
9
|
+
return error;
|
10
|
+
};
|
package/browser/hooks.d.ts
CHANGED
@@ -46,7 +46,6 @@ export declare const useField: (name: string, options?: {
|
|
46
46
|
} | undefined) => FieldProps;
|
47
47
|
/**
|
48
48
|
* Provides access to the entire form context.
|
49
|
-
* This is not usually necessary, but can be useful for advanced use cases.
|
50
49
|
*/
|
51
50
|
export declare const useFormContext: () => import("./internal/formContext").FormContextValue;
|
52
51
|
/**
|
@@ -55,3 +54,7 @@ export declare const useFormContext: () => import("./internal/formContext").Form
|
|
55
54
|
* is aware of what form it's in and when _that_ form is being submitted.
|
56
55
|
*/
|
57
56
|
export declare const useIsSubmitting: () => boolean;
|
57
|
+
/**
|
58
|
+
* Returns whether or not the current form is valid.
|
59
|
+
*/
|
60
|
+
export declare const useIsValid: () => boolean;
|
package/browser/hooks.js
CHANGED
@@ -3,11 +3,17 @@ import toPath from "lodash/toPath";
|
|
3
3
|
import { useContext, useEffect, useMemo } from "react";
|
4
4
|
import { FormContext } from "./internal/formContext";
|
5
5
|
import { createGetInputProps, } from "./internal/getInputProps";
|
6
|
+
const useInternalFormContext = (hookName) => {
|
7
|
+
const context = useContext(FormContext);
|
8
|
+
if (!context)
|
9
|
+
throw new Error(`${hookName} must be used within a ValidatedForm component`);
|
10
|
+
return context;
|
11
|
+
};
|
6
12
|
/**
|
7
13
|
* Provides the data and helpers necessary to set up a field.
|
8
14
|
*/
|
9
15
|
export const useField = (name, options) => {
|
10
|
-
const { fieldErrors, clearError, validateField, defaultValues, registerReceiveFocus, touchedFields, setFieldTouched, hasBeenSubmitted, } =
|
16
|
+
const { fieldErrors, clearError, validateField, defaultValues, registerReceiveFocus, touchedFields, setFieldTouched, hasBeenSubmitted, } = useInternalFormContext("useField");
|
11
17
|
const isTouched = !!touchedFields[name];
|
12
18
|
const { handleReceiveFocus } = options !== null && options !== void 0 ? options : {};
|
13
19
|
useEffect(() => {
|
@@ -20,7 +26,9 @@ export const useField = (name, options) => {
|
|
20
26
|
clearError: () => {
|
21
27
|
clearError(name);
|
22
28
|
},
|
23
|
-
validate: () =>
|
29
|
+
validate: () => {
|
30
|
+
validateField(name);
|
31
|
+
},
|
24
32
|
defaultValue: defaultValues
|
25
33
|
? get(defaultValues, toPath(name), undefined)
|
26
34
|
: undefined,
|
@@ -52,12 +60,15 @@ export const useField = (name, options) => {
|
|
52
60
|
};
|
53
61
|
/**
|
54
62
|
* Provides access to the entire form context.
|
55
|
-
* This is not usually necessary, but can be useful for advanced use cases.
|
56
63
|
*/
|
57
|
-
export const useFormContext = () =>
|
64
|
+
export const useFormContext = () => useInternalFormContext("useFormContext");
|
58
65
|
/**
|
59
66
|
* Returns whether or not the parent form is currently being submitted.
|
60
67
|
* This is different from remix's `useTransition().submission` in that it
|
61
68
|
* is aware of what form it's in and when _that_ form is being submitted.
|
62
69
|
*/
|
63
|
-
export const useIsSubmitting = () =>
|
70
|
+
export const useIsSubmitting = () => useInternalFormContext("useIsSubmitting").isSubmitting;
|
71
|
+
/**
|
72
|
+
* Returns whether or not the current form is valid.
|
73
|
+
*/
|
74
|
+
export const useIsValid = () => useInternalFormContext("useIsValid").isValid;
|
@@ -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>;
|
@@ -1,12 +1,2 @@
|
|
1
1
|
import { createContext } from "react";
|
2
|
-
export const FormContext = createContext(
|
3
|
-
fieldErrors: {},
|
4
|
-
clearError: () => { },
|
5
|
-
validateField: () => { },
|
6
|
-
isSubmitting: false,
|
7
|
-
hasBeenSubmitted: false,
|
8
|
-
isValid: true,
|
9
|
-
registerReceiveFocus: () => () => { },
|
10
|
-
touchedFields: {},
|
11
|
-
setFieldTouched: () => { },
|
12
|
-
});
|
2
|
+
export const FormContext = createContext(null);
|
@@ -5,7 +5,7 @@ const defaultValidationBehavior = {
|
|
5
5
|
};
|
6
6
|
const getCheckboxDefaultChecked = (value, defaultValue) => {
|
7
7
|
if (Array.isArray(defaultValue))
|
8
|
-
defaultValue.includes(value);
|
8
|
+
return defaultValue.includes(value);
|
9
9
|
if (typeof defaultValue === "boolean")
|
10
10
|
return defaultValue;
|
11
11
|
if (typeof defaultValue === "string")
|
package/browser/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
|
+
* 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/browser/server.js
CHANGED
@@ -1,17 +1,22 @@
|
|
1
1
|
import { json } from "@remix-run/server-runtime";
|
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
|
+
* 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
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
}, { status: 422 });
|
15
|
-
}
|
16
|
-
return json({ fieldErrors: errors }, { status: 422 });
|
17
|
-
};
|
16
|
+
export function validationError(error, repopulateFields) {
|
17
|
+
return json({
|
18
|
+
fieldErrors: error.fieldErrors,
|
19
|
+
subaction: error.subaction,
|
20
|
+
repopulateFields,
|
21
|
+
}, { status: 422 });
|
22
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare type ValidationState = "idle" | "validating" | "valid" | "invalid";
|
package/browser/types.js
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -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>;
|
@@ -13,17 +13,24 @@ const preprocessFormData = (data) => {
|
|
13
13
|
*/
|
14
14
|
export function createValidator(validator) {
|
15
15
|
return {
|
16
|
-
validate: (value) => {
|
16
|
+
validate: async (value) => {
|
17
17
|
const data = preprocessFormData(value);
|
18
|
-
const result = validator.validate(data);
|
18
|
+
const result = await validator.validate(data);
|
19
19
|
if (result.error) {
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
return {
|
21
|
+
data: undefined,
|
22
|
+
error: {
|
23
|
+
fieldErrors: result.error,
|
24
|
+
subaction: data.subaction,
|
25
|
+
},
|
26
|
+
submittedData: data,
|
27
|
+
};
|
25
28
|
}
|
26
|
-
return
|
29
|
+
return {
|
30
|
+
data: result.data,
|
31
|
+
error: undefined,
|
32
|
+
submittedData: data,
|
33
|
+
};
|
27
34
|
},
|
28
35
|
validateField: (data, field) => validator.validateField(preprocessFormData(data), field),
|
29
36
|
};
|
@@ -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;
|
package/build/ValidatedForm.d.ts
CHANGED
@@ -10,7 +10,7 @@ export declare type FormProps<DataType> = {
|
|
10
10
|
* A submit callback that gets called when the form is submitted
|
11
11
|
* after all validations have been run.
|
12
12
|
*/
|
13
|
-
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void
|
13
|
+
onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => Promise<void>;
|
14
14
|
/**
|
15
15
|
* Allows you to provide a `fetcher` from remix's `useFetcher` hook.
|
16
16
|
* The form will use the fetcher for loading states, action data, etc
|
@@ -47,4 +47,4 @@ export declare type FormProps<DataType> = {
|
|
47
47
|
/**
|
48
48
|
* The primary form component of `remix-validated-form`.
|
49
49
|
*/
|
50
|
-
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, ...rest }: FormProps<DataType>): JSX.Element;
|
50
|
+
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, ...rest }: FormProps<DataType>): JSX.Element;
|