remix-validated-form 4.3.1-beta.0 → 4.4.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 (58) hide show
  1. package/.turbo/turbo-build.log +5 -5
  2. package/browser/ValidatedForm.js +20 -35
  3. package/browser/hooks.d.ts +1 -1
  4. package/browser/hooks.js +10 -9
  5. package/browser/internal/hooks.d.ts +20 -9
  6. package/browser/internal/hooks.js +32 -23
  7. package/browser/internal/logic/getRadioChecked.js +1 -1
  8. package/browser/internal/state/cleanup.d.ts +2 -0
  9. package/browser/internal/state/cleanup.js +6 -0
  10. package/browser/internal/state/controlledFieldStore.d.ts +24 -0
  11. package/browser/internal/state/controlledFieldStore.js +57 -0
  12. package/browser/internal/state/controlledFields.d.ts +3 -116
  13. package/browser/internal/state/controlledFields.js +25 -68
  14. package/browser/internal/state/createFormStore.d.ts +40 -0
  15. package/browser/internal/state/createFormStore.js +83 -0
  16. package/browser/internal/state/storeFamily.d.ts +9 -0
  17. package/browser/internal/state/storeFamily.js +18 -0
  18. package/browser/internal/state/storeHooks.d.ts +5 -0
  19. package/browser/internal/state/storeHooks.js +10 -0
  20. package/browser/unreleased/formStateHooks.d.ts +15 -0
  21. package/browser/unreleased/formStateHooks.js +23 -14
  22. package/browser/userFacingFormContext.d.ts +15 -0
  23. package/browser/userFacingFormContext.js +6 -4
  24. package/dist/remix-validated-form.cjs.js +18 -1
  25. package/dist/remix-validated-form.cjs.js.map +1 -0
  26. package/dist/remix-validated-form.es.js +1039 -1729
  27. package/dist/remix-validated-form.es.js.map +1 -0
  28. package/dist/remix-validated-form.umd.js +18 -1
  29. package/dist/remix-validated-form.umd.js.map +1 -0
  30. package/dist/types/hooks.d.ts +1 -1
  31. package/dist/types/internal/hooks.d.ts +20 -9
  32. package/dist/types/internal/state/cleanup.d.ts +2 -0
  33. package/dist/types/internal/state/controlledFieldStore.d.ts +24 -0
  34. package/dist/types/internal/state/controlledFields.d.ts +3 -116
  35. package/dist/types/internal/state/createFormStore.d.ts +40 -0
  36. package/dist/types/internal/state/storeFamily.d.ts +9 -0
  37. package/dist/types/internal/state/storeHooks.d.ts +5 -0
  38. package/dist/types/unreleased/formStateHooks.d.ts +15 -0
  39. package/dist/types/userFacingFormContext.d.ts +15 -0
  40. package/package.json +4 -3
  41. package/src/ValidatedForm.tsx +38 -53
  42. package/src/hooks.ts +15 -18
  43. package/src/internal/hooks.ts +69 -45
  44. package/src/internal/logic/getRadioChecked.ts +1 -1
  45. package/src/internal/state/cleanup.ts +8 -0
  46. package/src/internal/state/controlledFieldStore.ts +91 -0
  47. package/src/internal/state/controlledFields.ts +31 -123
  48. package/src/internal/state/createFormStore.ts +152 -0
  49. package/src/internal/state/storeFamily.ts +24 -0
  50. package/src/internal/state/storeHooks.ts +22 -0
  51. package/src/unreleased/formStateHooks.ts +50 -27
  52. package/src/userFacingFormContext.ts +26 -5
  53. package/dist/types/internal/reset.d.ts +0 -28
  54. package/dist/types/internal/state/atomUtils.d.ts +0 -38
  55. package/dist/types/internal/state.d.ts +0 -343
  56. package/src/internal/reset.ts +0 -26
  57. package/src/internal/state/atomUtils.ts +0 -13
  58. package/src/internal/state.ts +0 -124
@@ -64,4 +64,4 @@ export declare const useField: (name: string, options?: {
64
64
  formId?: string | undefined;
65
65
  } | undefined) => FieldProps;
66
66
  export declare const useControlField: <T>(name: string, formId?: string | undefined) => readonly [T, (value: T) => void];
67
- export declare const useUpdateControlledField: (formId?: string | undefined) => (field: string, value: unknown) => void;
67
+ export declare const useUpdateControlledField: (formId?: string | undefined) => (fieldName: string, value: unknown) => void;
@@ -1,11 +1,7 @@
1
- import { Atom, WritableAtom } from "jotai";
2
- import { useUpdateAtom } from "jotai/utils";
3
1
  import { FieldErrors, ValidationErrorResponseData } from "..";
4
2
  import { InternalFormContextValue } from "./formContext";
5
3
  import { Hydratable } from "./hydratable";
6
- export declare const useFormUpdateAtom: typeof useUpdateAtom;
7
- export declare const useFormAtom: <Value, Update, Result extends void | Promise<void>>(anAtom: WritableAtom<Value, Update, Result>) => [Value extends Promise<infer V> ? V : Value, import("jotai/core/atom").SetAtom<Update, Result>];
8
- export declare const useFormAtomValue: <Value>(anAtom: Atom<Value>) => Value extends Promise<infer V> ? V : Value;
4
+ import { InternalFormId } from "./state/storeFamily";
9
5
  export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
10
6
  export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
11
7
  export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
@@ -14,8 +10,23 @@ export declare const useDefaultValuesForForm: (context: InternalFormContextValue
14
10
  [fieldName: string]: any;
15
11
  }>;
16
12
  export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
17
- export declare const useFieldTouched: (field: string, { formId }: InternalFormContextValue) => [boolean, (update: boolean) => void];
18
- export declare const useFieldError: (name: string, context: InternalFormContextValue) => readonly [string | undefined, (update?: string | undefined) => void];
13
+ export declare const useFieldTouched: (field: string, { formId }: InternalFormContextValue) => readonly [boolean, (touched: boolean) => void];
14
+ export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
15
+ export declare const useClearError: (context: InternalFormContextValue) => (field: string) => void;
19
16
  export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
20
- export declare const useClearError: ({ formId }: InternalFormContextValue) => (name: string) => void;
21
- export declare const useSetTouched: ({ formId }: InternalFormContextValue) => (name: string, touched: boolean) => void;
17
+ export declare const useInternalIsSubmitting: (formId: InternalFormId) => boolean;
18
+ export declare const useInternalIsValid: (formId: InternalFormId) => boolean;
19
+ export declare const useInternalHasBeenSubmitted: (formId: InternalFormId) => boolean;
20
+ export declare const useValidateField: (formId: InternalFormId) => (fieldName: string) => Promise<string | null>;
21
+ export declare const useValidate: (formId: InternalFormId) => () => Promise<void>;
22
+ export declare const useRegisterReceiveFocus: (formId: InternalFormId) => (fieldName: string, handler: () => void) => () => void;
23
+ export declare const useSyncedDefaultValues: (formId: InternalFormId) => {
24
+ [fieldName: string]: any;
25
+ };
26
+ export declare const useSetTouched: ({ formId }: InternalFormContextValue) => (field: string, touched: boolean) => void;
27
+ export declare const useTouchedFields: (formId: InternalFormId) => import("..").TouchedFields;
28
+ export declare const useFieldErrors: (formId: InternalFormId) => FieldErrors;
29
+ export declare const useSetFieldErrors: (formId: InternalFormId) => (errors: FieldErrors) => void;
30
+ export declare const useResetFormElement: (formId: InternalFormId) => () => void;
31
+ export declare const useFormActionProp: (formId: InternalFormId) => string | undefined;
32
+ export declare const useFormSubactionProp: (formId: InternalFormId) => string | undefined;
@@ -0,0 +1,2 @@
1
+ import { InternalFormId } from "./storeFamily";
2
+ export declare const cleanupFormState: (formId: InternalFormId) => void;
@@ -0,0 +1,24 @@
1
+ export declare type ControlledFieldState = {
2
+ fields: {
3
+ [fieldName: string]: {
4
+ refCount: number;
5
+ value: unknown;
6
+ defaultValue?: unknown;
7
+ hydrated: boolean;
8
+ valueUpdatePromise: Promise<void> | undefined;
9
+ resolveValueUpdate: (() => void) | undefined;
10
+ } | undefined;
11
+ };
12
+ register: (fieldName: string) => void;
13
+ unregister: (fieldName: string) => void;
14
+ setValue: (fieldName: string, value: unknown) => void;
15
+ hydrateWithDefault: (fieldName: string, defaultValue: unknown) => void;
16
+ awaitValueUpdate: (fieldName: string) => Promise<void>;
17
+ reset: () => void;
18
+ };
19
+ export declare const controlledFieldStore: {
20
+ (formId: import("./storeFamily").InternalFormId): import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ControlledFieldState>, "setState"> & {
21
+ setState(nextStateOrUpdater: ControlledFieldState | Partial<ControlledFieldState> | ((state: import("immer/dist/internal").WritableDraft<ControlledFieldState>) => void), shouldReplace?: boolean | undefined): void;
22
+ }>;
23
+ remove(formId: import("./storeFamily").InternalFormId): void;
24
+ };
@@ -1,119 +1,6 @@
1
- import { PrimitiveAtom } from "jotai";
2
1
  import { InternalFormContextValue } from "../formContext";
3
- import { FieldAtomKey, InternalFormId } from "./atomUtils";
4
- export declare const controlledFieldsAtom: {
5
- (param: InternalFormId): import("jotai").Atom<Record<string, PrimitiveAtom<unknown>>> & {
6
- write: (get: {
7
- <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
8
- <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
9
- <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
10
- } & {
11
- <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
12
- unstable_promise: true;
13
- }): Value_3 | Promise<Value_3>;
14
- <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
15
- unstable_promise: true;
16
- }): Value_4 | Promise<Value_4>;
17
- <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
18
- unstable_promise: true;
19
- }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
20
- }, set: {
21
- <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
22
- <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
23
- }, update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void;
24
- onMount?: (<S extends (update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void>(setAtom: S) => void | (() => void)) | undefined;
25
- } & {
26
- init: Record<string, PrimitiveAtom<unknown>>;
27
- };
28
- remove(param: InternalFormId): void;
29
- setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
30
- };
31
- export declare const valueUpdatePromiseAtom: {
32
- (param: FieldAtomKey): import("jotai").Atom<Promise<void> | undefined> & {
33
- write: (get: {
34
- <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
35
- <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
36
- <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
37
- } & {
38
- <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
39
- unstable_promise: true;
40
- }): Value_3 | Promise<Value_3>;
41
- <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
42
- unstable_promise: true;
43
- }): Value_4 | Promise<Value_4>;
44
- <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
45
- unstable_promise: true;
46
- }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
47
- }, set: {
48
- <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
49
- <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
50
- }, update: Promise<void> | ((prev: Promise<void> | undefined) => Promise<void> | undefined) | undefined) => void;
51
- onMount?: (<S extends (update?: Promise<void> | ((prev: Promise<void> | undefined) => Promise<void> | undefined) | undefined) => void>(setAtom: S) => void | (() => void)) | undefined;
52
- } & {
53
- init: Promise<void> | undefined;
54
- };
55
- remove(param: FieldAtomKey): void;
56
- setShouldRemove(shouldRemove: ((createdAt: number, param: FieldAtomKey) => boolean) | null): void;
57
- };
58
- export declare const resolveValueUpdateAtom: {
59
- (param: FieldAtomKey): import("jotai").Atom<(() => void) | undefined> & {
60
- write: (get: {
61
- <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
62
- <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
63
- <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
64
- } & {
65
- <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
66
- unstable_promise: true;
67
- }): Value_3 | Promise<Value_3>;
68
- <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
69
- unstable_promise: true;
70
- }): Value_4 | Promise<Value_4>;
71
- <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
72
- unstable_promise: true;
73
- }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
74
- }, set: {
75
- <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
76
- <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
77
- }, update: (() => void) | ((prev: (() => void) | undefined) => (() => void) | undefined) | undefined) => void;
78
- onMount?: (<S extends (update?: (() => void) | ((prev: (() => void) | undefined) => (() => void) | undefined) | undefined) => void>(setAtom: S) => void | (() => void)) | undefined;
79
- } & {
80
- init: (() => void) | undefined;
81
- };
82
- remove(param: FieldAtomKey): void;
83
- setShouldRemove(shouldRemove: ((createdAt: number, param: FieldAtomKey) => boolean) | null): void;
84
- };
85
- export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
86
- write: (get: {
87
- <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
88
- <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
89
- <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
90
- } & {
91
- <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
92
- unstable_promise: true;
93
- }): Value_3 | Promise<Value_3>;
94
- <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
95
- unstable_promise: true;
96
- }): Value_4 | Promise<Value_4>;
97
- <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
98
- unstable_promise: true;
99
- }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
100
- }, set: {
101
- <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
102
- <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
103
- }, update: {
104
- formId: InternalFormId;
105
- field: string;
106
- value: unknown;
107
- }) => void;
108
- onMount?: (<S extends (update: {
109
- formId: InternalFormId;
110
- field: string;
111
- value: unknown;
112
- }) => void>(setAtom: S) => void | (() => void)) | undefined;
113
- } & {
114
- init: null;
115
- };
2
+ import { InternalFormId } from "./storeFamily";
116
3
  export declare const useControlledFieldValue: (context: InternalFormContextValue, field: string) => any;
117
4
  export declare const useControllableValue: (context: InternalFormContextValue, field: string) => readonly [any, (value: unknown) => void];
118
- export declare const useUpdateControllableValue: (formId: InternalFormId) => (field: string, value: unknown) => void;
119
- export declare const useAwaitValue: (formId: InternalFormId) => (arg: string) => Promise<void>;
5
+ export declare const useUpdateControllableValue: (formId: InternalFormId) => (fieldName: string, value: unknown) => void;
6
+ export declare const useAwaitValue: (formId: InternalFormId) => (fieldName: string) => Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { FieldErrors, TouchedFields, Validator } from "../../validation/types";
2
+ export declare type SyncedFormProps = {
3
+ formId?: string;
4
+ action?: string;
5
+ subaction?: string;
6
+ defaultValues: {
7
+ [fieldName: string]: any;
8
+ };
9
+ registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
10
+ validator: Validator<unknown>;
11
+ };
12
+ export declare type FormState = {
13
+ isHydrated: boolean;
14
+ isSubmitting: boolean;
15
+ hasBeenSubmitted: boolean;
16
+ fieldErrors: FieldErrors;
17
+ touchedFields: TouchedFields;
18
+ formProps?: SyncedFormProps;
19
+ formElement: HTMLFormElement | null;
20
+ isValid: () => boolean;
21
+ startSubmit: () => void;
22
+ endSubmit: () => void;
23
+ setTouched: (field: string, touched: boolean) => void;
24
+ setFieldError: (field: string, error: string) => void;
25
+ setFieldErrors: (errors: FieldErrors) => void;
26
+ clearFieldError: (field: string) => void;
27
+ reset: () => void;
28
+ syncFormProps: (props: SyncedFormProps) => void;
29
+ setHydrated: () => void;
30
+ setFormElement: (formElement: HTMLFormElement | null) => void;
31
+ validateField: (fieldName: string) => Promise<string | null>;
32
+ validate: () => Promise<void>;
33
+ resetFormElement: () => void;
34
+ };
35
+ export declare const formStore: {
36
+ (formId: import("./storeFamily").InternalFormId): import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<FormState>, "setState"> & {
37
+ setState(nextStateOrUpdater: FormState | Partial<FormState> | ((state: import("immer/dist/internal").WritableDraft<FormState>) => void), shouldReplace?: boolean | undefined): void;
38
+ }>;
39
+ remove(formId: import("./storeFamily").InternalFormId): void;
40
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This is basically what `atomFamily` from jotai does,
3
+ * but it doesn't make sense to include the entire jotai library just for that api.
4
+ */
5
+ export declare type InternalFormId = string | symbol;
6
+ export declare const storeFamily: <T>(create: (formId: InternalFormId) => T) => {
7
+ (formId: InternalFormId): T;
8
+ remove(formId: InternalFormId): void;
9
+ };
@@ -0,0 +1,5 @@
1
+ import { ControlledFieldState } from "./controlledFieldStore";
2
+ import { FormState } from "./createFormStore";
3
+ import { InternalFormId } from "./storeFamily";
4
+ export declare const useFormStore: <T>(formId: InternalFormId, selector: (state: FormState) => T) => T;
5
+ export declare const useControlledFieldStore: <T>(formId: InternalFormId, selector: (state: ControlledFieldState) => T) => T;
@@ -30,6 +30,21 @@ export declare type FormHelpers = {
30
30
  * Change the touched state of the specified field.
31
31
  */
32
32
  setTouched: (fieldName: string, touched: boolean) => void;
33
+ /**
34
+ * Validate the whole form and populate any errors.
35
+ */
36
+ validate: () => Promise<void>;
37
+ /**
38
+ * Clears all errors on the form.
39
+ */
40
+ clearAllErrors: () => void;
41
+ /**
42
+ * Resets the form.
43
+ *
44
+ * _Note_: The equivalent behavior can be achieved by calling formElement.reset()
45
+ * or clicking a button element with `type="reset"`.
46
+ */
47
+ reset: () => void;
33
48
  };
34
49
  /**
35
50
  * Returns helpers that can be used to update the form state.
@@ -53,6 +53,21 @@ export declare type FormContextValue = {
53
53
  * Change the touched state of the specified field.
54
54
  */
55
55
  setFieldTouched: (fieldName: string, touched: boolean) => void;
56
+ /**
57
+ * Validate the whole form and populate any errors.
58
+ */
59
+ validate: () => Promise<void>;
60
+ /**
61
+ * Clears all errors on the form.
62
+ */
63
+ clearAllErrors: () => void;
64
+ /**
65
+ * Resets the form.
66
+ *
67
+ * _Note_: The equivalent behavior can be achieved by calling formElement.reset()
68
+ * or clicking a button element with `type="reset"`.
69
+ */
70
+ reset: () => void;
56
71
  };
57
72
  /**
58
73
  * Provides access to some of the internal state of the form.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "4.3.1-beta.0",
3
+ "version": "4.4.2",
4
4
  "description": "Form component and utils for easy form validation in remix",
5
5
  "browser": "./dist/remix-validated-form.cjs.js",
6
6
  "main": "./dist/remix-validated-form.umd.js",
@@ -49,8 +49,9 @@
49
49
  "vite-config": "*"
50
50
  },
51
51
  "dependencies": {
52
- "jotai": "^1.5.3",
52
+ "immer": "^9.0.12",
53
53
  "lodash": "^4.17.21",
54
- "tiny-invariant": "^1.2.0"
54
+ "tiny-invariant": "^1.2.0",
55
+ "zustand": "^4.0.0-rc.0"
55
56
  }
56
57
  }
@@ -1,5 +1,4 @@
1
1
  import { Form as RemixForm, useFetcher, useSubmit } from "@remix-run/react";
2
- import { useAtomCallback } from "jotai/utils";
3
2
  import uniq from "lodash/uniq";
4
3
  import React, {
5
4
  ComponentProps,
@@ -11,7 +10,6 @@ import React, {
11
10
  useRef,
12
11
  useState,
13
12
  } from "react";
14
- import invariant from "tiny-invariant";
15
13
  import { useIsSubmitting, useIsValid } from "./hooks";
16
14
  import { FORM_ID_FIELD } from "./internal/constants";
17
15
  import {
@@ -21,23 +19,16 @@ import {
21
19
  import {
22
20
  useDefaultValuesFromLoader,
23
21
  useErrorResponseForForm,
24
- useFormUpdateAtom,
25
22
  useHasActiveFormSubmit,
23
+ useSetFieldErrors,
26
24
  } from "./internal/hooks";
27
25
  import { MultiValueMap, useMultiValueMap } from "./internal/MultiValueMap";
28
- import { resetAtom } from "./internal/reset";
26
+ import { cleanupFormState } from "./internal/state/cleanup";
27
+ import { SyncedFormProps } from "./internal/state/createFormStore";
29
28
  import {
30
- cleanupFormState,
31
- endSubmitAtom,
32
- fieldErrorsAtom,
33
- formElementAtom,
34
- formPropsAtom,
35
- isHydratedAtom,
36
- setFieldErrorAtom,
37
- startSubmitAtom,
38
- SyncedFormProps,
39
- } from "./internal/state";
40
- import { useAwaitValue } from "./internal/state/controlledFields";
29
+ useControlledFieldStore,
30
+ useFormStore,
31
+ } from "./internal/state/storeHooks";
41
32
  import { useSubmitComplete } from "./internal/submissionCallbacks";
42
33
  import {
43
34
  mergeRefs,
@@ -195,6 +186,14 @@ function formEventProxy<T extends object>(event: T): T {
195
186
  }) as T;
196
187
  }
197
188
 
189
+ type HTMLSubmitEvent = React.BaseSyntheticEvent<
190
+ SubmitEvent,
191
+ Event,
192
+ HTMLFormElement
193
+ >;
194
+
195
+ type HTMLFormSubmitter = HTMLButtonElement | HTMLInputElement;
196
+
198
197
  /**
199
198
  * The primary form component of `remix-validated-form`.
200
199
  */
@@ -234,41 +233,27 @@ export function ValidatedForm<DataType>({
234
233
  const Form = fetcher?.Form ?? RemixForm;
235
234
 
236
235
  const submit = useSubmit();
237
- const setFieldErrors = useFormUpdateAtom(fieldErrorsAtom(formId));
238
- const setFieldError = useFormUpdateAtom(setFieldErrorAtom(formId));
239
- const reset = useFormUpdateAtom(resetAtom(formId));
240
- const startSubmit = useFormUpdateAtom(startSubmitAtom(formId));
241
- const endSubmit = useFormUpdateAtom(endSubmitAtom(formId));
242
- const syncFormProps = useFormUpdateAtom(formPropsAtom(formId));
243
- const setHydrated = useFormUpdateAtom(isHydratedAtom(formId));
244
- const setFormElementInState = useFormUpdateAtom(formElementAtom(formId));
236
+ const setFieldErrors = useSetFieldErrors(formId);
237
+ const setFieldError = useFormStore(formId, (state) => state.setFieldError);
238
+ const reset = useFormStore(formId, (state) => state.reset);
239
+ const resetControlledFields = useControlledFieldStore(
240
+ formId,
241
+ (state) => state.reset
242
+ );
243
+ const startSubmit = useFormStore(formId, (state) => state.startSubmit);
244
+ const endSubmit = useFormStore(formId, (state) => state.endSubmit);
245
+ const syncFormProps = useFormStore(formId, (state) => state.syncFormProps);
246
+ const setHydrated = useFormStore(formId, (state) => state.setHydrated);
247
+ const setFormElementInState = useFormStore(
248
+ formId,
249
+ (state) => state.setFormElement
250
+ );
245
251
 
246
252
  useEffect(() => {
247
- setHydrated(true);
253
+ setHydrated();
248
254
  return () => cleanupFormState(formId);
249
255
  }, [formId, setHydrated]);
250
256
 
251
- const awaitValue = useAwaitValue(formId);
252
- const validateField: SyncedFormProps["validateField"] = useCallback(
253
- async (field) => {
254
- invariant(formRef.current, "Cannot find reference to form");
255
- await awaitValue(field);
256
- const { error } = await validator.validateField(
257
- getDataFromForm(formRef.current),
258
- field
259
- );
260
-
261
- if (error) {
262
- setFieldError({ field, error });
263
- return error;
264
- } else {
265
- setFieldError({ field, error: undefined });
266
- return null;
267
- }
268
- },
269
- [awaitValue, setFieldError, validator]
270
- );
271
-
272
257
  const customFocusHandlers = useMultiValueMap<string, () => void>();
273
258
  const registerReceiveFocus: SyncedFormProps["registerReceiveFocus"] =
274
259
  useCallback(
@@ -286,8 +271,8 @@ export function ValidatedForm<DataType>({
286
271
  action,
287
272
  defaultValues: providedDefaultValues ?? backendDefaultValues ?? {},
288
273
  subaction,
289
- validateField,
290
274
  registerReceiveFocus,
275
+ validator,
291
276
  });
292
277
  }, [
293
278
  action,
@@ -295,8 +280,8 @@ export function ValidatedForm<DataType>({
295
280
  registerReceiveFocus,
296
281
  subaction,
297
282
  syncFormProps,
298
- validateField,
299
283
  backendDefaultValues,
284
+ validator,
300
285
  ]);
301
286
 
302
287
  useEffect(() => {
@@ -354,17 +339,16 @@ export function ValidatedForm<DataType>({
354
339
  return;
355
340
  }
356
341
 
342
+ const submitter = (e as unknown as HTMLSubmitEvent).nativeEvent
343
+ .submitter as HTMLFormSubmitter | null;
344
+
357
345
  // We deviate from the remix code here a bit because of our async submit.
358
346
  // In remix's `FormImpl`, they use `event.currentTarget` to get the form,
359
347
  // but we already have the form in `formRef.current` so we can just use that.
360
348
  // If we use `event.currentTarget` here, it will break because `currentTarget`
361
349
  // will have changed since the start of the submission.
362
- if (fetcher) fetcher.submit(clickedButtonRef.current || formRef.current);
363
- else
364
- submit(clickedButtonRef.current || formRef.current, {
365
- method,
366
- replace,
367
- });
350
+ if (fetcher) fetcher.submit(submitter || e.currentTarget);
351
+ else submit(submitter || e.currentTarget, { method, replace });
368
352
 
369
353
  clickedButtonRef.current = null;
370
354
  }
@@ -386,6 +370,7 @@ export function ValidatedForm<DataType>({
386
370
  onReset?.(event);
387
371
  if (event.defaultPrevented) return;
388
372
  reset();
373
+ resetControlledFields();
389
374
  }}
390
375
  >
391
376
  <InternalFormContext.Provider value={contextValue}>
package/src/hooks.ts CHANGED
@@ -8,15 +8,14 @@ import {
8
8
  useInternalFormContext,
9
9
  useFieldTouched,
10
10
  useFieldError,
11
- useFormAtomValue,
12
11
  useFieldDefaultValue,
12
+ useClearError,
13
+ useInternalIsSubmitting,
14
+ useInternalIsValid,
15
+ useInternalHasBeenSubmitted,
16
+ useValidateField,
17
+ useRegisterReceiveFocus,
13
18
  } from "./internal/hooks";
14
- import {
15
- formPropsAtom,
16
- hasBeenSubmittedAtom,
17
- isSubmittingAtom,
18
- isValidAtom,
19
- } from "./internal/state";
20
19
  import {
21
20
  useControllableValue,
22
21
  useUpdateControllableValue,
@@ -31,7 +30,7 @@ import {
31
30
  */
32
31
  export const useIsSubmitting = (formId?: string) => {
33
32
  const formContext = useInternalFormContext(formId, "useIsSubmitting");
34
- return useFormAtomValue(isSubmittingAtom(formContext.formId));
33
+ return useInternalIsSubmitting(formContext.formId);
35
34
  };
36
35
 
37
36
  /**
@@ -41,7 +40,7 @@ export const useIsSubmitting = (formId?: string) => {
41
40
  */
42
41
  export const useIsValid = (formId?: string) => {
43
42
  const formContext = useInternalFormContext(formId, "useIsValid");
44
- return useFormAtomValue(isValidAtom(formContext.formId));
43
+ return useInternalIsValid(formContext.formId);
45
44
  };
46
45
 
47
46
  export type FieldProps = {
@@ -103,14 +102,12 @@ export const useField = (
103
102
 
104
103
  const defaultValue = useFieldDefaultValue(name, formContext);
105
104
  const [touched, setTouched] = useFieldTouched(name, formContext);
106
- const [error, setError] = useFieldError(name, formContext);
105
+ const error = useFieldError(name, formContext);
106
+ const clearError = useClearError(formContext);
107
107
 
108
- const hasBeenSubmitted = useFormAtomValue(
109
- hasBeenSubmittedAtom(formContext.formId)
110
- );
111
- const { validateField, registerReceiveFocus } = useFormAtomValue(
112
- formPropsAtom(formContext.formId)
113
- );
108
+ const hasBeenSubmitted = useInternalHasBeenSubmitted(formContext.formId);
109
+ const validateField = useValidateField(formContext.formId);
110
+ const registerReceiveFocus = useRegisterReceiveFocus(formContext.formId);
114
111
 
115
112
  useEffect(() => {
116
113
  if (handleReceiveFocus)
@@ -120,7 +117,7 @@ export const useField = (
120
117
  const field = useMemo<FieldProps>(() => {
121
118
  const helpers = {
122
119
  error,
123
- clearError: () => setError(undefined),
120
+ clearError: () => clearError(name),
124
121
  validate: () => {
125
122
  validateField(name);
126
123
  },
@@ -140,13 +137,13 @@ export const useField = (
140
137
  };
141
138
  }, [
142
139
  error,
140
+ clearError,
143
141
  defaultValue,
144
142
  touched,
145
143
  setTouched,
146
144
  name,
147
145
  hasBeenSubmitted,
148
146
  options?.validationBehavior,
149
- setError,
150
147
  validateField,
151
148
  ]);
152
149