remix-validated-form 4.0.2 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/README.md +4 -4
  3. package/browser/ValidatedForm.d.ts +2 -2
  4. package/browser/ValidatedForm.js +142 -151
  5. package/browser/components.d.ts +5 -8
  6. package/browser/components.js +5 -5
  7. package/browser/hooks.d.ts +19 -14
  8. package/browser/hooks.js +41 -39
  9. package/browser/index.d.ts +1 -1
  10. package/browser/index.js +1 -0
  11. package/browser/internal/constants.d.ts +3 -0
  12. package/browser/internal/constants.js +3 -0
  13. package/browser/internal/formContext.d.ts +7 -49
  14. package/browser/internal/formContext.js +1 -1
  15. package/browser/internal/getInputProps.js +4 -3
  16. package/browser/internal/hooks.d.ts +22 -0
  17. package/browser/internal/hooks.js +110 -0
  18. package/browser/internal/state.d.ts +269 -0
  19. package/browser/internal/state.js +82 -0
  20. package/browser/internal/util.d.ts +1 -0
  21. package/browser/internal/util.js +2 -0
  22. package/browser/lowLevelHooks.d.ts +0 -0
  23. package/browser/lowLevelHooks.js +1 -0
  24. package/browser/server.d.ts +5 -0
  25. package/browser/server.js +5 -0
  26. package/browser/userFacingFormContext.d.ts +56 -0
  27. package/browser/userFacingFormContext.js +40 -0
  28. package/browser/validation/createValidator.js +4 -0
  29. package/browser/validation/types.d.ts +3 -0
  30. package/build/ValidatedForm.d.ts +2 -2
  31. package/build/ValidatedForm.js +138 -147
  32. package/build/hooks.d.ts +19 -14
  33. package/build/hooks.js +43 -45
  34. package/build/index.d.ts +1 -1
  35. package/build/index.js +1 -0
  36. package/build/internal/constants.d.ts +3 -0
  37. package/build/internal/constants.js +7 -0
  38. package/build/internal/formContext.d.ts +7 -49
  39. package/build/internal/formContext.js +2 -2
  40. package/build/internal/getInputProps.js +7 -3
  41. package/build/internal/hooks.d.ts +22 -0
  42. package/build/internal/hooks.js +130 -0
  43. package/build/internal/state.d.ts +269 -0
  44. package/build/internal/state.js +92 -0
  45. package/build/internal/util.d.ts +1 -0
  46. package/build/internal/util.js +3 -1
  47. package/build/server.d.ts +5 -0
  48. package/build/server.js +7 -1
  49. package/build/userFacingFormContext.d.ts +56 -0
  50. package/build/userFacingFormContext.js +44 -0
  51. package/build/validation/createValidator.js +4 -0
  52. package/build/validation/types.d.ts +3 -0
  53. package/package.json +3 -1
  54. package/src/ValidatedForm.tsx +205 -203
  55. package/src/hooks.ts +71 -54
  56. package/src/index.ts +1 -1
  57. package/src/internal/constants.ts +4 -0
  58. package/src/internal/formContext.ts +8 -49
  59. package/src/internal/getInputProps.ts +6 -4
  60. package/src/internal/hooks.ts +191 -0
  61. package/src/internal/state.ts +210 -0
  62. package/src/internal/util.ts +4 -0
  63. package/src/server.ts +16 -0
  64. package/src/userFacingFormContext.ts +129 -0
  65. package/src/validation/createValidator.ts +4 -0
  66. package/src/validation/types.ts +3 -1
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useFormContext = void 0;
4
+ const react_1 = require("react");
5
+ const hooks_1 = require("./hooks");
6
+ const hooks_2 = require("./internal/hooks");
7
+ const state_1 = require("./internal/state");
8
+ /**
9
+ * Provides access to some of the internal state of the form.
10
+ */
11
+ const useFormContext = (formId) => {
12
+ // Try to access context so we get our error specific to this hook if it's not there
13
+ const context = (0, hooks_2.useInternalFormContext)(formId, "useFormContext");
14
+ const action = (0, hooks_2.useContextSelectAtom)(context.formId, state_1.actionAtom);
15
+ const isSubmitting = (0, hooks_1.useIsSubmitting)(formId);
16
+ const hasBeenSubmitted = (0, hooks_2.useContextSelectAtom)(context.formId, state_1.hasBeenSubmittedAtom);
17
+ const isValid = (0, hooks_1.useIsValid)(formId);
18
+ const defaultValues = (0, hooks_2.useHydratableSelector)(context, state_1.defaultValuesAtom, (0, hooks_2.useDefaultValuesForForm)(context));
19
+ const fieldErrors = (0, hooks_2.useHydratableSelector)(context, state_1.fieldErrorsAtom, (0, hooks_2.useFieldErrorsForForm)(context));
20
+ const setFieldTouched = (0, hooks_2.useSetTouched)(context);
21
+ const touchedFields = (0, hooks_2.useContextSelectAtom)(context.formId, state_1.touchedFieldsAtom);
22
+ const validateField = (0, hooks_2.useContextSelectAtom)(context.formId, state_1.validateFieldAtom);
23
+ const registerReceiveFocus = (0, hooks_2.useContextSelectAtom)(context.formId, state_1.registerReceiveFocusAtom);
24
+ const internalClearError = (0, hooks_2.useClearError)(context);
25
+ const clearError = (0, react_1.useCallback)((...names) => {
26
+ names.forEach((name) => {
27
+ internalClearError(name);
28
+ });
29
+ }, [internalClearError]);
30
+ return {
31
+ isSubmitting,
32
+ hasBeenSubmitted,
33
+ isValid,
34
+ defaultValues,
35
+ clearError,
36
+ fieldErrors: fieldErrors !== null && fieldErrors !== void 0 ? fieldErrors : {},
37
+ action,
38
+ setFieldTouched,
39
+ touchedFields,
40
+ validateField,
41
+ registerReceiveFocus,
42
+ };
43
+ };
44
+ exports.useFormContext = useFormContext;
@@ -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.2",
3
+ "version": "4.1.0",
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
  }
@@ -1,31 +1,46 @@
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
+ FormEvent,
6
+ RefObject,
7
+ useCallback,
13
8
  useEffect,
14
9
  useMemo,
15
10
  useRef,
16
11
  useState,
17
12
  } from "react";
18
13
  import invariant from "tiny-invariant";
19
- import { FormContext, FormContextValue } from "./internal/formContext";
14
+ import { useIsSubmitting, useIsValid } from "./hooks";
15
+ import { FORM_ID_FIELD } from "./internal/constants";
16
+ import {
17
+ InternalFormContext,
18
+ InternalFormContextValue,
19
+ } from "./internal/formContext";
20
+ import {
21
+ useDefaultValuesFromLoader,
22
+ useErrorResponseForForm,
23
+ useFormUpdateAtom,
24
+ useHasActiveFormSubmit,
25
+ } from "./internal/hooks";
20
26
  import { MultiValueMap, useMultiValueMap } from "./internal/MultiValueMap";
27
+ import {
28
+ addErrorAtom,
29
+ clearErrorAtom,
30
+ endSubmitAtom,
31
+ formRegistry,
32
+ FormState,
33
+ resetAtom,
34
+ setFieldErrorsAtom,
35
+ startSubmitAtom,
36
+ syncFormContextAtom,
37
+ } from "./internal/state";
21
38
  import { useSubmitComplete } from "./internal/submissionCallbacks";
22
- import { omit, mergeRefs } from "./internal/util";
23
39
  import {
24
- FieldErrors,
25
- Validator,
26
- TouchedFields,
27
- ValidationErrorResponseData,
28
- } from "./validation/types";
40
+ mergeRefs,
41
+ useIsomorphicLayoutEffect as useLayoutEffect,
42
+ } from "./internal/util";
43
+ import { FieldErrors, Validator } from "./validation/types";
29
44
 
30
45
  export type FormProps<DataType> = {
31
46
  /**
@@ -39,7 +54,7 @@ export type FormProps<DataType> = {
39
54
  onSubmit?: (
40
55
  data: DataType,
41
56
  event: React.FormEvent<HTMLFormElement>
42
- ) => Promise<void>;
57
+ ) => void | Promise<void>;
43
58
  /**
44
59
  * Allows you to provide a `fetcher` from remix's `useFetcher` hook.
45
60
  * The form will use the fetcher for loading states, action data, etc
@@ -74,82 +89,8 @@ export type FormProps<DataType> = {
74
89
  disableFocusOnError?: boolean;
75
90
  } & Omit<ComponentProps<typeof RemixForm>, "onSubmit">;
76
91
 
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
92
  const getDataFromForm = (el: HTMLFormElement) => new FormData(el);
129
93
 
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
94
  function nonNull<T>(value: T | null | undefined): value is T {
154
95
  return value !== null;
155
96
  }
@@ -204,6 +145,58 @@ const focusFirstInvalidInput = (
204
145
  }
205
146
  };
206
147
 
148
+ const useFormId = (providedId?: string): string | symbol => {
149
+ // We can use a `Symbol` here because we only use it after hydration
150
+ const [symbolId] = useState(() => Symbol("remix-validated-form-id"));
151
+ return providedId ?? symbolId;
152
+ };
153
+
154
+ /**
155
+ * Use a component to access the state so we don't cause
156
+ * any extra rerenders of the whole form.
157
+ */
158
+ const FormResetter = ({
159
+ resetAfterSubmit,
160
+ formRef,
161
+ }: {
162
+ resetAfterSubmit: boolean;
163
+ formRef: RefObject<HTMLFormElement>;
164
+ }) => {
165
+ const isSubmitting = useIsSubmitting();
166
+ const isValid = useIsValid();
167
+ useSubmitComplete(isSubmitting, () => {
168
+ if (isValid && resetAfterSubmit) {
169
+ formRef.current?.reset();
170
+ }
171
+ });
172
+ return null;
173
+ };
174
+
175
+ function formEventProxy<T extends object>(event: T): T {
176
+ let defaultPrevented = false;
177
+ return new Proxy(event, {
178
+ get: (target, prop) => {
179
+ if (prop === "preventDefault") {
180
+ return () => {
181
+ defaultPrevented = true;
182
+ };
183
+ }
184
+
185
+ if (prop === "defaultPrevented") {
186
+ return defaultPrevented;
187
+ }
188
+
189
+ return target[prop as keyof T];
190
+ },
191
+ }) as T;
192
+ }
193
+
194
+ const useFormAtom = (formId: string | symbol) => {
195
+ const formAtom = formRegistry(formId);
196
+ useEffect(() => () => formRegistry.remove(formId), [formId]);
197
+ return formAtom;
198
+ };
199
+
207
200
  /**
208
201
  * The primary form component of `remix-validated-form`.
209
202
  */
@@ -213,102 +206,104 @@ export function ValidatedForm<DataType>({
213
206
  children,
214
207
  fetcher,
215
208
  action,
216
- defaultValues,
209
+ defaultValues: providedDefaultValues,
217
210
  formRef: formRefProp,
218
211
  onReset,
219
212
  subaction,
220
- resetAfterSubmit,
213
+ resetAfterSubmit = false,
221
214
  disableFocusOnError,
222
215
  method,
223
216
  replace,
217
+ id,
224
218
  ...rest
225
219
  }: FormProps<DataType>) {
226
- const backendError = useErrorResponseForThisForm(fetcher, subaction);
227
- const [fieldErrors, setFieldErrors] = useFieldErrors(
228
- backendError?.fieldErrors
220
+ const formId = useFormId(id);
221
+ const formAtom = useFormAtom(formId);
222
+ const contextValue = useMemo<InternalFormContextValue>(
223
+ () => ({
224
+ formId,
225
+ action,
226
+ subaction,
227
+ defaultValuesProp: providedDefaultValues,
228
+ fetcher,
229
+ }),
230
+ [action, fetcher, formId, providedDefaultValues, subaction]
229
231
  );
230
- const [isSubmitting, startSubmit, endSubmit] = useIsSubmitting(fetcher);
232
+ const backendError = useErrorResponseForForm(contextValue);
233
+ const backendDefaultValues = useDefaultValuesFromLoader(contextValue);
234
+ const hasActiveSubmission = useHasActiveFormSubmit(contextValue);
235
+ const formRef = useRef<HTMLFormElement>(null);
236
+ const Form = fetcher?.Form ?? RemixForm;
231
237
 
232
- const defaultsToUse = useDefaultValues(
233
- backendError?.repopulateFields,
234
- defaultValues
235
- );
236
- const [touchedFields, setTouchedFields] = useState<TouchedFields>({});
237
- const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
238
238
  const submit = useSubmit();
239
- const formRef = useRef<HTMLFormElement>(null);
240
- useSubmitComplete(isSubmitting, () => {
241
- endSubmit();
242
- if (!backendError && resetAfterSubmit) {
243
- formRef.current?.reset();
244
- }
245
- });
239
+ const clearError = useFormUpdateAtom(clearErrorAtom);
240
+ const addError = useFormUpdateAtom(addErrorAtom);
241
+ const setFieldErrors = useFormUpdateAtom(setFieldErrorsAtom);
242
+ const reset = useFormUpdateAtom(resetAtom);
243
+ const startSubmit = useFormUpdateAtom(startSubmitAtom);
244
+ const endSubmit = useFormUpdateAtom(endSubmitAtom);
245
+ const syncFormContext = useFormUpdateAtom(syncFormContextAtom);
246
+
247
+ const validateField: FormState["validateField"] = useCallback(
248
+ async (fieldName) => {
249
+ invariant(formRef.current, "Cannot find reference to form");
250
+ const { error } = await validator.validateField(
251
+ getDataFromForm(formRef.current),
252
+ fieldName as any
253
+ );
254
+
255
+ if (error) {
256
+ addError({ formAtom, name: fieldName, error });
257
+ return error;
258
+ } else {
259
+ clearError({ name: fieldName, formAtom });
260
+ return null;
261
+ }
262
+ },
263
+ [addError, clearError, formAtom, validator]
264
+ );
265
+
246
266
  const customFocusHandlers = useMultiValueMap<string, () => void>();
267
+ const registerReceiveFocus: FormState["registerReceiveFocus"] = useCallback(
268
+ (fieldName, handler) => {
269
+ customFocusHandlers().add(fieldName, handler);
270
+ return () => {
271
+ customFocusHandlers().remove(fieldName, handler);
272
+ };
273
+ },
274
+ [customFocusHandlers]
275
+ );
247
276
 
248
- const contextValue = useMemo<FormContextValue>(
249
- () => ({
250
- fieldErrors,
277
+ useLayoutEffect(() => {
278
+ syncFormContext({
279
+ formAtom,
251
280
  action,
252
- defaultValues: defaultsToUse,
253
- isSubmitting,
254
- isValid: Object.keys(fieldErrors).length === 0,
255
- touchedFields,
256
- setFieldTouched: (fieldName: string, touched: boolean) =>
257
- setTouchedFields((prev) => ({
258
- ...prev,
259
- [fieldName]: touched,
260
- })),
261
- clearError: (fieldName) => {
262
- setFieldErrors((prev) => omit(prev, fieldName));
263
- },
264
- validateField: async (fieldName) => {
265
- invariant(formRef.current, "Cannot find reference to form");
266
- const { error } = await validator.validateField(
267
- getDataFromForm(formRef.current),
268
- fieldName as any
269
- );
281
+ defaultValues: providedDefaultValues ?? backendDefaultValues,
282
+ subaction,
283
+ validateField,
284
+ registerReceiveFocus,
285
+ });
286
+ }, [
287
+ action,
288
+ formAtom,
289
+ providedDefaultValues,
290
+ registerReceiveFocus,
291
+ subaction,
292
+ syncFormContext,
293
+ validateField,
294
+ backendDefaultValues,
295
+ ]);
270
296
 
271
- // By checking and returning `prev` here, we can avoid a re-render
272
- // if the validation state is the same.
273
- if (error) {
274
- setFieldErrors((prev) => {
275
- if (prev[fieldName] === error) return prev;
276
- return {
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
- );
297
+ useEffect(() => {
298
+ setFieldErrors({
299
+ fieldErrors: backendError?.fieldErrors ?? {},
300
+ formAtom,
301
+ });
302
+ }, [backendError?.fieldErrors, formAtom, setFieldErrors]);
310
303
 
311
- const Form = fetcher?.Form ?? RemixForm;
304
+ useSubmitComplete(hasActiveSubmission, () => {
305
+ endSubmit({ formAtom });
306
+ });
312
307
 
313
308
  let clickedButtonRef = React.useRef<any>();
314
309
  useEffect(() => {
@@ -336,56 +331,63 @@ export function ValidatedForm<DataType>({
336
331
  };
337
332
  }, []);
338
333
 
334
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
335
+ startSubmit({ formAtom });
336
+ const result = await validator.validate(getDataFromForm(e.currentTarget));
337
+ if (result.error) {
338
+ endSubmit({ formAtom });
339
+ setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
340
+ if (!disableFocusOnError) {
341
+ focusFirstInvalidInput(
342
+ result.error.fieldErrors,
343
+ customFocusHandlers(),
344
+ formRef.current!
345
+ );
346
+ }
347
+ } else {
348
+ const eventProxy = formEventProxy(e);
349
+ await onSubmit?.(result.data, eventProxy);
350
+ if (eventProxy.defaultPrevented) {
351
+ endSubmit({ formAtom });
352
+ return;
353
+ }
354
+
355
+ if (fetcher) fetcher.submit(clickedButtonRef.current || e.currentTarget);
356
+ else
357
+ submit(clickedButtonRef.current || e.currentTarget, {
358
+ method,
359
+ replace,
360
+ });
361
+ clickedButtonRef.current = null;
362
+ }
363
+ };
364
+
339
365
  return (
340
366
  <Form
341
367
  ref={mergeRefs([formRef, formRefProp])}
342
368
  {...rest}
369
+ id={id}
343
370
  action={action}
344
371
  method={method}
345
372
  replace={replace}
346
- onSubmit={async (e) => {
373
+ onSubmit={(e) => {
347
374
  e.preventDefault();
348
- setHasBeenSubmitted(true);
349
- startSubmit();
350
- const result = await validator.validate(
351
- getDataFromForm(e.currentTarget)
352
- );
353
- if (result.error) {
354
- endSubmit();
355
- setFieldErrors(result.error.fieldErrors);
356
- if (!disableFocusOnError) {
357
- focusFirstInvalidInput(
358
- result.error.fieldErrors,
359
- customFocusHandlers(),
360
- formRef.current!
361
- );
362
- }
363
- } else {
364
- onSubmit && onSubmit(result.data, e);
365
- if (fetcher)
366
- fetcher.submit(clickedButtonRef.current || e.currentTarget);
367
- else
368
- submit(clickedButtonRef.current || e.currentTarget, {
369
- method,
370
- replace,
371
- });
372
- clickedButtonRef.current = null;
373
- }
375
+ handleSubmit(e);
374
376
  }}
375
377
  onReset={(event) => {
376
378
  onReset?.(event);
377
379
  if (event.defaultPrevented) return;
378
- setFieldErrors({});
379
- setTouchedFields({});
380
- setHasBeenSubmitted(false);
380
+ reset({ formAtom });
381
381
  }}
382
382
  >
383
- <FormContext.Provider value={contextValue}>
383
+ <InternalFormContext.Provider value={contextValue}>
384
+ <FormResetter formRef={formRef} resetAfterSubmit={resetAfterSubmit} />
384
385
  {subaction && (
385
386
  <input type="hidden" value={subaction} name="subaction" />
386
387
  )}
388
+ {id && <input type="hidden" value={id} name={FORM_ID_FIELD} />}
387
389
  {children}
388
- </FormContext.Provider>
390
+ </InternalFormContext.Provider>
389
391
  </Form>
390
392
  );
391
393
  }