remix-validated-form 4.0.0-beta.0 → 4.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/.turbo/turbo-build.log +2 -2
- package/browser/ValidatedForm.d.ts +1 -1
- package/browser/ValidatedForm.js +22 -19
- package/browser/hooks.d.ts +0 -9
- package/browser/hooks.js +3 -17
- package/browser/server.d.ts +1 -1
- package/browser/server.js +1 -1
- package/build/ValidatedForm.d.ts +1 -1
- package/build/ValidatedForm.js +21 -18
- package/build/hooks.d.ts +0 -9
- package/build/hooks.js +2 -16
- package/build/server.d.ts +1 -1
- package/build/server.js +1 -1
- package/package.json +1 -1
- package/src/ValidatedForm.tsx +27 -17
- package/src/hooks.ts +3 -23
- package/src/server.ts +1 -1
- package/src/types.ts +0 -1
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.1-beta.1 build:browser
|
4
4
|
> tsc --module ESNext --outDir ./browser
|
5
5
|
|
6
6
|
|
7
|
-
> remix-validated-form@
|
7
|
+
> remix-validated-form@4.0.1-beta.1 build:main
|
8
8
|
> tsc --module CommonJS --outDir ./build
|
9
9
|
|
@@ -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,5 +1,5 @@
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
2
|
-
import { Form as RemixForm, useActionData,
|
2
|
+
import { Form as RemixForm, useActionData, useSubmit, useTransition, } from "@remix-run/react";
|
3
3
|
import uniq from "lodash/uniq";
|
4
4
|
import React, { useEffect, useMemo, useRef, useState, } from "react";
|
5
5
|
import invariant from "tiny-invariant";
|
@@ -30,19 +30,16 @@ function useFieldErrors(fieldErrorsFromBackend) {
|
|
30
30
|
}, [fieldErrorsFromBackend]);
|
31
31
|
return [fieldErrors, setFieldErrors];
|
32
32
|
}
|
33
|
-
const useIsSubmitting = (
|
34
|
-
const
|
35
|
-
const
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
const
|
41
|
-
const
|
42
|
-
|
43
|
-
if (subaction)
|
44
|
-
return expectedAction === pendingAction && subaction === pendingSubAction;
|
45
|
-
return expectedAction === pendingAction && !pendingSubAction;
|
33
|
+
const useIsSubmitting = (fetcher) => {
|
34
|
+
const [isSubmitStarted, setSubmitStarted] = useState(false);
|
35
|
+
const transition = useTransition();
|
36
|
+
const hasActiveSubmission = fetcher
|
37
|
+
? fetcher.state === "submitting"
|
38
|
+
: !!transition.submission;
|
39
|
+
const isSubmitting = hasActiveSubmission && isSubmitStarted;
|
40
|
+
const startSubmit = () => setSubmitStarted(true);
|
41
|
+
const endSubmit = () => setSubmitStarted(false);
|
42
|
+
return [isSubmitting, startSubmit, endSubmit];
|
46
43
|
};
|
47
44
|
const getDataFromForm = (el) => new FormData(el);
|
48
45
|
/**
|
@@ -110,11 +107,11 @@ const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) =
|
|
110
107
|
/**
|
111
108
|
* The primary form component of `remix-validated-form`.
|
112
109
|
*/
|
113
|
-
export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, ...rest }) {
|
110
|
+
export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, ...rest }) {
|
114
111
|
var _a;
|
115
112
|
const backendError = useErrorResponseForThisForm(fetcher, subaction);
|
116
113
|
const [fieldErrors, setFieldErrors] = useFieldErrors(backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors);
|
117
|
-
const isSubmitting = useIsSubmitting(
|
114
|
+
const [isSubmitting, startSubmit, endSubmit] = useIsSubmitting(fetcher);
|
118
115
|
const defaultsToUse = useDefaultValues(backendError === null || backendError === void 0 ? void 0 : backendError.repopulateFields, defaultValues);
|
119
116
|
const [touchedFields, setTouchedFields] = useState({});
|
120
117
|
const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
|
@@ -122,6 +119,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
122
119
|
const formRef = useRef(null);
|
123
120
|
useSubmitComplete(isSubmitting, () => {
|
124
121
|
var _a;
|
122
|
+
endSubmit();
|
125
123
|
if (!backendError && resetAfterSubmit) {
|
126
124
|
(_a = formRef.current) === null || _a === void 0 ? void 0 : _a.reset();
|
127
125
|
}
|
@@ -131,7 +129,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
131
129
|
fieldErrors,
|
132
130
|
action,
|
133
131
|
defaultValues: defaultsToUse,
|
134
|
-
isSubmitting
|
132
|
+
isSubmitting,
|
135
133
|
isValid: Object.keys(fieldErrors).length === 0,
|
136
134
|
touchedFields,
|
137
135
|
setFieldTouched: (fieldName, touched) => setTouchedFields((prev) => ({
|
@@ -205,11 +203,13 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
205
203
|
window.removeEventListener("click", handleClick);
|
206
204
|
};
|
207
205
|
}, []);
|
208
|
-
return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, action: action, onSubmit: async (e) => {
|
206
|
+
return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, action: action, method: method, replace: replace, onSubmit: async (e) => {
|
209
207
|
e.preventDefault();
|
210
208
|
setHasBeenSubmitted(true);
|
209
|
+
startSubmit();
|
211
210
|
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
212
211
|
if (result.error) {
|
212
|
+
endSubmit();
|
213
213
|
setFieldErrors(result.error.fieldErrors);
|
214
214
|
if (!disableFocusOnError) {
|
215
215
|
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
@@ -220,7 +220,10 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
220
220
|
if (fetcher)
|
221
221
|
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
222
222
|
else
|
223
|
-
submit(clickedButtonRef.current || e.currentTarget
|
223
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
224
|
+
method,
|
225
|
+
replace,
|
226
|
+
});
|
224
227
|
clickedButtonRef.current = null;
|
225
228
|
}
|
226
229
|
}, onReset: (event) => {
|
package/browser/hooks.d.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import { GetInputProps, ValidationBehaviorOptions } from "./internal/getInputProps";
|
2
|
-
import { ValidationState } from "./types";
|
3
2
|
export declare type FieldProps = {
|
4
3
|
/**
|
5
4
|
* The validation error message if there is one.
|
@@ -13,14 +12,6 @@ export declare type FieldProps = {
|
|
13
12
|
* Validates the field.
|
14
13
|
*/
|
15
14
|
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;
|
24
15
|
/**
|
25
16
|
* The default value of the field, if there is one.
|
26
17
|
*/
|
package/browser/hooks.js
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import get from "lodash/get";
|
2
2
|
import toPath from "lodash/toPath";
|
3
|
-
import { useContext, useEffect, useMemo
|
3
|
+
import { useContext, useEffect, useMemo } from "react";
|
4
4
|
import { FormContext } from "./internal/formContext";
|
5
5
|
import { createGetInputProps, } from "./internal/getInputProps";
|
6
6
|
const useInternalFormContext = (hookName) => {
|
@@ -16,37 +16,24 @@ export const useField = (name, options) => {
|
|
16
16
|
const { fieldErrors, clearError, validateField, defaultValues, registerReceiveFocus, touchedFields, setFieldTouched, hasBeenSubmitted, } = useInternalFormContext("useField");
|
17
17
|
const isTouched = !!touchedFields[name];
|
18
18
|
const { handleReceiveFocus } = options !== null && options !== void 0 ? options : {};
|
19
|
-
const [isValidating, setValidating] = useState(false);
|
20
19
|
useEffect(() => {
|
21
20
|
if (handleReceiveFocus)
|
22
21
|
return registerReceiveFocus(name, handleReceiveFocus);
|
23
22
|
}, [handleReceiveFocus, name, registerReceiveFocus]);
|
24
23
|
const field = useMemo(() => {
|
25
|
-
const error = fieldErrors[name];
|
26
|
-
const getValidationState = () => {
|
27
|
-
if (isValidating)
|
28
|
-
return "validating";
|
29
|
-
if (error)
|
30
|
-
return "invalid";
|
31
|
-
if (!isTouched && !hasBeenSubmitted)
|
32
|
-
return "idle";
|
33
|
-
return "valid";
|
34
|
-
};
|
35
24
|
const helpers = {
|
36
|
-
error,
|
25
|
+
error: fieldErrors[name],
|
37
26
|
clearError: () => {
|
38
27
|
clearError(name);
|
39
28
|
},
|
40
29
|
validate: () => {
|
41
|
-
|
42
|
-
validateField(name).then((error) => setValidating(false));
|
30
|
+
validateField(name);
|
43
31
|
},
|
44
32
|
defaultValue: defaultValues
|
45
33
|
? get(defaultValues, toPath(name), undefined)
|
46
34
|
: undefined,
|
47
35
|
touched: isTouched,
|
48
36
|
setTouched: (touched) => setFieldTouched(name, touched),
|
49
|
-
validationState: getValidationState(),
|
50
37
|
};
|
51
38
|
const getInputProps = createGetInputProps({
|
52
39
|
...helpers,
|
@@ -65,7 +52,6 @@ export const useField = (name, options) => {
|
|
65
52
|
isTouched,
|
66
53
|
hasBeenSubmitted,
|
67
54
|
options === null || options === void 0 ? void 0 : options.validationBehavior,
|
68
|
-
isValidating,
|
69
55
|
clearError,
|
70
56
|
validateField,
|
71
57
|
setFieldTouched,
|
package/browser/server.d.ts
CHANGED
@@ -4,7 +4,7 @@ import { ValidatorError } from "./validation/types";
|
|
4
4
|
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
5
5
|
* display the errors on the correct fields on the correct form.
|
6
6
|
*
|
7
|
-
*
|
7
|
+
* You can also provide a second argument to `validationError`
|
8
8
|
* to specify how to repopulate the form when JS is disabled.
|
9
9
|
*
|
10
10
|
* @example
|
package/browser/server.js
CHANGED
@@ -4,7 +4,7 @@ import { json } from "@remix-run/server-runtime";
|
|
4
4
|
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
5
5
|
* display the errors on the correct fields on the correct form.
|
6
6
|
*
|
7
|
-
*
|
7
|
+
* You can also provide a second argument to `validationError`
|
8
8
|
* to specify how to repopulate the form when JS is disabled.
|
9
9
|
*
|
10
10
|
* @example
|
package/build/ValidatedForm.d.ts
CHANGED
@@ -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/build/ValidatedForm.js
CHANGED
@@ -55,19 +55,16 @@ function useFieldErrors(fieldErrorsFromBackend) {
|
|
55
55
|
}, [fieldErrorsFromBackend]);
|
56
56
|
return [fieldErrors, setFieldErrors];
|
57
57
|
}
|
58
|
-
const useIsSubmitting = (
|
59
|
-
const
|
60
|
-
const
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
const
|
66
|
-
const
|
67
|
-
|
68
|
-
if (subaction)
|
69
|
-
return expectedAction === pendingAction && subaction === pendingSubAction;
|
70
|
-
return expectedAction === pendingAction && !pendingSubAction;
|
58
|
+
const useIsSubmitting = (fetcher) => {
|
59
|
+
const [isSubmitStarted, setSubmitStarted] = (0, react_2.useState)(false);
|
60
|
+
const transition = (0, react_1.useTransition)();
|
61
|
+
const hasActiveSubmission = fetcher
|
62
|
+
? fetcher.state === "submitting"
|
63
|
+
: !!transition.submission;
|
64
|
+
const isSubmitting = hasActiveSubmission && isSubmitStarted;
|
65
|
+
const startSubmit = () => setSubmitStarted(true);
|
66
|
+
const endSubmit = () => setSubmitStarted(false);
|
67
|
+
return [isSubmitting, startSubmit, endSubmit];
|
71
68
|
};
|
72
69
|
const getDataFromForm = (el) => new FormData(el);
|
73
70
|
/**
|
@@ -135,11 +132,11 @@ const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) =
|
|
135
132
|
/**
|
136
133
|
* The primary form component of `remix-validated-form`.
|
137
134
|
*/
|
138
|
-
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, ...rest }) {
|
135
|
+
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, ...rest }) {
|
139
136
|
var _a;
|
140
137
|
const backendError = useErrorResponseForThisForm(fetcher, subaction);
|
141
138
|
const [fieldErrors, setFieldErrors] = useFieldErrors(backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors);
|
142
|
-
const isSubmitting = useIsSubmitting(
|
139
|
+
const [isSubmitting, startSubmit, endSubmit] = useIsSubmitting(fetcher);
|
143
140
|
const defaultsToUse = useDefaultValues(backendError === null || backendError === void 0 ? void 0 : backendError.repopulateFields, defaultValues);
|
144
141
|
const [touchedFields, setTouchedFields] = (0, react_2.useState)({});
|
145
142
|
const [hasBeenSubmitted, setHasBeenSubmitted] = (0, react_2.useState)(false);
|
@@ -147,6 +144,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
147
144
|
const formRef = (0, react_2.useRef)(null);
|
148
145
|
(0, submissionCallbacks_1.useSubmitComplete)(isSubmitting, () => {
|
149
146
|
var _a;
|
147
|
+
endSubmit();
|
150
148
|
if (!backendError && resetAfterSubmit) {
|
151
149
|
(_a = formRef.current) === null || _a === void 0 ? void 0 : _a.reset();
|
152
150
|
}
|
@@ -156,7 +154,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
156
154
|
fieldErrors,
|
157
155
|
action,
|
158
156
|
defaultValues: defaultsToUse,
|
159
|
-
isSubmitting
|
157
|
+
isSubmitting,
|
160
158
|
isValid: Object.keys(fieldErrors).length === 0,
|
161
159
|
touchedFields,
|
162
160
|
setFieldTouched: (fieldName, touched) => setTouchedFields((prev) => ({
|
@@ -230,11 +228,13 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
230
228
|
window.removeEventListener("click", handleClick);
|
231
229
|
};
|
232
230
|
}, []);
|
233
|
-
return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, action: action, onSubmit: async (e) => {
|
231
|
+
return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, action: action, method: method, replace: replace, onSubmit: async (e) => {
|
234
232
|
e.preventDefault();
|
235
233
|
setHasBeenSubmitted(true);
|
234
|
+
startSubmit();
|
236
235
|
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
237
236
|
if (result.error) {
|
237
|
+
endSubmit();
|
238
238
|
setFieldErrors(result.error.fieldErrors);
|
239
239
|
if (!disableFocusOnError) {
|
240
240
|
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
@@ -245,7 +245,10 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
245
245
|
if (fetcher)
|
246
246
|
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
247
247
|
else
|
248
|
-
submit(clickedButtonRef.current || e.currentTarget
|
248
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
249
|
+
method,
|
250
|
+
replace,
|
251
|
+
});
|
249
252
|
clickedButtonRef.current = null;
|
250
253
|
}
|
251
254
|
}, onReset: (event) => {
|
package/build/hooks.d.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import { GetInputProps, ValidationBehaviorOptions } from "./internal/getInputProps";
|
2
|
-
import { ValidationState } from "./types";
|
3
2
|
export declare type FieldProps = {
|
4
3
|
/**
|
5
4
|
* The validation error message if there is one.
|
@@ -13,14 +12,6 @@ export declare type FieldProps = {
|
|
13
12
|
* Validates the field.
|
14
13
|
*/
|
15
14
|
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;
|
24
15
|
/**
|
25
16
|
* The default value of the field, if there is one.
|
26
17
|
*/
|
package/build/hooks.js
CHANGED
@@ -22,37 +22,24 @@ const useField = (name, options) => {
|
|
22
22
|
const { fieldErrors, clearError, validateField, defaultValues, registerReceiveFocus, touchedFields, setFieldTouched, hasBeenSubmitted, } = useInternalFormContext("useField");
|
23
23
|
const isTouched = !!touchedFields[name];
|
24
24
|
const { handleReceiveFocus } = options !== null && options !== void 0 ? options : {};
|
25
|
-
const [isValidating, setValidating] = (0, react_1.useState)(false);
|
26
25
|
(0, react_1.useEffect)(() => {
|
27
26
|
if (handleReceiveFocus)
|
28
27
|
return registerReceiveFocus(name, handleReceiveFocus);
|
29
28
|
}, [handleReceiveFocus, name, registerReceiveFocus]);
|
30
29
|
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
|
-
};
|
41
30
|
const helpers = {
|
42
|
-
error,
|
31
|
+
error: fieldErrors[name],
|
43
32
|
clearError: () => {
|
44
33
|
clearError(name);
|
45
34
|
},
|
46
35
|
validate: () => {
|
47
|
-
|
48
|
-
validateField(name).then((error) => setValidating(false));
|
36
|
+
validateField(name);
|
49
37
|
},
|
50
38
|
defaultValue: defaultValues
|
51
39
|
? (0, get_1.default)(defaultValues, (0, toPath_1.default)(name), undefined)
|
52
40
|
: undefined,
|
53
41
|
touched: isTouched,
|
54
42
|
setTouched: (touched) => setFieldTouched(name, touched),
|
55
|
-
validationState: getValidationState(),
|
56
43
|
};
|
57
44
|
const getInputProps = (0, getInputProps_1.createGetInputProps)({
|
58
45
|
...helpers,
|
@@ -71,7 +58,6 @@ const useField = (name, options) => {
|
|
71
58
|
isTouched,
|
72
59
|
hasBeenSubmitted,
|
73
60
|
options === null || options === void 0 ? void 0 : options.validationBehavior,
|
74
|
-
isValidating,
|
75
61
|
clearError,
|
76
62
|
validateField,
|
77
63
|
setFieldTouched,
|
package/build/server.d.ts
CHANGED
@@ -4,7 +4,7 @@ import { ValidatorError } from "./validation/types";
|
|
4
4
|
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
5
5
|
* display the errors on the correct fields on the correct form.
|
6
6
|
*
|
7
|
-
*
|
7
|
+
* You can also provide a second argument to `validationError`
|
8
8
|
* to specify how to repopulate the form when JS is disabled.
|
9
9
|
*
|
10
10
|
* @example
|
package/build/server.js
CHANGED
@@ -7,7 +7,7 @@ const server_runtime_1 = require("@remix-run/server-runtime");
|
|
7
7
|
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
8
8
|
* display the errors on the correct fields on the correct form.
|
9
9
|
*
|
10
|
-
*
|
10
|
+
* You can also provide a second argument to `validationError`
|
11
11
|
* to specify how to repopulate the form when JS is disabled.
|
12
12
|
*
|
13
13
|
* @example
|
package/package.json
CHANGED
package/src/ValidatedForm.tsx
CHANGED
@@ -6,6 +6,7 @@ import {
|
|
6
6
|
useSubmit,
|
7
7
|
useTransition,
|
8
8
|
} from "@remix-run/react";
|
9
|
+
import { Fetcher } from "@remix-run/react/transition";
|
9
10
|
import uniq from "lodash/uniq";
|
10
11
|
import React, {
|
11
12
|
ComponentProps,
|
@@ -106,22 +107,19 @@ function useFieldErrors(
|
|
106
107
|
}
|
107
108
|
|
108
109
|
const useIsSubmitting = (
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
const
|
114
|
-
|
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;
|
115
118
|
|
116
|
-
|
117
|
-
|
119
|
+
const startSubmit = () => setSubmitStarted(true);
|
120
|
+
const endSubmit = () => setSubmitStarted(false);
|
118
121
|
|
119
|
-
|
120
|
-
const pendingSubAction = formData.get("subaction");
|
121
|
-
const expectedAction = action ?? actionForCurrentPage;
|
122
|
-
if (subaction)
|
123
|
-
return expectedAction === pendingAction && subaction === pendingSubAction;
|
124
|
-
return expectedAction === pendingAction && !pendingSubAction;
|
122
|
+
return [isSubmitting, startSubmit, endSubmit];
|
125
123
|
};
|
126
124
|
|
127
125
|
const getDataFromForm = (el: HTMLFormElement) => new FormData(el);
|
@@ -218,13 +216,16 @@ export function ValidatedForm<DataType>({
|
|
218
216
|
subaction,
|
219
217
|
resetAfterSubmit,
|
220
218
|
disableFocusOnError,
|
219
|
+
method,
|
220
|
+
replace,
|
221
221
|
...rest
|
222
222
|
}: FormProps<DataType>) {
|
223
223
|
const backendError = useErrorResponseForThisForm(fetcher, subaction);
|
224
224
|
const [fieldErrors, setFieldErrors] = useFieldErrors(
|
225
225
|
backendError?.fieldErrors
|
226
226
|
);
|
227
|
-
const isSubmitting = useIsSubmitting(
|
227
|
+
const [isSubmitting, startSubmit, endSubmit] = useIsSubmitting(fetcher);
|
228
|
+
|
228
229
|
const defaultsToUse = useDefaultValues(
|
229
230
|
backendError?.repopulateFields,
|
230
231
|
defaultValues
|
@@ -234,6 +235,7 @@ export function ValidatedForm<DataType>({
|
|
234
235
|
const submit = useSubmit();
|
235
236
|
const formRef = useRef<HTMLFormElement>(null);
|
236
237
|
useSubmitComplete(isSubmitting, () => {
|
238
|
+
endSubmit();
|
237
239
|
if (!backendError && resetAfterSubmit) {
|
238
240
|
formRef.current?.reset();
|
239
241
|
}
|
@@ -245,7 +247,7 @@ export function ValidatedForm<DataType>({
|
|
245
247
|
fieldErrors,
|
246
248
|
action,
|
247
249
|
defaultValues: defaultsToUse,
|
248
|
-
isSubmitting
|
250
|
+
isSubmitting,
|
249
251
|
isValid: Object.keys(fieldErrors).length === 0,
|
250
252
|
touchedFields,
|
251
253
|
setFieldTouched: (fieldName: string, touched: boolean) =>
|
@@ -336,13 +338,17 @@ export function ValidatedForm<DataType>({
|
|
336
338
|
ref={mergeRefs([formRef, formRefProp])}
|
337
339
|
{...rest}
|
338
340
|
action={action}
|
341
|
+
method={method}
|
342
|
+
replace={replace}
|
339
343
|
onSubmit={async (e) => {
|
340
344
|
e.preventDefault();
|
341
345
|
setHasBeenSubmitted(true);
|
346
|
+
startSubmit();
|
342
347
|
const result = await validator.validate(
|
343
348
|
getDataFromForm(e.currentTarget)
|
344
349
|
);
|
345
350
|
if (result.error) {
|
351
|
+
endSubmit();
|
346
352
|
setFieldErrors(result.error.fieldErrors);
|
347
353
|
if (!disableFocusOnError) {
|
348
354
|
focusFirstInvalidInput(
|
@@ -355,7 +361,11 @@ export function ValidatedForm<DataType>({
|
|
355
361
|
onSubmit && onSubmit(result.data, e);
|
356
362
|
if (fetcher)
|
357
363
|
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
358
|
-
else
|
364
|
+
else
|
365
|
+
submit(clickedButtonRef.current || e.currentTarget, {
|
366
|
+
method,
|
367
|
+
replace,
|
368
|
+
});
|
359
369
|
clickedButtonRef.current = null;
|
360
370
|
}
|
361
371
|
}}
|
package/src/hooks.ts
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
import get from "lodash/get";
|
2
2
|
import toPath from "lodash/toPath";
|
3
|
-
import { useContext, useEffect, useMemo
|
3
|
+
import { useContext, useEffect, useMemo } from "react";
|
4
4
|
import { FormContext } from "./internal/formContext";
|
5
5
|
import {
|
6
6
|
createGetInputProps,
|
7
7
|
GetInputProps,
|
8
8
|
ValidationBehaviorOptions,
|
9
9
|
} from "./internal/getInputProps";
|
10
|
-
import { ValidationState } from "./types";
|
11
10
|
|
12
11
|
const useInternalFormContext = (hookName: string) => {
|
13
12
|
const context = useContext(FormContext);
|
@@ -31,14 +30,6 @@ export type FieldProps = {
|
|
31
30
|
* Validates the field.
|
32
31
|
*/
|
33
32
|
validate: () => void;
|
34
|
-
/**
|
35
|
-
* The validation state of the field.
|
36
|
-
* - idle: the field has not been validated yet.
|
37
|
-
* - validating: the field is currently being validated.
|
38
|
-
* - valid: the field is valid.
|
39
|
-
* - invalid: the field is invalid.
|
40
|
-
*/
|
41
|
-
validationState: ValidationState;
|
42
33
|
/**
|
43
34
|
* The default value of the field, if there is one.
|
44
35
|
*/
|
@@ -88,7 +79,6 @@ export const useField = (
|
|
88
79
|
|
89
80
|
const isTouched = !!touchedFields[name];
|
90
81
|
const { handleReceiveFocus } = options ?? {};
|
91
|
-
const [isValidating, setValidating] = useState(false);
|
92
82
|
|
93
83
|
useEffect(() => {
|
94
84
|
if (handleReceiveFocus)
|
@@ -96,28 +86,19 @@ export const useField = (
|
|
96
86
|
}, [handleReceiveFocus, name, registerReceiveFocus]);
|
97
87
|
|
98
88
|
const field = useMemo<FieldProps>(() => {
|
99
|
-
const error = fieldErrors[name];
|
100
|
-
const getValidationState = (): ValidationState => {
|
101
|
-
if (isValidating) return "validating";
|
102
|
-
if (error) return "invalid";
|
103
|
-
if (!isTouched && !hasBeenSubmitted) return "idle";
|
104
|
-
return "valid";
|
105
|
-
};
|
106
89
|
const helpers = {
|
107
|
-
error,
|
90
|
+
error: fieldErrors[name],
|
108
91
|
clearError: () => {
|
109
92
|
clearError(name);
|
110
93
|
},
|
111
94
|
validate: () => {
|
112
|
-
|
113
|
-
validateField(name).then((error) => setValidating(false));
|
95
|
+
validateField(name);
|
114
96
|
},
|
115
97
|
defaultValue: defaultValues
|
116
98
|
? get(defaultValues, toPath(name), undefined)
|
117
99
|
: undefined,
|
118
100
|
touched: isTouched,
|
119
101
|
setTouched: (touched: boolean) => setFieldTouched(name, touched),
|
120
|
-
validationState: getValidationState(),
|
121
102
|
};
|
122
103
|
const getInputProps = createGetInputProps({
|
123
104
|
...helpers,
|
@@ -136,7 +117,6 @@ export const useField = (
|
|
136
117
|
isTouched,
|
137
118
|
hasBeenSubmitted,
|
138
119
|
options?.validationBehavior,
|
139
|
-
isValidating,
|
140
120
|
clearError,
|
141
121
|
validateField,
|
142
122
|
setFieldTouched,
|
package/src/server.ts
CHANGED
@@ -9,7 +9,7 @@ import {
|
|
9
9
|
* When you return this from your action, `ValidatedForm` on the frontend will automatically
|
10
10
|
* display the errors on the correct fields on the correct form.
|
11
11
|
*
|
12
|
-
*
|
12
|
+
* You can also provide a second argument to `validationError`
|
13
13
|
* to specify how to repopulate the form when JS is disabled.
|
14
14
|
*
|
15
15
|
* @example
|
package/src/types.ts
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
export type ValidationState = "idle" | "validating" | "valid" | "invalid";
|