remix-validated-form 4.0.2 → 4.1.0-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 +4 -4
- package/browser/ValidatedForm.d.ts +1 -1
- package/browser/ValidatedForm.js +99 -137
- package/browser/components.d.ts +5 -8
- package/browser/components.js +5 -5
- package/browser/hooks.d.ts +19 -14
- package/browser/hooks.js +36 -40
- package/browser/index.d.ts +1 -1
- package/browser/index.js +1 -0
- package/browser/internal/constants.d.ts +3 -0
- package/browser/internal/constants.js +3 -0
- package/browser/internal/formContext.d.ts +7 -49
- package/browser/internal/formContext.js +1 -1
- package/browser/internal/getInputProps.js +4 -3
- package/browser/internal/hooks.d.ts +23 -0
- package/browser/internal/hooks.js +114 -0
- package/browser/internal/state.d.ts +269 -0
- package/browser/internal/state.js +82 -0
- package/browser/internal/util.d.ts +1 -0
- package/browser/internal/util.js +2 -0
- package/browser/lowLevelHooks.d.ts +0 -0
- package/browser/lowLevelHooks.js +1 -0
- package/browser/server.d.ts +5 -0
- package/browser/server.js +5 -0
- package/browser/userFacingFormContext.d.ts +56 -0
- package/browser/userFacingFormContext.js +40 -0
- package/browser/validation/createValidator.js +4 -0
- package/browser/validation/types.d.ts +3 -0
- package/build/ValidatedForm.d.ts +1 -1
- package/build/ValidatedForm.js +95 -133
- package/build/hooks.d.ts +19 -14
- package/build/hooks.js +38 -46
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -0
- package/build/internal/constants.d.ts +3 -0
- package/build/internal/constants.js +7 -0
- package/build/internal/formContext.d.ts +7 -49
- package/build/internal/formContext.js +2 -2
- package/build/internal/getInputProps.js +7 -3
- package/build/internal/hooks.d.ts +23 -0
- package/build/internal/hooks.js +135 -0
- package/build/internal/state.d.ts +269 -0
- package/build/internal/state.js +92 -0
- package/build/internal/util.d.ts +1 -0
- package/build/internal/util.js +3 -1
- package/build/server.d.ts +5 -0
- package/build/server.js +7 -1
- package/build/userFacingFormContext.d.ts +56 -0
- package/build/userFacingFormContext.js +44 -0
- package/build/validation/createValidator.js +4 -0
- package/build/validation/types.d.ts +3 -0
- package/package.json +3 -1
- package/src/ValidatedForm.tsx +149 -180
- package/src/hooks.ts +69 -55
- package/src/index.ts +1 -1
- package/src/internal/constants.ts +4 -0
- package/src/internal/formContext.ts +8 -49
- package/src/internal/getInputProps.ts +6 -4
- package/src/internal/hooks.ts +200 -0
- package/src/internal/state.ts +210 -0
- package/src/internal/util.ts +4 -0
- package/src/server.ts +16 -0
- package/src/userFacingFormContext.ts +129 -0
- package/src/validation/createValidator.ts +4 -0
- package/src/validation/types.ts +3 -1
@@ -1,3 +1,4 @@
|
|
1
|
+
import { FORM_ID_FIELD } from "../internal/constants";
|
1
2
|
import { objectFromPathEntries } from "../internal/flatten";
|
2
3
|
const preprocessFormData = (data) => {
|
3
4
|
// A slightly janky way of determining if the data is a FormData object
|
@@ -22,14 +23,17 @@ export function createValidator(validator) {
|
|
22
23
|
error: {
|
23
24
|
fieldErrors: result.error,
|
24
25
|
subaction: data.subaction,
|
26
|
+
formId: data[FORM_ID_FIELD],
|
25
27
|
},
|
26
28
|
submittedData: data,
|
29
|
+
formId: data[FORM_ID_FIELD],
|
27
30
|
};
|
28
31
|
}
|
29
32
|
return {
|
30
33
|
data: result.data,
|
31
34
|
error: undefined,
|
32
35
|
submittedData: data,
|
36
|
+
formId: data[FORM_ID_FIELD],
|
33
37
|
};
|
34
38
|
},
|
35
39
|
validateField: (data, field) => validator.validateField(preprocessFormData(data), field),
|
@@ -5,15 +5,18 @@ export declare type GenericObject = {
|
|
5
5
|
};
|
6
6
|
export declare type ValidatorError = {
|
7
7
|
subaction?: string;
|
8
|
+
formId?: string;
|
8
9
|
fieldErrors: FieldErrors;
|
9
10
|
};
|
10
11
|
export declare type ValidationErrorResponseData = {
|
11
12
|
subaction?: string;
|
13
|
+
formId?: string;
|
12
14
|
fieldErrors: FieldErrors;
|
13
15
|
repopulateFields?: unknown;
|
14
16
|
};
|
15
17
|
export declare type BaseResult = {
|
16
18
|
submittedData: GenericObject;
|
19
|
+
formId?: string;
|
17
20
|
};
|
18
21
|
export declare type ErrorResult = BaseResult & {
|
19
22
|
error: ValidatorError;
|
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, method, replace, ...rest }: FormProps<DataType>): JSX.Element;
|
50
|
+
export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues: providedDefaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit, disableFocusOnError, method, replace, id, ...rest }: FormProps<DataType>): JSX.Element;
|
package/build/ValidatedForm.js
CHANGED
@@ -28,66 +28,15 @@ const react_1 = require("@remix-run/react");
|
|
28
28
|
const uniq_1 = __importDefault(require("lodash/uniq"));
|
29
29
|
const react_2 = __importStar(require("react"));
|
30
30
|
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
|
31
|
+
const hooks_1 = require("./hooks");
|
32
|
+
const constants_1 = require("./internal/constants");
|
31
33
|
const formContext_1 = require("./internal/formContext");
|
34
|
+
const hooks_2 = require("./internal/hooks");
|
32
35
|
const MultiValueMap_1 = require("./internal/MultiValueMap");
|
36
|
+
const state_1 = require("./internal/state");
|
33
37
|
const submissionCallbacks_1 = require("./internal/submissionCallbacks");
|
34
38
|
const util_1 = require("./internal/util");
|
35
|
-
function useErrorResponseForThisForm(fetcher, subaction) {
|
36
|
-
var _a;
|
37
|
-
const actionData = (0, react_1.useActionData)();
|
38
|
-
if (fetcher) {
|
39
|
-
if ((_a = fetcher.data) === null || _a === void 0 ? void 0 : _a.fieldErrors)
|
40
|
-
return fetcher.data;
|
41
|
-
return null;
|
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;
|
48
|
-
return null;
|
49
|
-
}
|
50
|
-
function useFieldErrors(fieldErrorsFromBackend) {
|
51
|
-
const [fieldErrors, setFieldErrors] = (0, react_2.useState)(fieldErrorsFromBackend !== null && fieldErrorsFromBackend !== void 0 ? fieldErrorsFromBackend : {});
|
52
|
-
(0, react_2.useEffect)(() => {
|
53
|
-
if (fieldErrorsFromBackend)
|
54
|
-
setFieldErrors(fieldErrorsFromBackend);
|
55
|
-
}, [fieldErrorsFromBackend]);
|
56
|
-
return [fieldErrors, setFieldErrors];
|
57
|
-
}
|
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 startSubmit = () => setSubmitStarted(true);
|
65
|
-
const endSubmit = () => setSubmitStarted(false);
|
66
|
-
(0, submissionCallbacks_1.useSubmitComplete)(hasActiveSubmission, () => {
|
67
|
-
endSubmit();
|
68
|
-
});
|
69
|
-
return [isSubmitStarted, startSubmit, endSubmit];
|
70
|
-
};
|
71
39
|
const getDataFromForm = (el) => new FormData(el);
|
72
|
-
/**
|
73
|
-
* The purpose for this logic is to handle validation errors when javascript is disabled.
|
74
|
-
* Normally (without js), when a form is submitted and the action returns the validation errors,
|
75
|
-
* the form will be reset. The errors will be displayed on the correct fields,
|
76
|
-
* but all the values in the form will be gone. This is not good UX.
|
77
|
-
*
|
78
|
-
* To get around this, we return the submitted form data from the server,
|
79
|
-
* and use those to populate the form via `defaultValues`.
|
80
|
-
* This results in a more seamless UX akin to what you would see when js is enabled.
|
81
|
-
*
|
82
|
-
* One potential downside is that resetting the form will reset the form
|
83
|
-
* to the _new_ default values that were returned from the server with the validation errors.
|
84
|
-
* However, this case is less of a problem than the janky UX caused by losing the form values.
|
85
|
-
* It will only ever be a problem if the form includes a `<button type="reset" />`
|
86
|
-
* and only if JS is disabled.
|
87
|
-
*/
|
88
|
-
function useDefaultValues(repopulateFieldsFromBackend, defaultValues) {
|
89
|
-
return repopulateFieldsFromBackend !== null && repopulateFieldsFromBackend !== void 0 ? repopulateFieldsFromBackend : defaultValues;
|
90
|
-
}
|
91
40
|
function nonNull(value) {
|
92
41
|
return value !== null;
|
93
42
|
}
|
@@ -131,85 +80,101 @@ const focusFirstInvalidInput = (fieldErrors, customFocusHandlers, formElement) =
|
|
131
80
|
}
|
132
81
|
}
|
133
82
|
};
|
83
|
+
const useFormId = (providedId) => {
|
84
|
+
// We can use a `Symbol` here because we only use it after hydration
|
85
|
+
const [symbolId] = (0, react_2.useState)(() => Symbol("remix-validated-form-id"));
|
86
|
+
return providedId !== null && providedId !== void 0 ? providedId : symbolId;
|
87
|
+
};
|
134
88
|
/**
|
135
|
-
*
|
89
|
+
* Use a component to access the state so we don't cause
|
90
|
+
* any extra rerenders of the whole form.
|
136
91
|
*/
|
137
|
-
|
138
|
-
|
139
|
-
const
|
140
|
-
const [fieldErrors, setFieldErrors] = useFieldErrors(backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors);
|
141
|
-
const [isSubmitting, startSubmit, endSubmit] = useIsSubmitting(fetcher);
|
142
|
-
const defaultsToUse = useDefaultValues(backendError === null || backendError === void 0 ? void 0 : backendError.repopulateFields, defaultValues);
|
143
|
-
const [touchedFields, setTouchedFields] = (0, react_2.useState)({});
|
144
|
-
const [hasBeenSubmitted, setHasBeenSubmitted] = (0, react_2.useState)(false);
|
145
|
-
const submit = (0, react_1.useSubmit)();
|
146
|
-
const formRef = (0, react_2.useRef)(null);
|
92
|
+
const FormResetter = ({ resetAfterSubmit, formRef, }) => {
|
93
|
+
const isSubmitting = (0, hooks_1.useIsSubmitting)();
|
94
|
+
const isValid = (0, hooks_1.useIsValid)();
|
147
95
|
(0, submissionCallbacks_1.useSubmitComplete)(isSubmitting, () => {
|
148
96
|
var _a;
|
149
|
-
|
150
|
-
if (!backendError && resetAfterSubmit) {
|
97
|
+
if (isValid && resetAfterSubmit) {
|
151
98
|
(_a = formRef.current) === null || _a === void 0 ? void 0 : _a.reset();
|
152
99
|
}
|
153
100
|
});
|
154
|
-
|
101
|
+
return null;
|
102
|
+
};
|
103
|
+
/**
|
104
|
+
* The primary form component of `remix-validated-form`.
|
105
|
+
*/
|
106
|
+
function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: providedDefaultValues, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
|
107
|
+
var _a;
|
108
|
+
const formId = useFormId(id);
|
109
|
+
const formAtom = (0, state_1.formRegistry)(formId);
|
155
110
|
const contextValue = (0, react_2.useMemo)(() => ({
|
156
|
-
|
111
|
+
formId,
|
157
112
|
action,
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
113
|
+
subaction,
|
114
|
+
defaultValuesProp: providedDefaultValues,
|
115
|
+
fetcher,
|
116
|
+
}), [action, fetcher, formId, providedDefaultValues, subaction]);
|
117
|
+
const backendError = (0, hooks_2.useErrorResponseForForm)(contextValue);
|
118
|
+
const backendDefaultValues = (0, hooks_2.useDefaultValuesFromLoader)(contextValue);
|
119
|
+
const hasActiveSubmission = (0, hooks_2.useHasActiveFormSubmit)(contextValue);
|
120
|
+
const formRef = (0, react_2.useRef)(null);
|
121
|
+
const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : react_1.Form;
|
122
|
+
const submit = (0, react_1.useSubmit)();
|
123
|
+
const clearError = (0, hooks_2.useFormUpdateAtom)(state_1.clearErrorAtom);
|
124
|
+
const addError = (0, hooks_2.useFormUpdateAtom)(state_1.addErrorAtom);
|
125
|
+
const setFieldErrors = (0, hooks_2.useFormUpdateAtom)(state_1.setFieldErrorsAtom);
|
126
|
+
const reset = (0, hooks_2.useFormUpdateAtom)(state_1.resetAtom);
|
127
|
+
const startSubmit = (0, hooks_2.useFormUpdateAtom)(state_1.startSubmitAtom);
|
128
|
+
const endSubmit = (0, hooks_2.useFormUpdateAtom)(state_1.endSubmitAtom);
|
129
|
+
const syncFormContext = (0, hooks_2.useFormUpdateAtom)(state_1.syncFormContextAtom);
|
130
|
+
const validateField = (0, react_2.useCallback)(async (fieldName) => {
|
131
|
+
(0, tiny_invariant_1.default)(formRef.current, "Cannot find reference to form");
|
132
|
+
const { error } = await validator.validateField(getDataFromForm(formRef.current), fieldName);
|
133
|
+
if (error) {
|
134
|
+
addError({ formAtom, name: fieldName, error });
|
135
|
+
return error;
|
136
|
+
}
|
137
|
+
else {
|
138
|
+
clearError({ name: fieldName, formAtom });
|
139
|
+
return null;
|
140
|
+
}
|
141
|
+
}, [addError, clearError, formAtom, validator]);
|
142
|
+
const customFocusHandlers = (0, MultiValueMap_1.useMultiValueMap)();
|
143
|
+
const registerReceiveFocus = (0, react_2.useCallback)((fieldName, handler) => {
|
144
|
+
customFocusHandlers().add(fieldName, handler);
|
145
|
+
return () => {
|
146
|
+
customFocusHandlers().remove(fieldName, handler);
|
147
|
+
};
|
148
|
+
}, [customFocusHandlers]);
|
149
|
+
(0, util_1.useIsomorphicLayoutEffect)(() => {
|
150
|
+
syncFormContext({
|
151
|
+
formAtom,
|
152
|
+
action,
|
153
|
+
defaultValues: providedDefaultValues !== null && providedDefaultValues !== void 0 ? providedDefaultValues : backendDefaultValues,
|
154
|
+
subaction,
|
155
|
+
validateField,
|
156
|
+
registerReceiveFocus,
|
157
|
+
});
|
158
|
+
}, [
|
203
159
|
action,
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
160
|
+
formAtom,
|
161
|
+
providedDefaultValues,
|
162
|
+
registerReceiveFocus,
|
163
|
+
subaction,
|
164
|
+
syncFormContext,
|
165
|
+
validateField,
|
166
|
+
backendDefaultValues,
|
211
167
|
]);
|
212
|
-
|
168
|
+
(0, react_2.useEffect)(() => {
|
169
|
+
var _a;
|
170
|
+
setFieldErrors({
|
171
|
+
fieldErrors: (_a = backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors) !== null && _a !== void 0 ? _a : {},
|
172
|
+
formAtom,
|
173
|
+
});
|
174
|
+
}, [backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors, formAtom, setFieldErrors]);
|
175
|
+
(0, submissionCallbacks_1.useSubmitComplete)(hasActiveSubmission, () => {
|
176
|
+
endSubmit({ formAtom });
|
177
|
+
});
|
213
178
|
let clickedButtonRef = react_2.default.useRef();
|
214
179
|
(0, react_2.useEffect)(() => {
|
215
180
|
let form = formRef.current;
|
@@ -230,20 +195,19 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
230
195
|
window.removeEventListener("click", handleClick);
|
231
196
|
};
|
232
197
|
}, []);
|
233
|
-
return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, action: action, method: method, replace: replace, onSubmit: async (e) => {
|
198
|
+
return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: async (e) => {
|
234
199
|
e.preventDefault();
|
235
|
-
|
236
|
-
startSubmit();
|
200
|
+
startSubmit({ formAtom });
|
237
201
|
const result = await validator.validate(getDataFromForm(e.currentTarget));
|
238
202
|
if (result.error) {
|
239
|
-
endSubmit();
|
240
|
-
setFieldErrors(result.error.fieldErrors);
|
203
|
+
endSubmit({ formAtom });
|
204
|
+
setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
|
241
205
|
if (!disableFocusOnError) {
|
242
206
|
focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
|
243
207
|
}
|
244
208
|
}
|
245
209
|
else {
|
246
|
-
onSubmit
|
210
|
+
onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, e);
|
247
211
|
if (fetcher)
|
248
212
|
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
249
213
|
else
|
@@ -257,9 +221,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
257
221
|
onReset === null || onReset === void 0 ? void 0 : onReset(event);
|
258
222
|
if (event.defaultPrevented)
|
259
223
|
return;
|
260
|
-
|
261
|
-
|
262
|
-
setHasBeenSubmitted(false);
|
263
|
-
}, children: (0, jsx_runtime_1.jsxs)(formContext_1.FormContext.Provider, { value: contextValue, children: [subaction && ((0, jsx_runtime_1.jsx)("input", { type: "hidden", value: subaction, name: "subaction" }, void 0)), children] }, void 0) }, void 0));
|
224
|
+
reset({ formAtom });
|
225
|
+
}, children: (0, jsx_runtime_1.jsxs)(formContext_1.InternalFormContext.Provider, { value: contextValue, children: [(0, jsx_runtime_1.jsx)(FormResetter, { formRef: formRef, resetAfterSubmit: resetAfterSubmit }, void 0), subaction && ((0, jsx_runtime_1.jsx)("input", { type: "hidden", value: subaction, name: "subaction" }, void 0)), id && (0, jsx_runtime_1.jsx)("input", { type: "hidden", value: id, name: constants_1.FORM_ID_FIELD }, void 0), children] }, void 0) }, void 0));
|
264
226
|
}
|
265
227
|
exports.ValidatedForm = ValidatedForm;
|
package/build/hooks.d.ts
CHANGED
@@ -1,4 +1,18 @@
|
|
1
1
|
import { GetInputProps, ValidationBehaviorOptions } from "./internal/getInputProps";
|
2
|
+
/**
|
3
|
+
* Returns whether or not the parent form is currently being submitted.
|
4
|
+
* This is different from remix's `useTransition().submission` in that it
|
5
|
+
* is aware of what form it's in and when _that_ form is being submitted.
|
6
|
+
*
|
7
|
+
* @param formId
|
8
|
+
*/
|
9
|
+
export declare const useIsSubmitting: (formId?: string | undefined) => boolean;
|
10
|
+
/**
|
11
|
+
* Returns whether or not the current form is valid.
|
12
|
+
*
|
13
|
+
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
|
14
|
+
*/
|
15
|
+
export declare const useIsValid: (formId?: string | undefined) => boolean;
|
2
16
|
export declare type FieldProps = {
|
3
17
|
/**
|
4
18
|
* The validation error message if there is one.
|
@@ -43,18 +57,9 @@ export declare const useField: (name: string, options?: {
|
|
43
57
|
* Allows you to specify when a field gets validated (when using getInputProps)
|
44
58
|
*/
|
45
59
|
validationBehavior?: Partial<ValidationBehaviorOptions> | undefined;
|
60
|
+
/**
|
61
|
+
* The formId of the form you want to use.
|
62
|
+
* This is not necesary if the input is used inside a form.
|
63
|
+
*/
|
64
|
+
formId?: string | undefined;
|
46
65
|
} | undefined) => FieldProps;
|
47
|
-
/**
|
48
|
-
* Provides access to the entire form context.
|
49
|
-
*/
|
50
|
-
export declare const useFormContext: () => import("./internal/formContext").FormContextValue;
|
51
|
-
/**
|
52
|
-
* Returns whether or not the parent form is currently being submitted.
|
53
|
-
* This is different from remix's `useTransition().submission` in that it
|
54
|
-
* is aware of what form it's in and when _that_ form is being submitted.
|
55
|
-
*/
|
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/build/hooks.js
CHANGED
@@ -1,45 +1,54 @@
|
|
1
1
|
"use strict";
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
-
};
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.
|
7
|
-
const get_1 = __importDefault(require("lodash/get"));
|
8
|
-
const toPath_1 = __importDefault(require("lodash/toPath"));
|
3
|
+
exports.useField = exports.useIsValid = exports.useIsSubmitting = void 0;
|
9
4
|
const react_1 = require("react");
|
10
|
-
const formContext_1 = require("./internal/formContext");
|
11
5
|
const getInputProps_1 = require("./internal/getInputProps");
|
12
|
-
const
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
6
|
+
const hooks_1 = require("./internal/hooks");
|
7
|
+
const state_1 = require("./internal/state");
|
8
|
+
/**
|
9
|
+
* Returns whether or not the parent form is currently being submitted.
|
10
|
+
* This is different from remix's `useTransition().submission` in that it
|
11
|
+
* is aware of what form it's in and when _that_ form is being submitted.
|
12
|
+
*
|
13
|
+
* @param formId
|
14
|
+
*/
|
15
|
+
const useIsSubmitting = (formId) => (0, hooks_1.useUnknownFormContextSelectAtom)(formId, state_1.isSubmittingAtom, "useIsSubmitting");
|
16
|
+
exports.useIsSubmitting = useIsSubmitting;
|
17
|
+
/**
|
18
|
+
* Returns whether or not the current form is valid.
|
19
|
+
*
|
20
|
+
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
|
21
|
+
*/
|
22
|
+
const useIsValid = (formId) => (0, hooks_1.useUnknownFormContextSelectAtom)(formId, state_1.isValidAtom, "useIsValid");
|
23
|
+
exports.useIsValid = useIsValid;
|
18
24
|
/**
|
19
25
|
* Provides the data and helpers necessary to set up a field.
|
20
26
|
*/
|
21
27
|
const useField = (name, options) => {
|
22
|
-
const {
|
23
|
-
const
|
24
|
-
const
|
28
|
+
const { handleReceiveFocus, formId: providedFormId } = options !== null && options !== void 0 ? options : {};
|
29
|
+
const formContext = (0, hooks_1.useInternalFormContext)(providedFormId, "useField");
|
30
|
+
const defaultValue = (0, hooks_1.useFieldDefaultValue)(name, formContext);
|
31
|
+
const touched = (0, hooks_1.useFieldTouched)(name, formContext);
|
32
|
+
const error = (0, hooks_1.useFieldError)(name, formContext);
|
33
|
+
const clearError = (0, hooks_1.useClearError)(formContext);
|
34
|
+
const setTouched = (0, hooks_1.useSetTouched)(formContext);
|
35
|
+
const hasBeenSubmitted = (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.hasBeenSubmittedAtom);
|
36
|
+
const validateField = (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.validateFieldAtom);
|
37
|
+
const registerReceiveFocus = (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.registerReceiveFocusAtom);
|
25
38
|
(0, react_1.useEffect)(() => {
|
26
39
|
if (handleReceiveFocus)
|
27
40
|
return registerReceiveFocus(name, handleReceiveFocus);
|
28
41
|
}, [handleReceiveFocus, name, registerReceiveFocus]);
|
29
42
|
const field = (0, react_1.useMemo)(() => {
|
30
43
|
const helpers = {
|
31
|
-
error
|
32
|
-
clearError: () =>
|
33
|
-
clearError(name);
|
34
|
-
},
|
44
|
+
error,
|
45
|
+
clearError: () => clearError(name),
|
35
46
|
validate: () => {
|
36
47
|
validateField(name);
|
37
48
|
},
|
38
|
-
defaultValue
|
39
|
-
|
40
|
-
|
41
|
-
touched: isTouched,
|
42
|
-
setTouched: (touched) => setFieldTouched(name, touched),
|
49
|
+
defaultValue,
|
50
|
+
touched,
|
51
|
+
setTouched: (touched) => setTouched(name, touched),
|
43
52
|
};
|
44
53
|
const getInputProps = (0, getInputProps_1.createGetInputProps)({
|
45
54
|
...helpers,
|
@@ -52,33 +61,16 @@ const useField = (name, options) => {
|
|
52
61
|
getInputProps,
|
53
62
|
};
|
54
63
|
}, [
|
55
|
-
|
64
|
+
error,
|
65
|
+
defaultValue,
|
66
|
+
touched,
|
56
67
|
name,
|
57
|
-
defaultValues,
|
58
|
-
isTouched,
|
59
68
|
hasBeenSubmitted,
|
60
69
|
options === null || options === void 0 ? void 0 : options.validationBehavior,
|
61
70
|
clearError,
|
62
71
|
validateField,
|
63
|
-
|
72
|
+
setTouched,
|
64
73
|
]);
|
65
74
|
return field;
|
66
75
|
};
|
67
76
|
exports.useField = useField;
|
68
|
-
/**
|
69
|
-
* Provides access to the entire form context.
|
70
|
-
*/
|
71
|
-
const useFormContext = () => useInternalFormContext("useFormContext");
|
72
|
-
exports.useFormContext = useFormContext;
|
73
|
-
/**
|
74
|
-
* Returns whether or not the parent form is currently being submitted.
|
75
|
-
* This is different from remix's `useTransition().submission` in that it
|
76
|
-
* is aware of what form it's in and when _that_ form is being submitted.
|
77
|
-
*/
|
78
|
-
const useIsSubmitting = () => useInternalFormContext("useIsSubmitting").isSubmitting;
|
79
|
-
exports.useIsSubmitting = useIsSubmitting;
|
80
|
-
/**
|
81
|
-
* Returns whether or not the current form is valid.
|
82
|
-
*/
|
83
|
-
const useIsValid = () => useInternalFormContext("useIsValid").isValid;
|
84
|
-
exports.useIsValid = useIsValid;
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
@@ -15,3 +15,4 @@ __exportStar(require("./server"), exports);
|
|
15
15
|
__exportStar(require("./ValidatedForm"), exports);
|
16
16
|
__exportStar(require("./validation/types"), exports);
|
17
17
|
__exportStar(require("./validation/createValidator"), exports);
|
18
|
+
__exportStar(require("./userFacingFormContext"), exports);
|
@@ -0,0 +1,7 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.formDefaultValuesKey = exports.FORM_DEFAULTS_FIELD = exports.FORM_ID_FIELD = void 0;
|
4
|
+
exports.FORM_ID_FIELD = "__rvfInternalFormId";
|
5
|
+
exports.FORM_DEFAULTS_FIELD = "__rvfInternalFormDefaults";
|
6
|
+
const formDefaultValuesKey = (formId) => `${exports.FORM_DEFAULTS_FIELD}_${formId}`;
|
7
|
+
exports.formDefaultValuesKey = formDefaultValuesKey;
|
@@ -1,54 +1,12 @@
|
|
1
1
|
/// <reference types="react" />
|
2
|
-
import {
|
3
|
-
export declare type
|
4
|
-
|
5
|
-
* All the errors in all the fields in the form.
|
6
|
-
*/
|
7
|
-
fieldErrors: FieldErrors;
|
8
|
-
/**
|
9
|
-
* Clear the errors of the specified fields.
|
10
|
-
*/
|
11
|
-
clearError: (...names: string[]) => void;
|
12
|
-
/**
|
13
|
-
* Validate the specified field.
|
14
|
-
*/
|
15
|
-
validateField: (fieldName: string) => Promise<string | null>;
|
16
|
-
/**
|
17
|
-
* The `action` prop of the form.
|
18
|
-
*/
|
2
|
+
import { useFetcher } from "@remix-run/react";
|
3
|
+
export declare type InternalFormContextValue = {
|
4
|
+
formId: string | symbol;
|
19
5
|
action?: string;
|
20
|
-
|
21
|
-
|
22
|
-
*/
|
23
|
-
isSubmitting: boolean;
|
24
|
-
/**
|
25
|
-
* Whether or not a submission has been attempted.
|
26
|
-
* This is true once the form has been submitted, even if there were validation errors.
|
27
|
-
* Resets to false when the form is reset.
|
28
|
-
*/
|
29
|
-
hasBeenSubmitted: boolean;
|
30
|
-
/**
|
31
|
-
* Whether or not the form is valid.
|
32
|
-
*/
|
33
|
-
isValid: boolean;
|
34
|
-
/**
|
35
|
-
* The default values of the form.
|
36
|
-
*/
|
37
|
-
defaultValues?: {
|
6
|
+
subaction?: string;
|
7
|
+
defaultValuesProp?: {
|
38
8
|
[fieldName: string]: any;
|
39
9
|
};
|
40
|
-
|
41
|
-
* Register a custom focus handler to be used when
|
42
|
-
* the field needs to receive focus due to a validation error.
|
43
|
-
*/
|
44
|
-
registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
|
45
|
-
/**
|
46
|
-
* Any fields that have been touched by the user.
|
47
|
-
*/
|
48
|
-
touchedFields: TouchedFields;
|
49
|
-
/**
|
50
|
-
* Change the touched state of the specified field.
|
51
|
-
*/
|
52
|
-
setFieldTouched: (fieldName: string, touched: boolean) => void;
|
10
|
+
fetcher?: ReturnType<typeof useFetcher>;
|
53
11
|
};
|
54
|
-
export declare const
|
12
|
+
export declare const InternalFormContext: import("react").Context<InternalFormContextValue | null>;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.
|
3
|
+
exports.InternalFormContext = void 0;
|
4
4
|
const react_1 = require("react");
|
5
|
-
exports.
|
5
|
+
exports.InternalFormContext = (0, react_1.createContext)(null);
|
@@ -1,6 +1,10 @@
|
|
1
1
|
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
6
|
exports.createGetInputProps = void 0;
|
7
|
+
const omitBy_1 = __importDefault(require("lodash/omitBy"));
|
4
8
|
const defaultValidationBehavior = {
|
5
9
|
initial: "onBlur",
|
6
10
|
whenTouched: "onChange",
|
@@ -46,18 +50,18 @@ const createGetInputProps = ({ clearError, validate, defaultValue, touched, setT
|
|
46
50
|
},
|
47
51
|
name,
|
48
52
|
};
|
49
|
-
if (
|
53
|
+
if (props.type === "checkbox") {
|
50
54
|
const value = (_a = props.value) !== null && _a !== void 0 ? _a : "on";
|
51
55
|
inputProps.defaultChecked = getCheckboxDefaultChecked(value, defaultValue);
|
52
56
|
}
|
53
|
-
else if (
|
57
|
+
else if (props.type === "radio") {
|
54
58
|
const value = (_b = props.value) !== null && _b !== void 0 ? _b : "on";
|
55
59
|
inputProps.defaultChecked = defaultValue === value;
|
56
60
|
}
|
57
61
|
else {
|
58
62
|
inputProps.defaultValue = defaultValue;
|
59
63
|
}
|
60
|
-
return inputProps;
|
64
|
+
return (0, omitBy_1.default)(inputProps, (value) => value === undefined);
|
61
65
|
};
|
62
66
|
};
|
63
67
|
exports.createGetInputProps = createGetInputProps;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Atom } from "jotai";
|
2
|
+
import { useUpdateAtom } from "jotai/utils";
|
3
|
+
import { ValidationErrorResponseData } from "..";
|
4
|
+
import { InternalFormContextValue } from "./formContext";
|
5
|
+
import { FormAtom } from "./state";
|
6
|
+
declare type FormSelectorAtomCreator<T> = (formState: FormAtom) => Atom<T>;
|
7
|
+
declare const USE_HYDRATED_STATE: unique symbol;
|
8
|
+
export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
|
9
|
+
export declare const useContextSelectAtom: <T>(formId: string | symbol, selectorAtomCreator: FormSelectorAtomCreator<T>) => T extends Promise<infer V> ? V : T;
|
10
|
+
export declare const useUnknownFormContextSelectAtom: <T>(formId: string | symbol | undefined, selectorAtomCreator: FormSelectorAtomCreator<T>, hookName: string) => T extends Promise<infer V> ? V : T;
|
11
|
+
export declare const useHydratableSelector: <T, U>({ formId }: InternalFormContextValue, atomCreator: FormSelectorAtomCreator<T>, dataToUse: U | typeof USE_HYDRATED_STATE, selector?: (data: U) => T) => T | (T extends Promise<infer V> ? V : T);
|
12
|
+
export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
|
13
|
+
export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => import("..").FieldErrors | typeof USE_HYDRATED_STATE | undefined;
|
14
|
+
export declare const useDefaultValuesFromLoader: ({ formId, }: InternalFormContextValue) => any;
|
15
|
+
export declare const useDefaultValuesForForm: (context: InternalFormContextValue) => any;
|
16
|
+
export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
|
17
|
+
export declare const useFieldTouched: (name: string, { formId }: InternalFormContextValue) => boolean;
|
18
|
+
export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
|
19
|
+
export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
|
20
|
+
export declare const useFormUpdateAtom: typeof useUpdateAtom;
|
21
|
+
export declare const useClearError: (context: InternalFormContextValue) => (name: string) => void;
|
22
|
+
export declare const useSetTouched: (context: InternalFormContextValue) => (name: string, touched: boolean) => void;
|
23
|
+
export {};
|