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,6 +1,7 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.createValidator = void 0;
|
4
|
+
const constants_1 = require("../internal/constants");
|
4
5
|
const flatten_1 = require("../internal/flatten");
|
5
6
|
const preprocessFormData = (data) => {
|
6
7
|
// A slightly janky way of determining if the data is a FormData object
|
@@ -25,14 +26,17 @@ function createValidator(validator) {
|
|
25
26
|
error: {
|
26
27
|
fieldErrors: result.error,
|
27
28
|
subaction: data.subaction,
|
29
|
+
formId: data[constants_1.FORM_ID_FIELD],
|
28
30
|
},
|
29
31
|
submittedData: data,
|
32
|
+
formId: data[constants_1.FORM_ID_FIELD],
|
30
33
|
};
|
31
34
|
}
|
32
35
|
return {
|
33
36
|
data: result.data,
|
34
37
|
error: undefined,
|
35
38
|
submittedData: data,
|
39
|
+
formId: data[constants_1.FORM_ID_FIELD],
|
36
40
|
};
|
37
41
|
},
|
38
42
|
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/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "remix-validated-form",
|
3
|
-
"version": "4.0.
|
3
|
+
"version": "4.1.0-beta.1",
|
4
4
|
"description": "Form component and utils for easy form validation in remix",
|
5
5
|
"browser": "./browser/index.js",
|
6
6
|
"main": "./build/index.js",
|
@@ -48,6 +48,8 @@
|
|
48
48
|
"typescript": "^4.5.3"
|
49
49
|
},
|
50
50
|
"dependencies": {
|
51
|
+
"immer": "^9.0.12",
|
52
|
+
"jotai": "^1.5.3",
|
51
53
|
"lodash": "^4.17.21",
|
52
54
|
"tiny-invariant": "^1.2.0"
|
53
55
|
}
|
package/src/ValidatedForm.tsx
CHANGED
@@ -1,31 +1,45 @@
|
|
1
|
-
import {
|
2
|
-
Form as RemixForm,
|
3
|
-
useActionData,
|
4
|
-
useFetcher,
|
5
|
-
useFormAction,
|
6
|
-
useSubmit,
|
7
|
-
useTransition,
|
8
|
-
} from "@remix-run/react";
|
9
|
-
import { Fetcher } from "@remix-run/react/transition";
|
1
|
+
import { Form as RemixForm, useFetcher, useSubmit } from "@remix-run/react";
|
10
2
|
import uniq from "lodash/uniq";
|
11
3
|
import React, {
|
12
4
|
ComponentProps,
|
5
|
+
RefObject,
|
6
|
+
useCallback,
|
13
7
|
useEffect,
|
14
8
|
useMemo,
|
15
9
|
useRef,
|
16
10
|
useState,
|
17
11
|
} from "react";
|
18
12
|
import invariant from "tiny-invariant";
|
19
|
-
import {
|
13
|
+
import { useIsSubmitting, useIsValid } from "./hooks";
|
14
|
+
import { FORM_ID_FIELD } from "./internal/constants";
|
15
|
+
import {
|
16
|
+
InternalFormContext,
|
17
|
+
InternalFormContextValue,
|
18
|
+
} from "./internal/formContext";
|
19
|
+
import {
|
20
|
+
useDefaultValuesFromLoader,
|
21
|
+
useErrorResponseForForm,
|
22
|
+
useFormUpdateAtom,
|
23
|
+
useHasActiveFormSubmit,
|
24
|
+
} from "./internal/hooks";
|
20
25
|
import { MultiValueMap, useMultiValueMap } from "./internal/MultiValueMap";
|
26
|
+
import {
|
27
|
+
addErrorAtom,
|
28
|
+
clearErrorAtom,
|
29
|
+
endSubmitAtom,
|
30
|
+
formRegistry,
|
31
|
+
FormState,
|
32
|
+
resetAtom,
|
33
|
+
setFieldErrorsAtom,
|
34
|
+
startSubmitAtom,
|
35
|
+
syncFormContextAtom,
|
36
|
+
} from "./internal/state";
|
21
37
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
22
|
-
import { omit, mergeRefs } from "./internal/util";
|
23
38
|
import {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
} from "./validation/types";
|
39
|
+
mergeRefs,
|
40
|
+
useIsomorphicLayoutEffect as useLayoutEffect,
|
41
|
+
} from "./internal/util";
|
42
|
+
import { FieldErrors, Validator } from "./validation/types";
|
29
43
|
|
30
44
|
export type FormProps<DataType> = {
|
31
45
|
/**
|
@@ -74,82 +88,8 @@ export type FormProps<DataType> = {
|
|
74
88
|
disableFocusOnError?: boolean;
|
75
89
|
} & Omit<ComponentProps<typeof RemixForm>, "onSubmit">;
|
76
90
|
|
77
|
-
function useErrorResponseForThisForm(
|
78
|
-
fetcher?: ReturnType<typeof useFetcher>,
|
79
|
-
subaction?: string
|
80
|
-
): ValidationErrorResponseData | null {
|
81
|
-
const actionData = useActionData<any>();
|
82
|
-
if (fetcher) {
|
83
|
-
if ((fetcher.data as any)?.fieldErrors) return fetcher.data as any;
|
84
|
-
return null;
|
85
|
-
}
|
86
|
-
|
87
|
-
if (!actionData?.fieldErrors) return null;
|
88
|
-
if (
|
89
|
-
(!subaction && !actionData.subaction) ||
|
90
|
-
actionData.subaction === subaction
|
91
|
-
)
|
92
|
-
return actionData;
|
93
|
-
return null;
|
94
|
-
}
|
95
|
-
|
96
|
-
function useFieldErrors(
|
97
|
-
fieldErrorsFromBackend?: FieldErrors
|
98
|
-
): [FieldErrors, React.Dispatch<React.SetStateAction<FieldErrors>>] {
|
99
|
-
const [fieldErrors, setFieldErrors] = useState<FieldErrors>(
|
100
|
-
fieldErrorsFromBackend ?? {}
|
101
|
-
);
|
102
|
-
useEffect(() => {
|
103
|
-
if (fieldErrorsFromBackend) setFieldErrors(fieldErrorsFromBackend);
|
104
|
-
}, [fieldErrorsFromBackend]);
|
105
|
-
|
106
|
-
return [fieldErrors, setFieldErrors];
|
107
|
-
}
|
108
|
-
|
109
|
-
const useIsSubmitting = (
|
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
|
-
|
118
|
-
const startSubmit = () => setSubmitStarted(true);
|
119
|
-
const endSubmit = () => setSubmitStarted(false);
|
120
|
-
|
121
|
-
useSubmitComplete(hasActiveSubmission, () => {
|
122
|
-
endSubmit();
|
123
|
-
});
|
124
|
-
|
125
|
-
return [isSubmitStarted, startSubmit, endSubmit];
|
126
|
-
};
|
127
|
-
|
128
91
|
const getDataFromForm = (el: HTMLFormElement) => new FormData(el);
|
129
92
|
|
130
|
-
/**
|
131
|
-
* The purpose for this logic is to handle validation errors when javascript is disabled.
|
132
|
-
* Normally (without js), when a form is submitted and the action returns the validation errors,
|
133
|
-
* the form will be reset. The errors will be displayed on the correct fields,
|
134
|
-
* but all the values in the form will be gone. This is not good UX.
|
135
|
-
*
|
136
|
-
* To get around this, we return the submitted form data from the server,
|
137
|
-
* and use those to populate the form via `defaultValues`.
|
138
|
-
* This results in a more seamless UX akin to what you would see when js is enabled.
|
139
|
-
*
|
140
|
-
* One potential downside is that resetting the form will reset the form
|
141
|
-
* to the _new_ default values that were returned from the server with the validation errors.
|
142
|
-
* However, this case is less of a problem than the janky UX caused by losing the form values.
|
143
|
-
* It will only ever be a problem if the form includes a `<button type="reset" />`
|
144
|
-
* and only if JS is disabled.
|
145
|
-
*/
|
146
|
-
function useDefaultValues<DataType>(
|
147
|
-
repopulateFieldsFromBackend?: any,
|
148
|
-
defaultValues?: Partial<DataType>
|
149
|
-
) {
|
150
|
-
return repopulateFieldsFromBackend ?? defaultValues;
|
151
|
-
}
|
152
|
-
|
153
93
|
function nonNull<T>(value: T | null | undefined): value is T {
|
154
94
|
return value !== null;
|
155
95
|
}
|
@@ -204,6 +144,33 @@ const focusFirstInvalidInput = (
|
|
204
144
|
}
|
205
145
|
};
|
206
146
|
|
147
|
+
const useFormId = (providedId?: string): string | symbol => {
|
148
|
+
// We can use a `Symbol` here because we only use it after hydration
|
149
|
+
const [symbolId] = useState(() => Symbol("remix-validated-form-id"));
|
150
|
+
return providedId ?? symbolId;
|
151
|
+
};
|
152
|
+
|
153
|
+
/**
|
154
|
+
* Use a component to access the state so we don't cause
|
155
|
+
* any extra rerenders of the whole form.
|
156
|
+
*/
|
157
|
+
const FormResetter = ({
|
158
|
+
resetAfterSubmit,
|
159
|
+
formRef,
|
160
|
+
}: {
|
161
|
+
resetAfterSubmit: boolean;
|
162
|
+
formRef: RefObject<HTMLFormElement>;
|
163
|
+
}) => {
|
164
|
+
const isSubmitting = useIsSubmitting();
|
165
|
+
const isValid = useIsValid();
|
166
|
+
useSubmitComplete(isSubmitting, () => {
|
167
|
+
if (isValid && resetAfterSubmit) {
|
168
|
+
formRef.current?.reset();
|
169
|
+
}
|
170
|
+
});
|
171
|
+
return null;
|
172
|
+
};
|
173
|
+
|
207
174
|
/**
|
208
175
|
* The primary form component of `remix-validated-form`.
|
209
176
|
*/
|
@@ -213,102 +180,104 @@ export function ValidatedForm<DataType>({
|
|
213
180
|
children,
|
214
181
|
fetcher,
|
215
182
|
action,
|
216
|
-
defaultValues,
|
183
|
+
defaultValues: providedDefaultValues,
|
217
184
|
formRef: formRefProp,
|
218
185
|
onReset,
|
219
186
|
subaction,
|
220
|
-
resetAfterSubmit,
|
187
|
+
resetAfterSubmit = false,
|
221
188
|
disableFocusOnError,
|
222
189
|
method,
|
223
190
|
replace,
|
191
|
+
id,
|
224
192
|
...rest
|
225
193
|
}: FormProps<DataType>) {
|
226
|
-
const
|
227
|
-
const
|
228
|
-
|
194
|
+
const formId = useFormId(id);
|
195
|
+
const formAtom = formRegistry(formId);
|
196
|
+
const contextValue = useMemo<InternalFormContextValue>(
|
197
|
+
() => ({
|
198
|
+
formId,
|
199
|
+
action,
|
200
|
+
subaction,
|
201
|
+
defaultValuesProp: providedDefaultValues,
|
202
|
+
fetcher,
|
203
|
+
}),
|
204
|
+
[action, fetcher, formId, providedDefaultValues, subaction]
|
229
205
|
);
|
230
|
-
const
|
206
|
+
const backendError = useErrorResponseForForm(contextValue);
|
207
|
+
const backendDefaultValues = useDefaultValuesFromLoader(contextValue);
|
208
|
+
const hasActiveSubmission = useHasActiveFormSubmit(contextValue);
|
209
|
+
const formRef = useRef<HTMLFormElement>(null);
|
210
|
+
const Form = fetcher?.Form ?? RemixForm;
|
231
211
|
|
232
|
-
const defaultsToUse = useDefaultValues(
|
233
|
-
backendError?.repopulateFields,
|
234
|
-
defaultValues
|
235
|
-
);
|
236
|
-
const [touchedFields, setTouchedFields] = useState<TouchedFields>({});
|
237
|
-
const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
|
238
212
|
const submit = useSubmit();
|
239
|
-
const
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
213
|
+
const clearError = useFormUpdateAtom(clearErrorAtom);
|
214
|
+
const addError = useFormUpdateAtom(addErrorAtom);
|
215
|
+
const setFieldErrors = useFormUpdateAtom(setFieldErrorsAtom);
|
216
|
+
const reset = useFormUpdateAtom(resetAtom);
|
217
|
+
const startSubmit = useFormUpdateAtom(startSubmitAtom);
|
218
|
+
const endSubmit = useFormUpdateAtom(endSubmitAtom);
|
219
|
+
const syncFormContext = useFormUpdateAtom(syncFormContextAtom);
|
220
|
+
|
221
|
+
const validateField: FormState["validateField"] = useCallback(
|
222
|
+
async (fieldName) => {
|
223
|
+
invariant(formRef.current, "Cannot find reference to form");
|
224
|
+
const { error } = await validator.validateField(
|
225
|
+
getDataFromForm(formRef.current),
|
226
|
+
fieldName as any
|
227
|
+
);
|
228
|
+
|
229
|
+
if (error) {
|
230
|
+
addError({ formAtom, name: fieldName, error });
|
231
|
+
return error;
|
232
|
+
} else {
|
233
|
+
clearError({ name: fieldName, formAtom });
|
234
|
+
return null;
|
235
|
+
}
|
236
|
+
},
|
237
|
+
[addError, clearError, formAtom, validator]
|
238
|
+
);
|
239
|
+
|
246
240
|
const customFocusHandlers = useMultiValueMap<string, () => void>();
|
241
|
+
const registerReceiveFocus: FormState["registerReceiveFocus"] = useCallback(
|
242
|
+
(fieldName, handler) => {
|
243
|
+
customFocusHandlers().add(fieldName, handler);
|
244
|
+
return () => {
|
245
|
+
customFocusHandlers().remove(fieldName, handler);
|
246
|
+
};
|
247
|
+
},
|
248
|
+
[customFocusHandlers]
|
249
|
+
);
|
247
250
|
|
248
|
-
|
249
|
-
(
|
250
|
-
|
251
|
+
useLayoutEffect(() => {
|
252
|
+
syncFormContext({
|
253
|
+
formAtom,
|
251
254
|
action,
|
252
|
-
defaultValues:
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
getDataFromForm(formRef.current),
|
268
|
-
fieldName as any
|
269
|
-
);
|
255
|
+
defaultValues: providedDefaultValues ?? backendDefaultValues,
|
256
|
+
subaction,
|
257
|
+
validateField,
|
258
|
+
registerReceiveFocus,
|
259
|
+
});
|
260
|
+
}, [
|
261
|
+
action,
|
262
|
+
formAtom,
|
263
|
+
providedDefaultValues,
|
264
|
+
registerReceiveFocus,
|
265
|
+
subaction,
|
266
|
+
syncFormContext,
|
267
|
+
validateField,
|
268
|
+
backendDefaultValues,
|
269
|
+
]);
|
270
270
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
...prev,
|
278
|
-
[fieldName]: error,
|
279
|
-
};
|
280
|
-
});
|
281
|
-
return error;
|
282
|
-
} else {
|
283
|
-
setFieldErrors((prev) => {
|
284
|
-
if (!(fieldName in prev)) return prev;
|
285
|
-
return omit(prev, fieldName);
|
286
|
-
});
|
287
|
-
return null;
|
288
|
-
}
|
289
|
-
},
|
290
|
-
registerReceiveFocus: (fieldName, handler) => {
|
291
|
-
customFocusHandlers().add(fieldName, handler);
|
292
|
-
return () => {
|
293
|
-
customFocusHandlers().remove(fieldName, handler);
|
294
|
-
};
|
295
|
-
},
|
296
|
-
hasBeenSubmitted,
|
297
|
-
}),
|
298
|
-
[
|
299
|
-
fieldErrors,
|
300
|
-
action,
|
301
|
-
defaultsToUse,
|
302
|
-
isSubmitting,
|
303
|
-
touchedFields,
|
304
|
-
hasBeenSubmitted,
|
305
|
-
setFieldErrors,
|
306
|
-
validator,
|
307
|
-
customFocusHandlers,
|
308
|
-
]
|
309
|
-
);
|
271
|
+
useEffect(() => {
|
272
|
+
setFieldErrors({
|
273
|
+
fieldErrors: backendError?.fieldErrors ?? {},
|
274
|
+
formAtom,
|
275
|
+
});
|
276
|
+
}, [backendError?.fieldErrors, formAtom, setFieldErrors]);
|
310
277
|
|
311
|
-
|
278
|
+
useSubmitComplete(hasActiveSubmission, () => {
|
279
|
+
endSubmit({ formAtom });
|
280
|
+
});
|
312
281
|
|
313
282
|
let clickedButtonRef = React.useRef<any>();
|
314
283
|
useEffect(() => {
|
@@ -340,19 +309,19 @@ export function ValidatedForm<DataType>({
|
|
340
309
|
<Form
|
341
310
|
ref={mergeRefs([formRef, formRefProp])}
|
342
311
|
{...rest}
|
312
|
+
id={id}
|
343
313
|
action={action}
|
344
314
|
method={method}
|
345
315
|
replace={replace}
|
346
316
|
onSubmit={async (e) => {
|
347
317
|
e.preventDefault();
|
348
|
-
|
349
|
-
startSubmit();
|
318
|
+
startSubmit({ formAtom });
|
350
319
|
const result = await validator.validate(
|
351
320
|
getDataFromForm(e.currentTarget)
|
352
321
|
);
|
353
322
|
if (result.error) {
|
354
|
-
endSubmit();
|
355
|
-
setFieldErrors(result.error.fieldErrors);
|
323
|
+
endSubmit({ formAtom });
|
324
|
+
setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
|
356
325
|
if (!disableFocusOnError) {
|
357
326
|
focusFirstInvalidInput(
|
358
327
|
result.error.fieldErrors,
|
@@ -361,7 +330,7 @@ export function ValidatedForm<DataType>({
|
|
361
330
|
);
|
362
331
|
}
|
363
332
|
} else {
|
364
|
-
onSubmit
|
333
|
+
onSubmit?.(result.data, e);
|
365
334
|
if (fetcher)
|
366
335
|
fetcher.submit(clickedButtonRef.current || e.currentTarget);
|
367
336
|
else
|
@@ -375,17 +344,17 @@ export function ValidatedForm<DataType>({
|
|
375
344
|
onReset={(event) => {
|
376
345
|
onReset?.(event);
|
377
346
|
if (event.defaultPrevented) return;
|
378
|
-
|
379
|
-
setTouchedFields({});
|
380
|
-
setHasBeenSubmitted(false);
|
347
|
+
reset({ formAtom });
|
381
348
|
}}
|
382
349
|
>
|
383
|
-
<
|
350
|
+
<InternalFormContext.Provider value={contextValue}>
|
351
|
+
<FormResetter formRef={formRef} resetAfterSubmit={resetAfterSubmit} />
|
384
352
|
{subaction && (
|
385
353
|
<input type="hidden" value={subaction} name="subaction" />
|
386
354
|
)}
|
355
|
+
{id && <input type="hidden" value={id} name={FORM_ID_FIELD} />}
|
387
356
|
{children}
|
388
|
-
</
|
357
|
+
</InternalFormContext.Provider>
|
389
358
|
</Form>
|
390
359
|
);
|
391
360
|
}
|
package/src/hooks.ts
CHANGED
@@ -1,21 +1,44 @@
|
|
1
|
-
import
|
2
|
-
import toPath from "lodash/toPath";
|
3
|
-
import { useContext, useEffect, useMemo } from "react";
|
4
|
-
import { FormContext } from "./internal/formContext";
|
1
|
+
import { useEffect, useMemo } from "react";
|
5
2
|
import {
|
6
3
|
createGetInputProps,
|
7
4
|
GetInputProps,
|
8
5
|
ValidationBehaviorOptions,
|
9
6
|
} from "./internal/getInputProps";
|
7
|
+
import {
|
8
|
+
useUnknownFormContextSelectAtom,
|
9
|
+
useInternalFormContext,
|
10
|
+
useFieldTouched,
|
11
|
+
useFieldError,
|
12
|
+
useFieldDefaultValue,
|
13
|
+
useContextSelectAtom,
|
14
|
+
useClearError,
|
15
|
+
useSetTouched,
|
16
|
+
} from "./internal/hooks";
|
17
|
+
import {
|
18
|
+
hasBeenSubmittedAtom,
|
19
|
+
isSubmittingAtom,
|
20
|
+
isValidAtom,
|
21
|
+
registerReceiveFocusAtom,
|
22
|
+
validateFieldAtom,
|
23
|
+
} from "./internal/state";
|
10
24
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
25
|
+
/**
|
26
|
+
* Returns whether or not the parent form is currently being submitted.
|
27
|
+
* This is different from remix's `useTransition().submission` in that it
|
28
|
+
* is aware of what form it's in and when _that_ form is being submitted.
|
29
|
+
*
|
30
|
+
* @param formId
|
31
|
+
*/
|
32
|
+
export const useIsSubmitting = (formId?: string) =>
|
33
|
+
useUnknownFormContextSelectAtom(formId, isSubmittingAtom, "useIsSubmitting");
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Returns whether or not the current form is valid.
|
37
|
+
*
|
38
|
+
* @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
|
39
|
+
*/
|
40
|
+
export const useIsValid = (formId?: string) =>
|
41
|
+
useUnknownFormContextSelectAtom(formId, isValidAtom, "useIsValid");
|
19
42
|
|
20
43
|
export type FieldProps = {
|
21
44
|
/**
|
@@ -64,21 +87,34 @@ export const useField = (
|
|
64
87
|
* Allows you to specify when a field gets validated (when using getInputProps)
|
65
88
|
*/
|
66
89
|
validationBehavior?: Partial<ValidationBehaviorOptions>;
|
90
|
+
/**
|
91
|
+
* The formId of the form you want to use.
|
92
|
+
* This is not necesary if the input is used inside a form.
|
93
|
+
*/
|
94
|
+
formId?: string;
|
67
95
|
}
|
68
96
|
): FieldProps => {
|
69
|
-
const {
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
touchedFields,
|
76
|
-
setFieldTouched,
|
77
|
-
hasBeenSubmitted,
|
78
|
-
} = useInternalFormContext("useField");
|
97
|
+
const { handleReceiveFocus, formId: providedFormId } = options ?? {};
|
98
|
+
const formContext = useInternalFormContext(providedFormId, "useField");
|
99
|
+
|
100
|
+
const defaultValue = useFieldDefaultValue(name, formContext);
|
101
|
+
const touched = useFieldTouched(name, formContext);
|
102
|
+
const error = useFieldError(name, formContext);
|
79
103
|
|
80
|
-
const
|
81
|
-
const
|
104
|
+
const clearError = useClearError(formContext);
|
105
|
+
const setTouched = useSetTouched(formContext);
|
106
|
+
const hasBeenSubmitted = useContextSelectAtom(
|
107
|
+
formContext.formId,
|
108
|
+
hasBeenSubmittedAtom
|
109
|
+
);
|
110
|
+
const validateField = useContextSelectAtom(
|
111
|
+
formContext.formId,
|
112
|
+
validateFieldAtom
|
113
|
+
);
|
114
|
+
const registerReceiveFocus = useContextSelectAtom(
|
115
|
+
formContext.formId,
|
116
|
+
registerReceiveFocusAtom
|
117
|
+
);
|
82
118
|
|
83
119
|
useEffect(() => {
|
84
120
|
if (handleReceiveFocus)
|
@@ -87,18 +123,14 @@ export const useField = (
|
|
87
123
|
|
88
124
|
const field = useMemo<FieldProps>(() => {
|
89
125
|
const helpers = {
|
90
|
-
error
|
91
|
-
clearError: () =>
|
92
|
-
clearError(name);
|
93
|
-
},
|
126
|
+
error,
|
127
|
+
clearError: () => clearError(name),
|
94
128
|
validate: () => {
|
95
129
|
validateField(name);
|
96
130
|
},
|
97
|
-
defaultValue
|
98
|
-
|
99
|
-
|
100
|
-
touched: isTouched,
|
101
|
-
setTouched: (touched: boolean) => setFieldTouched(name, touched),
|
131
|
+
defaultValue,
|
132
|
+
touched,
|
133
|
+
setTouched: (touched: boolean) => setTouched(name, touched),
|
102
134
|
};
|
103
135
|
const getInputProps = createGetInputProps({
|
104
136
|
...helpers,
|
@@ -111,34 +143,16 @@ export const useField = (
|
|
111
143
|
getInputProps,
|
112
144
|
};
|
113
145
|
}, [
|
114
|
-
|
146
|
+
error,
|
147
|
+
defaultValue,
|
148
|
+
touched,
|
115
149
|
name,
|
116
|
-
defaultValues,
|
117
|
-
isTouched,
|
118
150
|
hasBeenSubmitted,
|
119
151
|
options?.validationBehavior,
|
120
152
|
clearError,
|
121
153
|
validateField,
|
122
|
-
|
154
|
+
setTouched,
|
123
155
|
]);
|
124
156
|
|
125
157
|
return field;
|
126
158
|
};
|
127
|
-
|
128
|
-
/**
|
129
|
-
* Provides access to the entire form context.
|
130
|
-
*/
|
131
|
-
export const useFormContext = () => useInternalFormContext("useFormContext");
|
132
|
-
|
133
|
-
/**
|
134
|
-
* Returns whether or not the parent form is currently being submitted.
|
135
|
-
* This is different from remix's `useTransition().submission` in that it
|
136
|
-
* is aware of what form it's in and when _that_ form is being submitted.
|
137
|
-
*/
|
138
|
-
export const useIsSubmitting = () =>
|
139
|
-
useInternalFormContext("useIsSubmitting").isSubmitting;
|
140
|
-
|
141
|
-
/**
|
142
|
-
* Returns whether or not the current form is valid.
|
143
|
-
*/
|
144
|
-
export const useIsValid = () => useInternalFormContext("useIsValid").isValid;
|
package/src/index.ts
CHANGED