remix-validated-form 4.1.0 → 4.1.4-beta.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 (73) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/browser/ValidatedForm.d.ts +1 -1
  3. package/browser/ValidatedForm.js +12 -6
  4. package/browser/internal/MultiValueMap.js +3 -3
  5. package/browser/internal/hooks-valtio.d.ts +18 -0
  6. package/browser/internal/hooks-valtio.js +110 -0
  7. package/browser/internal/hooks-zustand.d.ts +16 -0
  8. package/browser/internal/hooks-zustand.js +100 -0
  9. package/browser/internal/hydratable.d.ts +14 -0
  10. package/browser/internal/hydratable.js +14 -0
  11. package/browser/internal/immerMiddleware.d.ts +6 -0
  12. package/browser/internal/immerMiddleware.js +7 -0
  13. package/browser/internal/logic/getCheckboxChecked copy.d.ts +1 -0
  14. package/browser/internal/logic/getCheckboxChecked copy.js +9 -0
  15. package/browser/internal/logic/getCheckboxChecked.d.ts +1 -0
  16. package/browser/internal/logic/getCheckboxChecked.js +9 -0
  17. package/browser/internal/logic/getRadioChecked.d.ts +1 -0
  18. package/browser/internal/logic/getRadioChecked.js +5 -0
  19. package/browser/internal/logic/setFieldValue.d.ts +1 -0
  20. package/browser/internal/logic/setFieldValue.js +40 -0
  21. package/browser/internal/logic/setInputValueInForm.d.ts +1 -0
  22. package/browser/internal/logic/setInputValueInForm.js +120 -0
  23. package/browser/internal/setFieldValue.d.ts +20 -0
  24. package/browser/internal/setFieldValue.js +83 -0
  25. package/browser/internal/setFormValues.d.ts +2 -0
  26. package/browser/internal/setFormValues.js +26 -0
  27. package/browser/internal/state/atomUtils.d.ts +38 -0
  28. package/browser/internal/state/atomUtils.js +5 -0
  29. package/browser/internal/state/controlledFields.d.ts +66 -0
  30. package/browser/internal/state/controlledFields.js +93 -0
  31. package/browser/internal/state/setFieldValue.d.ts +0 -0
  32. package/browser/internal/state/setFieldValue.js +1 -0
  33. package/browser/internal/state-valtio.d.ts +62 -0
  34. package/browser/internal/state-valtio.js +69 -0
  35. package/browser/internal/state-zustand.d.ts +47 -0
  36. package/browser/internal/state-zustand.js +85 -0
  37. package/browser/internal/util.d.ts +1 -0
  38. package/browser/internal/util.js +12 -1
  39. package/build/ValidatedForm.d.ts +1 -1
  40. package/build/ValidatedForm.js +11 -5
  41. package/build/internal/MultiValueMap.js +2 -2
  42. package/build/internal/hooks-valtio.d.ts +18 -0
  43. package/build/internal/hooks-valtio.js +128 -0
  44. package/build/internal/hooks-zustand.d.ts +16 -0
  45. package/build/internal/hooks-zustand.js +117 -0
  46. package/build/internal/hydratable.d.ts +14 -0
  47. package/build/internal/hydratable.js +17 -0
  48. package/build/internal/immerMiddleware.d.ts +6 -0
  49. package/build/internal/immerMiddleware.js +14 -0
  50. package/build/internal/logic/getCheckboxChecked.d.ts +1 -0
  51. package/build/internal/logic/getCheckboxChecked.js +13 -0
  52. package/build/internal/logic/getRadioChecked.d.ts +1 -0
  53. package/build/internal/logic/getRadioChecked.js +9 -0
  54. package/build/internal/logic/setFieldValue.d.ts +1 -0
  55. package/build/internal/logic/setFieldValue.js +47 -0
  56. package/build/internal/logic/setInputValueInForm.d.ts +1 -0
  57. package/build/internal/logic/setInputValueInForm.js +127 -0
  58. package/build/internal/setFormValues.d.ts +2 -0
  59. package/build/internal/setFormValues.js +33 -0
  60. package/build/internal/state/atomUtils.d.ts +38 -0
  61. package/build/internal/state/atomUtils.js +13 -0
  62. package/build/internal/state/controlledFields.d.ts +66 -0
  63. package/build/internal/state/controlledFields.js +95 -0
  64. package/build/internal/state-valtio.d.ts +62 -0
  65. package/build/internal/state-valtio.js +83 -0
  66. package/build/internal/state-zustand.d.ts +47 -0
  67. package/build/internal/state-zustand.js +91 -0
  68. package/build/internal/util.d.ts +1 -0
  69. package/build/internal/util.js +16 -1
  70. package/package.json +5 -5
  71. package/src/ValidatedForm.tsx +13 -5
  72. package/src/internal/MultiValueMap.ts +3 -3
  73. package/src/internal/util.ts +13 -1
@@ -0,0 +1,38 @@
1
+ import { Atom } from "jotai";
2
+ export declare type InternalFormId = string | symbol;
3
+ export declare const formAtomFamily: <T>(data: T) => {
4
+ (param: InternalFormId): Atom<T> & {
5
+ write: (get: {
6
+ <Value>(atom: Atom<Value | Promise<Value>>): Value;
7
+ <Value_1>(atom: Atom<Promise<Value_1>>): Value_1;
8
+ <Value_2>(atom: Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
9
+ } & {
10
+ <Value_3>(atom: Atom<Value_3 | Promise<Value_3>>, options: {
11
+ unstable_promise: true;
12
+ }): Value_3 | Promise<Value_3>;
13
+ <Value_4>(atom: Atom<Promise<Value_4>>, options: {
14
+ unstable_promise: true;
15
+ }): Value_4 | Promise<Value_4>;
16
+ <Value_5>(atom: Atom<Value_5>, options: {
17
+ unstable_promise: true;
18
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
19
+ }, set: {
20
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
21
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
22
+ }, update: T | ((prev: T) => T)) => void;
23
+ onMount?: (<S extends import("jotai/core/atom").SetAtom<T | ((prev: T) => T), void>>(setAtom: S) => void | (() => void)) | undefined;
24
+ } & {
25
+ init: T;
26
+ };
27
+ remove(param: InternalFormId): void;
28
+ setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
29
+ };
30
+ export declare type FieldAtomKey = {
31
+ formId: InternalFormId;
32
+ field: string;
33
+ };
34
+ export declare const fieldAtomFamily: <T extends Atom<unknown>>(func: (key: FieldAtomKey) => T) => {
35
+ (param: FieldAtomKey): T;
36
+ remove(param: FieldAtomKey): void;
37
+ setShouldRemove(shouldRemove: ((createdAt: number, param: FieldAtomKey) => boolean) | null): void;
38
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fieldAtomFamily = exports.formAtomFamily = void 0;
7
+ const jotai_1 = require("jotai");
8
+ const utils_1 = require("jotai/utils");
9
+ const isEqual_1 = __importDefault(require("lodash/isEqual"));
10
+ const formAtomFamily = (data) => (0, utils_1.atomFamily)((_) => (0, jotai_1.atom)(data));
11
+ exports.formAtomFamily = formAtomFamily;
12
+ const fieldAtomFamily = (func) => (0, utils_1.atomFamily)(func, isEqual_1.default);
13
+ exports.fieldAtomFamily = fieldAtomFamily;
@@ -0,0 +1,66 @@
1
+ import { PrimitiveAtom } from "jotai";
2
+ import { InternalFormId } from "./atomUtils";
3
+ export declare type ValueSerializer = (val: unknown) => string;
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 setControlledFieldValueAtom: import("jotai").Atom<null> & {
32
+ write: (get: {
33
+ <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
34
+ <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
35
+ <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
36
+ } & {
37
+ <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
38
+ unstable_promise: true;
39
+ }): Value_3 | Promise<Value_3>;
40
+ <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
41
+ unstable_promise: true;
42
+ }): Value_4 | Promise<Value_4>;
43
+ <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
44
+ unstable_promise: true;
45
+ }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
46
+ }, set: {
47
+ <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
48
+ <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
49
+ }, update: {
50
+ formId: InternalFormId;
51
+ field: string;
52
+ value: unknown;
53
+ }) => Promise<void>;
54
+ onMount?: (<S extends (update: {
55
+ formId: InternalFormId;
56
+ field: string;
57
+ value: unknown;
58
+ }) => Promise<void>>(setAtom: S) => void | (() => void)) | undefined;
59
+ } & {
60
+ init: null;
61
+ };
62
+ export declare const useAllControlledFields: (formId: InternalFormId) => Record<string, PrimitiveAtom<unknown>>;
63
+ export declare const useControlledFieldValue: (formId: InternalFormId, field: string) => any;
64
+ export declare const useControllableValue: (formId: InternalFormId, field: string) => readonly [any, (value: unknown) => Promise<void>];
65
+ export declare const useFieldSerializer: (formId: InternalFormId, field: string) => ValueSerializer;
66
+ export declare const useRegisterFieldSerializer: (formId: InternalFormId, field: string, serializer?: ValueSerializer | undefined) => void;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.useRegisterFieldSerializer = exports.useFieldSerializer = exports.useControllableValue = exports.useControlledFieldValue = exports.useAllControlledFields = exports.setControlledFieldValueAtom = exports.controlledFieldsAtom = void 0;
7
+ const jotai_1 = require("jotai");
8
+ const omit_1 = __importDefault(require("lodash/omit"));
9
+ const react_1 = require("react");
10
+ const hooks_1 = require("../hooks");
11
+ const state_1 = require("../state");
12
+ const atomUtils_1 = require("./atomUtils");
13
+ exports.controlledFieldsAtom = (0, atomUtils_1.formAtomFamily)({});
14
+ const refCountAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(0));
15
+ const fieldValueAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
16
+ const fieldValueHydratedAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(false));
17
+ const pendingValidateAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
18
+ const valueSerializerAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)({ serializer: JSON.stringify }));
19
+ const registerAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) => {
20
+ set(refCountAtom({ formId, field }), (prev) => prev + 1);
21
+ const newRefCount = get(refCountAtom({ formId, field }));
22
+ if (newRefCount === 1) {
23
+ set((0, exports.controlledFieldsAtom)(formId), (prev) => ({
24
+ ...prev,
25
+ [field]: fieldValueAtom({ formId, field }),
26
+ }));
27
+ }
28
+ });
29
+ const unregisterAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) => {
30
+ set(refCountAtom({ formId, field }), (prev) => prev - 1);
31
+ const newRefCount = get(refCountAtom({ formId, field }));
32
+ if (newRefCount === 0) {
33
+ set((0, exports.controlledFieldsAtom)(formId), (prev) => (0, omit_1.default)(prev, field));
34
+ fieldValueAtom.remove({ formId, field });
35
+ pendingValidateAtom.remove({ formId, field });
36
+ }
37
+ });
38
+ exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null, async (_get, set, { formId, field, value, }) => {
39
+ set(fieldValueAtom({ formId, field }), value);
40
+ const pending = pendingValidateAtom({ formId, field });
41
+ await new Promise((resolve) => set(pending, resolve));
42
+ set(pending, undefined);
43
+ });
44
+ const useAllControlledFields = (formId) => (0, hooks_1.useFormAtomValue)((0, exports.controlledFieldsAtom)(formId));
45
+ exports.useAllControlledFields = useAllControlledFields;
46
+ const useControlledFieldValue = (formId, field) => {
47
+ const fieldAtom = fieldValueAtom({ formId, field });
48
+ const [value, setValue] = (0, hooks_1.useFormAtom)(fieldAtom);
49
+ const defaultValue = (0, hooks_1.useFieldDefaultValue)(field, { formId });
50
+ const isHydrated = (0, hooks_1.useFormAtomValue)((0, state_1.isHydratedAtom)(formId));
51
+ const [isFieldHydrated, setIsFieldHydrated] = (0, hooks_1.useFormAtom)(fieldValueHydratedAtom({ formId, field }));
52
+ (0, react_1.useEffect)(() => {
53
+ if (isHydrated && !isFieldHydrated) {
54
+ setValue({ formId, field, value: defaultValue });
55
+ setIsFieldHydrated(true);
56
+ }
57
+ }, [
58
+ defaultValue,
59
+ field,
60
+ formId,
61
+ isFieldHydrated,
62
+ isHydrated,
63
+ setIsFieldHydrated,
64
+ setValue,
65
+ ]);
66
+ return isFieldHydrated ? value : defaultValue;
67
+ };
68
+ exports.useControlledFieldValue = useControlledFieldValue;
69
+ const useControllableValue = (formId, field) => {
70
+ const pending = (0, hooks_1.useFormAtomValue)(pendingValidateAtom({ formId, field }));
71
+ (0, react_1.useEffect)(() => {
72
+ pending === null || pending === void 0 ? void 0 : pending();
73
+ }, [pending]);
74
+ const register = (0, hooks_1.useFormUpdateAtom)(registerAtom);
75
+ const unregister = (0, hooks_1.useFormUpdateAtom)(unregisterAtom);
76
+ (0, react_1.useEffect)(() => {
77
+ register({ formId, field });
78
+ return () => unregister({ formId, field });
79
+ }, [field, formId, register, unregister]);
80
+ const setControlledFieldValue = (0, hooks_1.useFormUpdateAtom)(exports.setControlledFieldValueAtom);
81
+ const setValue = (0, react_1.useCallback)((value) => setControlledFieldValue({ formId, field, value }), [field, formId, setControlledFieldValue]);
82
+ const value = (0, exports.useControlledFieldValue)(formId, field);
83
+ return [value, setValue];
84
+ };
85
+ exports.useControllableValue = useControllableValue;
86
+ const useFieldSerializer = (formId, field) => (0, hooks_1.useFormAtomValue)(valueSerializerAtom({ formId, field })).serializer;
87
+ exports.useFieldSerializer = useFieldSerializer;
88
+ const useRegisterFieldSerializer = (formId, field, serializer) => {
89
+ const setSerializer = (0, hooks_1.useFormUpdateAtom)(valueSerializerAtom({ formId, field }));
90
+ (0, react_1.useEffect)(() => {
91
+ if (serializer)
92
+ setSerializer({ serializer });
93
+ }, [serializer, setSerializer]);
94
+ };
95
+ exports.useRegisterFieldSerializer = useRegisterFieldSerializer;
@@ -0,0 +1,62 @@
1
+ import { FieldErrors, TouchedFields } from "..";
2
+ export declare type InternalFormState = {
3
+ hydrated: boolean;
4
+ fieldErrors: FieldErrors;
5
+ isSubmitting: boolean;
6
+ hasBeenSubmitted: boolean;
7
+ touchedFields: TouchedFields;
8
+ action?: string;
9
+ subaction?: string;
10
+ defaultValues: {
11
+ [fieldName: string]: any;
12
+ };
13
+ validateField: (fieldName: string) => Promise<string | null>;
14
+ registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
15
+ setFieldValue: (fieldName: string, value: unknown) => void;
16
+ };
17
+ declare type SyncFormArgs = {
18
+ defaultValues?: {
19
+ [fieldName: string]: any;
20
+ };
21
+ action?: string;
22
+ subaction?: string;
23
+ validateField: InternalFormState["validateField"];
24
+ registerReceiveFocus: InternalFormState["registerReceiveFocus"];
25
+ setFieldValueForForm: InternalFormState["setFieldValue"];
26
+ };
27
+ declare type StoreState = {
28
+ forms: {
29
+ [formId: string | symbol]: InternalFormState;
30
+ };
31
+ };
32
+ export declare const state: StoreState;
33
+ export declare const registerFormSlice: (formId: string | symbol, { registerReceiveFocus, setFieldValueForForm, validateField, action, defaultValues, subaction, }: SyncFormArgs) => void;
34
+ export declare const unregisterFormSlice: (formId: string | symbol) => void;
35
+ export declare const useFormData: (formId: string | symbol) => {
36
+ readonly hydrated: boolean;
37
+ readonly fieldErrors: {
38
+ readonly [x: string]: string;
39
+ };
40
+ readonly isSubmitting: boolean;
41
+ readonly hasBeenSubmitted: boolean;
42
+ readonly touchedFields: {
43
+ readonly [x: string]: boolean;
44
+ };
45
+ readonly action?: string | undefined;
46
+ readonly subaction?: string | undefined;
47
+ readonly defaultValues: {
48
+ readonly [x: string]: any;
49
+ };
50
+ readonly validateField: (fieldName: string) => Promise<string | null>;
51
+ readonly registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
52
+ readonly setFieldValue: (fieldName: string, value: unknown) => void;
53
+ };
54
+ export declare const startSubmit: (formId: string | symbol) => void;
55
+ export declare const endSubmit: (formId: string | symbol) => void;
56
+ export declare const sync: (formId: string | symbol, { defaultValues, action, subaction, registerReceiveFocus, validateField, setFieldValueForForm, }: SyncFormArgs) => void;
57
+ export declare const clearError: (formId: string | symbol, fieldName: string) => void;
58
+ export declare const addError: (formId: string | symbol, fieldName: string, error: string) => void;
59
+ export declare const setTouched: (formId: string | symbol, fieldName: string, touched: boolean) => void;
60
+ export declare const reset: (formId: string | symbol) => void;
61
+ export declare const setFieldErrors: (formId: string | symbol, fieldErrors: FieldErrors) => void;
62
+ export {};
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setFieldErrors = exports.reset = exports.setTouched = exports.addError = exports.clearError = exports.sync = exports.endSubmit = exports.startSubmit = exports.useFormData = exports.unregisterFormSlice = exports.registerFormSlice = exports.state = void 0;
4
+ const valtio_1 = require("valtio");
5
+ exports.state = (0, valtio_1.proxy)({ forms: {} });
6
+ const registerFormSlice = (formId, { registerReceiveFocus, setFieldValueForForm, validateField, action, defaultValues, subaction, }) => {
7
+ exports.state.forms[formId] = {
8
+ hydrated: true,
9
+ defaultValues: defaultValues !== null && defaultValues !== void 0 ? defaultValues : {},
10
+ fieldErrors: {},
11
+ hasBeenSubmitted: false,
12
+ isSubmitting: false,
13
+ touchedFields: {},
14
+ registerReceiveFocus,
15
+ setFieldValue: setFieldValueForForm,
16
+ validateField,
17
+ action,
18
+ subaction,
19
+ };
20
+ };
21
+ exports.registerFormSlice = registerFormSlice;
22
+ const unregisterFormSlice = (formId) => {
23
+ delete exports.state.forms[formId];
24
+ };
25
+ exports.unregisterFormSlice = unregisterFormSlice;
26
+ const unhydratedFormState = {
27
+ hydrated: false,
28
+ fieldErrors: {},
29
+ isSubmitting: false,
30
+ hasBeenSubmitted: false,
31
+ touchedFields: {},
32
+ defaultValues: {},
33
+ validateField: () => Promise.resolve(null),
34
+ registerReceiveFocus: () => () => { },
35
+ setFieldValue: () => { },
36
+ };
37
+ const useFormData = (formId) => {
38
+ var _a;
39
+ const snapshot = (0, valtio_1.useSnapshot)(exports.state);
40
+ return (_a = snapshot.forms[formId]) !== null && _a !== void 0 ? _a : unhydratedFormState;
41
+ };
42
+ exports.useFormData = useFormData;
43
+ const startSubmit = (formId) => {
44
+ exports.state.forms[formId].isSubmitting = true;
45
+ exports.state.forms[formId].hasBeenSubmitted = true;
46
+ };
47
+ exports.startSubmit = startSubmit;
48
+ const endSubmit = (formId) => {
49
+ exports.state.forms[formId].isSubmitting = false;
50
+ };
51
+ exports.endSubmit = endSubmit;
52
+ const sync = (formId, { defaultValues, action, subaction, registerReceiveFocus, validateField, setFieldValueForForm, }) => {
53
+ exports.state.forms[formId].defaultValues = defaultValues !== null && defaultValues !== void 0 ? defaultValues : {};
54
+ exports.state.forms[formId].action = action;
55
+ exports.state.forms[formId].subaction = subaction;
56
+ exports.state.forms[formId].registerReceiveFocus = registerReceiveFocus;
57
+ exports.state.forms[formId].validateField = validateField;
58
+ exports.state.forms[formId].hydrated = true;
59
+ exports.state.forms[formId].setFieldValue = setFieldValueForForm;
60
+ };
61
+ exports.sync = sync;
62
+ const clearError = (formId, fieldName) => {
63
+ delete exports.state.forms[formId].fieldErrors[fieldName];
64
+ };
65
+ exports.clearError = clearError;
66
+ const addError = (formId, fieldName, error) => {
67
+ exports.state.forms[formId].fieldErrors[fieldName] = error;
68
+ };
69
+ exports.addError = addError;
70
+ const setTouched = (formId, fieldName, touched) => {
71
+ exports.state.forms[formId].touchedFields[fieldName] = touched;
72
+ };
73
+ exports.setTouched = setTouched;
74
+ const reset = (formId) => {
75
+ exports.state.forms[formId].fieldErrors = {};
76
+ exports.state.forms[formId].touchedFields = {};
77
+ exports.state.forms[formId].hasBeenSubmitted = false;
78
+ };
79
+ exports.reset = reset;
80
+ const setFieldErrors = (formId, fieldErrors) => {
81
+ exports.state.forms[formId].fieldErrors = fieldErrors;
82
+ };
83
+ exports.setFieldErrors = setFieldErrors;
@@ -0,0 +1,47 @@
1
+ import { FieldErrors, TouchedFields } from "..";
2
+ export declare type InternalFormState = {
3
+ hydrated: boolean;
4
+ fieldErrors: FieldErrors;
5
+ isSubmitting: boolean;
6
+ hasBeenSubmitted: boolean;
7
+ touchedFields: TouchedFields;
8
+ action?: string;
9
+ subaction?: string;
10
+ defaultValues: {
11
+ [fieldName: string]: any;
12
+ };
13
+ validateField: (fieldName: string) => Promise<string | null>;
14
+ registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
15
+ setFieldValue: (fieldName: string, value: unknown) => void;
16
+ };
17
+ declare type Helpers = {
18
+ startSubmit: () => void;
19
+ endSubmit: () => void;
20
+ sync: (args: SyncFormArgs) => void;
21
+ clearError: (name: string) => void;
22
+ addError: (name: string, error: string) => void;
23
+ setTouched: (name: string, touched: boolean) => void;
24
+ reset: () => void;
25
+ setFieldErrors: (fieldErrors: FieldErrors) => void;
26
+ register: (init: SyncFormArgs) => void;
27
+ unregister: () => void;
28
+ };
29
+ declare type SyncFormArgs = {
30
+ defaultValues?: {
31
+ [fieldName: string]: any;
32
+ };
33
+ action?: string;
34
+ subaction?: string;
35
+ validateField: InternalFormState["validateField"];
36
+ registerReceiveFocus: InternalFormState["registerReceiveFocus"];
37
+ setFieldValueForForm: InternalFormState["setFieldValue"];
38
+ };
39
+ declare type StoreState = {
40
+ forms: {
41
+ [formId: string | symbol]: InternalFormState;
42
+ };
43
+ form: (formId: string | symbol) => InternalFormState;
44
+ helpers: (formId: string | symbol) => Helpers;
45
+ };
46
+ export declare const useStore: import("zustand").UseBoundStore<StoreState, import("zustand").StoreApi<StoreState>>;
47
+ export {};
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.useStore = void 0;
7
+ const zustand_1 = __importDefault(require("zustand"));
8
+ const immerMiddleware_1 = require("./immerMiddleware");
9
+ const unhydratedFormState = {
10
+ hydrated: false,
11
+ fieldErrors: {},
12
+ isSubmitting: false,
13
+ hasBeenSubmitted: false,
14
+ touchedFields: {},
15
+ defaultValues: {},
16
+ validateField: () => Promise.resolve(null),
17
+ registerReceiveFocus: () => () => { },
18
+ setFieldValue: () => { },
19
+ // clearError: () => {},
20
+ // addError: () => {},
21
+ // setTouched: () => {},
22
+ // reset: () => {},
23
+ // startSubmit: () => {},
24
+ // endSubmit: () => {},
25
+ // sync: () => {},
26
+ // setFieldErrors: () => {},
27
+ };
28
+ exports.useStore = (0, zustand_1.default)((0, immerMiddleware_1.immer)((set, get) => ({
29
+ forms: {},
30
+ form: (formId) => { var _a; return (_a = get().forms[formId]) !== null && _a !== void 0 ? _a : unhydratedFormState; },
31
+ helpers: (formId) => ({
32
+ clearError: (name) => set((state) => {
33
+ delete state.forms[formId].fieldErrors[name];
34
+ }),
35
+ addError: (name, error) => set((state) => {
36
+ state.forms[formId].fieldErrors[name] = error;
37
+ }),
38
+ setTouched: (name, touched) => set((state) => {
39
+ state.forms[formId].touchedFields[name] = touched;
40
+ }),
41
+ reset: () => set((state) => {
42
+ state.forms[formId].fieldErrors = {};
43
+ state.forms[formId].touchedFields = {};
44
+ state.forms[formId].hasBeenSubmitted = false;
45
+ }),
46
+ startSubmit: () => set((state) => {
47
+ state.forms[formId].hasBeenSubmitted = true;
48
+ state.forms[formId].isSubmitting = true;
49
+ }),
50
+ endSubmit: () => set((state) => {
51
+ state.forms[formId].isSubmitting = false;
52
+ }),
53
+ setFieldErrors: (fieldErrors) => {
54
+ set((state) => {
55
+ state.forms[formId].fieldErrors = fieldErrors;
56
+ });
57
+ },
58
+ sync: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => set((state) => {
59
+ state.forms[formId].defaultValues = defaultValues !== null && defaultValues !== void 0 ? defaultValues : {};
60
+ state.forms[formId].action = action;
61
+ state.forms[formId].subaction = subaction;
62
+ state.forms[formId].registerReceiveFocus = registerReceiveFocus;
63
+ state.forms[formId].validateField = validateField;
64
+ state.forms[formId].hydrated = true;
65
+ state.forms[formId].setFieldValue = setFieldValueForForm;
66
+ }),
67
+ unregister: () => {
68
+ set((state) => {
69
+ delete state.forms[formId];
70
+ });
71
+ },
72
+ register: ({ defaultValues, action, subaction, validateField, registerReceiveFocus, setFieldValueForForm, }) => {
73
+ set((state) => {
74
+ state.forms[formId] = {
75
+ defaultValues: defaultValues !== null && defaultValues !== void 0 ? defaultValues : {},
76
+ setFieldValue: setFieldValueForForm,
77
+ registerReceiveFocus,
78
+ validateField,
79
+ action,
80
+ subaction,
81
+ hydrated: true,
82
+ fieldErrors: {},
83
+ isSubmitting: false,
84
+ hasBeenSubmitted: false,
85
+ touchedFields: {},
86
+ // helpers
87
+ };
88
+ });
89
+ },
90
+ }),
91
+ })));
@@ -2,3 +2,4 @@ import type React from "react";
2
2
  export declare const omit: (obj: any, ...keys: string[]) => any;
3
3
  export declare const mergeRefs: <T = any>(refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined)[]) => (instance: T | null) => void;
4
4
  export declare const useIsomorphicLayoutEffect: typeof React.useEffect;
5
+ export declare const useDeepEqualsMemo: <T>(item: T) => T;
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.useIsomorphicLayoutEffect = exports.mergeRefs = exports.omit = void 0;
6
+ exports.useDeepEqualsMemo = exports.useIsomorphicLayoutEffect = exports.mergeRefs = exports.omit = void 0;
7
+ const isEqual_1 = __importDefault(require("lodash/isEqual"));
4
8
  const react_1 = require("react");
5
9
  const omit = (obj, ...keys) => {
6
10
  const result = { ...obj };
@@ -24,3 +28,14 @@ const mergeRefs = (refs) => {
24
28
  };
25
29
  exports.mergeRefs = mergeRefs;
26
30
  exports.useIsomorphicLayoutEffect = typeof window !== "undefined" ? react_1.useLayoutEffect : react_1.useEffect;
31
+ const useDeepEqualsMemo = (item) => {
32
+ const ref = (0, react_1.useRef)(item);
33
+ const areEqual = ref.current === item || (0, isEqual_1.default)(ref.current, item);
34
+ (0, react_1.useEffect)(() => {
35
+ if (!areEqual) {
36
+ ref.current = item;
37
+ }
38
+ });
39
+ return areEqual ? ref.current : item;
40
+ };
41
+ exports.useDeepEqualsMemo = useDeepEqualsMemo;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "4.1.0",
3
+ "version": "4.1.4-beta.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",
@@ -33,13 +33,13 @@
33
33
  "validation"
34
34
  ],
35
35
  "peerDependencies": {
36
- "@remix-run/react": "^1.0.6",
37
- "@remix-run/server-runtime": "^1.0.6",
36
+ "@remix-run/react": "1.x",
37
+ "@remix-run/server-runtime": "1.x",
38
38
  "react": "^17.0.2"
39
39
  },
40
40
  "devDependencies": {
41
- "@remix-run/react": "^1.0.6",
42
- "@remix-run/server-runtime": "^1.0.6",
41
+ "@remix-run/react": "^1.2.1",
42
+ "@remix-run/server-runtime": "^1.2.1",
43
43
  "@types/lodash": "^4.14.178",
44
44
  "@types/react": "^17.0.37",
45
45
  "fetch-blob": "^3.1.3",
@@ -38,6 +38,7 @@ import {
38
38
  import { useSubmitComplete } from "./internal/submissionCallbacks";
39
39
  import {
40
40
  mergeRefs,
41
+ useDeepEqualsMemo,
41
42
  useIsomorphicLayoutEffect as useLayoutEffect,
42
43
  } from "./internal/util";
43
44
  import { FieldErrors, Validator } from "./validation/types";
@@ -206,7 +207,7 @@ export function ValidatedForm<DataType>({
206
207
  children,
207
208
  fetcher,
208
209
  action,
209
- defaultValues: providedDefaultValues,
210
+ defaultValues: unMemoizedDefaults,
210
211
  formRef: formRefProp,
211
212
  onReset,
212
213
  subaction,
@@ -219,6 +220,7 @@ export function ValidatedForm<DataType>({
219
220
  }: FormProps<DataType>) {
220
221
  const formId = useFormId(id);
221
222
  const formAtom = useFormAtom(formId);
223
+ const providedDefaultValues = useDeepEqualsMemo(unMemoizedDefaults);
222
224
  const contextValue = useMemo<InternalFormContextValue>(
223
225
  () => ({
224
226
  formId,
@@ -325,9 +327,9 @@ export function ValidatedForm<DataType>({
325
327
  }
326
328
  }
327
329
 
328
- window.addEventListener("click", handleClick);
330
+ window.addEventListener("click", handleClick, { capture: true });
329
331
  return () => {
330
- window.removeEventListener("click", handleClick);
332
+ window.removeEventListener("click", handleClick, { capture: true });
331
333
  };
332
334
  }, []);
333
335
 
@@ -352,12 +354,18 @@ export function ValidatedForm<DataType>({
352
354
  return;
353
355
  }
354
356
 
355
- if (fetcher) fetcher.submit(clickedButtonRef.current || e.currentTarget);
357
+ // We deviate from the remix code here a bit because of our async submit.
358
+ // In remix's `FormImpl`, they use `event.currentTarget` to get the form,
359
+ // but we already have the form in `formRef.current` so we can just use that.
360
+ // If we use `event.currentTarget` here, it will break because `currentTarget`
361
+ // will have changed since the start of the submission.
362
+ if (fetcher) fetcher.submit(clickedButtonRef.current || formRef.current);
356
363
  else
357
- submit(clickedButtonRef.current || e.currentTarget, {
364
+ submit(clickedButtonRef.current || formRef.current, {
358
365
  method,
359
366
  replace,
360
367
  });
368
+
361
369
  clickedButtonRef.current = null;
362
370
  }
363
371
  };
@@ -1,4 +1,4 @@
1
- import { useRef } from "react";
1
+ import { useCallback, useRef } from "react";
2
2
 
3
3
  export class MultiValueMap<Key, Value> {
4
4
  private dict: Map<Key, Value[]> = new Map();
@@ -30,9 +30,9 @@ export class MultiValueMap<Key, Value> {
30
30
 
31
31
  export const useMultiValueMap = <Key, Value>() => {
32
32
  const ref = useRef<MultiValueMap<Key, Value> | null>(null);
33
- return () => {
33
+ return useCallback(() => {
34
34
  if (ref.current) return ref.current;
35
35
  ref.current = new MultiValueMap();
36
36
  return ref.current;
37
- };
37
+ }, []);
38
38
  };
@@ -1,5 +1,6 @@
1
+ import isEqual from "lodash/isEqual";
1
2
  import type React from "react";
2
- import { useEffect, useLayoutEffect } from "react";
3
+ import { useEffect, useLayoutEffect, useRef } from "react";
3
4
 
4
5
  export const omit = (obj: any, ...keys: string[]) => {
5
6
  const result = { ...obj };
@@ -25,3 +26,14 @@ export const mergeRefs = <T = any>(
25
26
 
26
27
  export const useIsomorphicLayoutEffect =
27
28
  typeof window !== "undefined" ? useLayoutEffect : useEffect;
29
+
30
+ export const useDeepEqualsMemo = <T>(item: T): T => {
31
+ const ref = useRef<T>(item);
32
+ const areEqual = ref.current === item || isEqual(ref.current, item);
33
+ useEffect(() => {
34
+ if (!areEqual) {
35
+ ref.current = item;
36
+ }
37
+ });
38
+ return areEqual ? ref.current : item;
39
+ };