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.
- package/.turbo/turbo-build.log +5 -5
- package/browser/ValidatedForm.js +20 -35
- package/browser/hooks.d.ts +1 -1
- package/browser/hooks.js +10 -9
- package/browser/internal/hooks.d.ts +20 -9
- package/browser/internal/hooks.js +32 -23
- package/browser/internal/logic/getRadioChecked.js +1 -1
- package/browser/internal/state/cleanup.d.ts +2 -0
- package/browser/internal/state/cleanup.js +6 -0
- package/browser/internal/state/controlledFieldStore.d.ts +24 -0
- package/browser/internal/state/controlledFieldStore.js +57 -0
- package/browser/internal/state/controlledFields.d.ts +3 -116
- package/browser/internal/state/controlledFields.js +25 -68
- package/browser/internal/state/createFormStore.d.ts +40 -0
- package/browser/internal/state/createFormStore.js +83 -0
- package/browser/internal/state/storeFamily.d.ts +9 -0
- package/browser/internal/state/storeFamily.js +18 -0
- package/browser/internal/state/storeHooks.d.ts +5 -0
- package/browser/internal/state/storeHooks.js +10 -0
- package/browser/unreleased/formStateHooks.d.ts +15 -0
- package/browser/unreleased/formStateHooks.js +23 -14
- package/browser/userFacingFormContext.d.ts +15 -0
- package/browser/userFacingFormContext.js +6 -4
- package/dist/remix-validated-form.cjs.js +18 -1
- package/dist/remix-validated-form.cjs.js.map +1 -0
- package/dist/remix-validated-form.es.js +1039 -1729
- package/dist/remix-validated-form.es.js.map +1 -0
- package/dist/remix-validated-form.umd.js +18 -1
- package/dist/remix-validated-form.umd.js.map +1 -0
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/internal/hooks.d.ts +20 -9
- package/dist/types/internal/state/cleanup.d.ts +2 -0
- package/dist/types/internal/state/controlledFieldStore.d.ts +24 -0
- package/dist/types/internal/state/controlledFields.d.ts +3 -116
- package/dist/types/internal/state/createFormStore.d.ts +40 -0
- package/dist/types/internal/state/storeFamily.d.ts +9 -0
- package/dist/types/internal/state/storeHooks.d.ts +5 -0
- package/dist/types/unreleased/formStateHooks.d.ts +15 -0
- package/dist/types/userFacingFormContext.d.ts +15 -0
- package/package.json +4 -3
- package/src/ValidatedForm.tsx +38 -53
- package/src/hooks.ts +15 -18
- package/src/internal/hooks.ts +69 -45
- package/src/internal/logic/getRadioChecked.ts +1 -1
- package/src/internal/state/cleanup.ts +8 -0
- package/src/internal/state/controlledFieldStore.ts +91 -0
- package/src/internal/state/controlledFields.ts +31 -123
- package/src/internal/state/createFormStore.ts +152 -0
- package/src/internal/state/storeFamily.ts +24 -0
- package/src/internal/state/storeHooks.ts +22 -0
- package/src/unreleased/formStateHooks.ts +50 -27
- package/src/userFacingFormContext.ts +26 -5
- package/dist/types/internal/reset.d.ts +0 -28
- package/dist/types/internal/state/atomUtils.d.ts +0 -38
- package/dist/types/internal/state.d.ts +0 -343
- package/src/internal/reset.ts +0 -26
- package/src/internal/state/atomUtils.ts +0 -13
- package/src/internal/state.ts +0 -124
package/dist/types/hooks.d.ts
CHANGED
@@ -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) => (
|
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
|
-
|
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, (
|
18
|
-
export declare const useFieldError: (name: string, context: InternalFormContextValue) =>
|
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
|
21
|
-
export declare const
|
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,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 {
|
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) => (
|
119
|
-
export declare const useAwaitValue: (formId: InternalFormId) => (
|
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
|
+
"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
|
-
"
|
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
|
}
|
package/src/ValidatedForm.tsx
CHANGED
@@ -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 {
|
26
|
+
import { cleanupFormState } from "./internal/state/cleanup";
|
27
|
+
import { SyncedFormProps } from "./internal/state/createFormStore";
|
29
28
|
import {
|
30
|
-
|
31
|
-
|
32
|
-
|
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 =
|
238
|
-
const setFieldError =
|
239
|
-
const reset =
|
240
|
-
const
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
const
|
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(
|
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(
|
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
|
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
|
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
|
105
|
+
const error = useFieldError(name, formContext);
|
106
|
+
const clearError = useClearError(formContext);
|
107
107
|
|
108
|
-
const hasBeenSubmitted =
|
109
|
-
|
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: () =>
|
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
|
|