remix-validated-form 4.0.1-beta.2 → 4.1.0-beta.2

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 +137 -149
  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 +133 -145
  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 +199 -200
  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.1-beta.2",
3
+ "version": "4.1.0-beta.2",
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,79 +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
- const isSubmitting = hasActiveSubmission && isSubmitStarted;
118
-
119
- const startSubmit = () => setSubmitStarted(true);
120
- const endSubmit = () => setSubmitStarted(false);
121
-
122
- return [isSubmitting, startSubmit, endSubmit];
123
- };
124
-
125
92
  const getDataFromForm = (el: HTMLFormElement) => new FormData(el);
126
93
 
127
- /**
128
- * The purpose for this logic is to handle validation errors when javascript is disabled.
129
- * Normally (without js), when a form is submitted and the action returns the validation errors,
130
- * the form will be reset. The errors will be displayed on the correct fields,
131
- * but all the values in the form will be gone. This is not good UX.
132
- *
133
- * To get around this, we return the submitted form data from the server,
134
- * and use those to populate the form via `defaultValues`.
135
- * This results in a more seamless UX akin to what you would see when js is enabled.
136
- *
137
- * One potential downside is that resetting the form will reset the form
138
- * to the _new_ default values that were returned from the server with the validation errors.
139
- * However, this case is less of a problem than the janky UX caused by losing the form values.
140
- * It will only ever be a problem if the form includes a `<button type="reset" />`
141
- * and only if JS is disabled.
142
- */
143
- function useDefaultValues<DataType>(
144
- repopulateFieldsFromBackend?: any,
145
- defaultValues?: Partial<DataType>
146
- ) {
147
- return repopulateFieldsFromBackend ?? defaultValues;
148
- }
149
-
150
94
  function nonNull<T>(value: T | null | undefined): value is T {
151
95
  return value !== null;
152
96
  }
@@ -201,6 +145,52 @@ const focusFirstInvalidInput = (
201
145
  }
202
146
  };
203
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
+
204
194
  /**
205
195
  * The primary form component of `remix-validated-form`.
206
196
  */
@@ -210,102 +200,104 @@ export function ValidatedForm<DataType>({
210
200
  children,
211
201
  fetcher,
212
202
  action,
213
- defaultValues,
203
+ defaultValues: providedDefaultValues,
214
204
  formRef: formRefProp,
215
205
  onReset,
216
206
  subaction,
217
- resetAfterSubmit,
207
+ resetAfterSubmit = false,
218
208
  disableFocusOnError,
219
209
  method,
220
210
  replace,
211
+ id,
221
212
  ...rest
222
213
  }: FormProps<DataType>) {
223
- const backendError = useErrorResponseForThisForm(fetcher, subaction);
224
- const [fieldErrors, setFieldErrors] = useFieldErrors(
225
- backendError?.fieldErrors
214
+ const formId = useFormId(id);
215
+ const formAtom = formRegistry(formId);
216
+ const contextValue = useMemo<InternalFormContextValue>(
217
+ () => ({
218
+ formId,
219
+ action,
220
+ subaction,
221
+ defaultValuesProp: providedDefaultValues,
222
+ fetcher,
223
+ }),
224
+ [action, fetcher, formId, providedDefaultValues, subaction]
226
225
  );
227
- const [isSubmitting, startSubmit, endSubmit] = useIsSubmitting(fetcher);
226
+ const backendError = useErrorResponseForForm(contextValue);
227
+ const backendDefaultValues = useDefaultValuesFromLoader(contextValue);
228
+ const hasActiveSubmission = useHasActiveFormSubmit(contextValue);
229
+ const formRef = useRef<HTMLFormElement>(null);
230
+ const Form = fetcher?.Form ?? RemixForm;
228
231
 
229
- const defaultsToUse = useDefaultValues(
230
- backendError?.repopulateFields,
231
- defaultValues
232
- );
233
- const [touchedFields, setTouchedFields] = useState<TouchedFields>({});
234
- const [hasBeenSubmitted, setHasBeenSubmitted] = useState(false);
235
232
  const submit = useSubmit();
236
- const formRef = useRef<HTMLFormElement>(null);
237
- useSubmitComplete(isSubmitting, () => {
238
- endSubmit();
239
- if (!backendError && resetAfterSubmit) {
240
- formRef.current?.reset();
241
- }
242
- });
233
+ const clearError = useFormUpdateAtom(clearErrorAtom);
234
+ const addError = useFormUpdateAtom(addErrorAtom);
235
+ const setFieldErrors = useFormUpdateAtom(setFieldErrorsAtom);
236
+ const reset = useFormUpdateAtom(resetAtom);
237
+ const startSubmit = useFormUpdateAtom(startSubmitAtom);
238
+ const endSubmit = useFormUpdateAtom(endSubmitAtom);
239
+ const syncFormContext = useFormUpdateAtom(syncFormContextAtom);
240
+
241
+ const validateField: FormState["validateField"] = useCallback(
242
+ async (fieldName) => {
243
+ invariant(formRef.current, "Cannot find reference to form");
244
+ const { error } = await validator.validateField(
245
+ getDataFromForm(formRef.current),
246
+ fieldName as any
247
+ );
248
+
249
+ if (error) {
250
+ addError({ formAtom, name: fieldName, error });
251
+ return error;
252
+ } else {
253
+ clearError({ name: fieldName, formAtom });
254
+ return null;
255
+ }
256
+ },
257
+ [addError, clearError, formAtom, validator]
258
+ );
259
+
243
260
  const customFocusHandlers = useMultiValueMap<string, () => void>();
261
+ const registerReceiveFocus: FormState["registerReceiveFocus"] = useCallback(
262
+ (fieldName, handler) => {
263
+ customFocusHandlers().add(fieldName, handler);
264
+ return () => {
265
+ customFocusHandlers().remove(fieldName, handler);
266
+ };
267
+ },
268
+ [customFocusHandlers]
269
+ );
244
270
 
245
- const contextValue = useMemo<FormContextValue>(
246
- () => ({
247
- fieldErrors,
271
+ useLayoutEffect(() => {
272
+ syncFormContext({
273
+ formAtom,
248
274
  action,
249
- defaultValues: defaultsToUse,
250
- isSubmitting,
251
- isValid: Object.keys(fieldErrors).length === 0,
252
- touchedFields,
253
- setFieldTouched: (fieldName: string, touched: boolean) =>
254
- setTouchedFields((prev) => ({
255
- ...prev,
256
- [fieldName]: touched,
257
- })),
258
- clearError: (fieldName) => {
259
- setFieldErrors((prev) => omit(prev, fieldName));
260
- },
261
- validateField: async (fieldName) => {
262
- invariant(formRef.current, "Cannot find reference to form");
263
- const { error } = await validator.validateField(
264
- getDataFromForm(formRef.current),
265
- fieldName as any
266
- );
275
+ defaultValues: providedDefaultValues ?? backendDefaultValues,
276
+ subaction,
277
+ validateField,
278
+ registerReceiveFocus,
279
+ });
280
+ }, [
281
+ action,
282
+ formAtom,
283
+ providedDefaultValues,
284
+ registerReceiveFocus,
285
+ subaction,
286
+ syncFormContext,
287
+ validateField,
288
+ backendDefaultValues,
289
+ ]);
267
290
 
268
- // By checking and returning `prev` here, we can avoid a re-render
269
- // if the validation state is the same.
270
- if (error) {
271
- setFieldErrors((prev) => {
272
- if (prev[fieldName] === error) return prev;
273
- return {
274
- ...prev,
275
- [fieldName]: error,
276
- };
277
- });
278
- return error;
279
- } else {
280
- setFieldErrors((prev) => {
281
- if (!(fieldName in prev)) return prev;
282
- return omit(prev, fieldName);
283
- });
284
- return null;
285
- }
286
- },
287
- registerReceiveFocus: (fieldName, handler) => {
288
- customFocusHandlers().add(fieldName, handler);
289
- return () => {
290
- customFocusHandlers().remove(fieldName, handler);
291
- };
292
- },
293
- hasBeenSubmitted,
294
- }),
295
- [
296
- fieldErrors,
297
- action,
298
- defaultsToUse,
299
- isSubmitting,
300
- touchedFields,
301
- hasBeenSubmitted,
302
- setFieldErrors,
303
- validator,
304
- customFocusHandlers,
305
- ]
306
- );
291
+ useEffect(() => {
292
+ setFieldErrors({
293
+ fieldErrors: backendError?.fieldErrors ?? {},
294
+ formAtom,
295
+ });
296
+ }, [backendError?.fieldErrors, formAtom, setFieldErrors]);
307
297
 
308
- const Form = fetcher?.Form ?? RemixForm;
298
+ useSubmitComplete(hasActiveSubmission, () => {
299
+ endSubmit({ formAtom });
300
+ });
309
301
 
310
302
  let clickedButtonRef = React.useRef<any>();
311
303
  useEffect(() => {
@@ -333,56 +325,63 @@ export function ValidatedForm<DataType>({
333
325
  };
334
326
  }, []);
335
327
 
328
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
329
+ startSubmit({ formAtom });
330
+ const result = await validator.validate(getDataFromForm(e.currentTarget));
331
+ if (result.error) {
332
+ endSubmit({ formAtom });
333
+ setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
334
+ if (!disableFocusOnError) {
335
+ focusFirstInvalidInput(
336
+ result.error.fieldErrors,
337
+ customFocusHandlers(),
338
+ formRef.current!
339
+ );
340
+ }
341
+ } else {
342
+ const eventProxy = formEventProxy(e);
343
+ await onSubmit?.(result.data, eventProxy);
344
+ if (eventProxy.defaultPrevented) {
345
+ endSubmit({ formAtom });
346
+ return;
347
+ }
348
+
349
+ if (fetcher) fetcher.submit(clickedButtonRef.current || e.currentTarget);
350
+ else
351
+ submit(clickedButtonRef.current || e.currentTarget, {
352
+ method,
353
+ replace,
354
+ });
355
+ clickedButtonRef.current = null;
356
+ }
357
+ };
358
+
336
359
  return (
337
360
  <Form
338
361
  ref={mergeRefs([formRef, formRefProp])}
339
362
  {...rest}
363
+ id={id}
340
364
  action={action}
341
365
  method={method}
342
366
  replace={replace}
343
- onSubmit={async (e) => {
367
+ onSubmit={(e) => {
344
368
  e.preventDefault();
345
- setHasBeenSubmitted(true);
346
- startSubmit();
347
- const result = await validator.validate(
348
- getDataFromForm(e.currentTarget)
349
- );
350
- if (result.error) {
351
- endSubmit();
352
- setFieldErrors(result.error.fieldErrors);
353
- if (!disableFocusOnError) {
354
- focusFirstInvalidInput(
355
- result.error.fieldErrors,
356
- customFocusHandlers(),
357
- formRef.current!
358
- );
359
- }
360
- } else {
361
- onSubmit && onSubmit(result.data, e);
362
- if (fetcher)
363
- fetcher.submit(clickedButtonRef.current || e.currentTarget);
364
- else
365
- submit(clickedButtonRef.current || e.currentTarget, {
366
- method,
367
- replace,
368
- });
369
- clickedButtonRef.current = null;
370
- }
369
+ handleSubmit(e);
371
370
  }}
372
371
  onReset={(event) => {
373
372
  onReset?.(event);
374
373
  if (event.defaultPrevented) return;
375
- setFieldErrors({});
376
- setTouchedFields({});
377
- setHasBeenSubmitted(false);
374
+ reset({ formAtom });
378
375
  }}
379
376
  >
380
- <FormContext.Provider value={contextValue}>
377
+ <InternalFormContext.Provider value={contextValue}>
378
+ <FormResetter formRef={formRef} resetAfterSubmit={resetAfterSubmit} />
381
379
  {subaction && (
382
380
  <input type="hidden" value={subaction} name="subaction" />
383
381
  )}
382
+ {id && <input type="hidden" value={id} name={FORM_ID_FIELD} />}
384
383
  {children}
385
- </FormContext.Provider>
384
+ </InternalFormContext.Provider>
386
385
  </Form>
387
386
  );
388
387
  }