remix-validated-form 4.1.8 → 4.3.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 (39) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/README.md +7 -5
  3. package/browser/ValidatedForm.js +6 -2
  4. package/browser/hooks.d.ts +2 -0
  5. package/browser/hooks.js +10 -0
  6. package/browser/internal/MultiValueMap.d.ts +2 -0
  7. package/browser/internal/MultiValueMap.js +4 -0
  8. package/browser/internal/getInputProps.js +2 -1
  9. package/browser/internal/reset.d.ts +28 -0
  10. package/browser/internal/reset.js +13 -0
  11. package/browser/internal/state/controlledFields.d.ts +62 -9
  12. package/browser/internal/state/controlledFields.js +36 -36
  13. package/browser/internal/state.d.ts +0 -27
  14. package/browser/internal/state.js +0 -5
  15. package/browser/server.d.ts +1 -1
  16. package/browser/server.js +2 -2
  17. package/build/ValidatedForm.js +6 -2
  18. package/build/hooks.d.ts +2 -0
  19. package/build/hooks.js +13 -1
  20. package/build/internal/MultiValueMap.d.ts +2 -0
  21. package/build/internal/MultiValueMap.js +4 -0
  22. package/build/internal/getInputProps.js +2 -1
  23. package/build/internal/reset.d.ts +28 -0
  24. package/build/internal/reset.js +19 -0
  25. package/build/internal/state/controlledFields.d.ts +62 -9
  26. package/build/internal/state/controlledFields.js +39 -40
  27. package/build/internal/state.d.ts +0 -27
  28. package/build/internal/state.js +1 -6
  29. package/build/server.d.ts +1 -1
  30. package/build/server.js +2 -2
  31. package/package.json +2 -2
  32. package/src/ValidatedForm.tsx +6 -2
  33. package/src/hooks.ts +15 -0
  34. package/src/internal/MultiValueMap.ts +6 -0
  35. package/src/internal/getInputProps.ts +2 -1
  36. package/src/internal/reset.ts +26 -0
  37. package/src/internal/state/controlledFields.ts +170 -0
  38. package/src/internal/state.ts +0 -8
  39. package/src/server.ts +3 -2
@@ -0,0 +1,19 @@
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.resetAtom = void 0;
7
+ const jotai_1 = require("jotai");
8
+ const utils_1 = require("jotai/utils");
9
+ const get_1 = __importDefault(require("lodash/get"));
10
+ const state_1 = require("./state");
11
+ const controlledFields_1 = require("./state/controlledFields");
12
+ exports.resetAtom = (0, utils_1.atomFamily)((formId) => (0, jotai_1.atom)(null, (get, set) => {
13
+ set((0, state_1.fieldErrorsAtom)(formId), {});
14
+ set((0, state_1.touchedFieldsAtom)(formId), {});
15
+ set((0, state_1.hasBeenSubmittedAtom)(formId), false);
16
+ const { defaultValues } = get((0, state_1.formPropsAtom)(formId));
17
+ const controlledFields = get((0, controlledFields_1.controlledFieldsAtom)(formId));
18
+ Object.entries(controlledFields).forEach(([name, atom]) => set(atom, (0, get_1.default)(defaultValues, name)));
19
+ }));
@@ -1,6 +1,6 @@
1
1
  import { PrimitiveAtom } from "jotai";
2
- import { InternalFormId } from "./atomUtils";
3
- export declare type ValueSerializer = (val: unknown) => string;
2
+ import { InternalFormContextValue } from "../formContext";
3
+ import { FieldAtomKey, InternalFormId } from "./atomUtils";
4
4
  export declare const controlledFieldsAtom: {
5
5
  (param: InternalFormId): import("jotai").Atom<Record<string, PrimitiveAtom<unknown>>> & {
6
6
  write: (get: {
@@ -28,6 +28,60 @@ export declare const controlledFieldsAtom: {
28
28
  remove(param: InternalFormId): void;
29
29
  setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
30
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
+ };
31
85
  export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
32
86
  write: (get: {
33
87
  <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
@@ -50,17 +104,16 @@ export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
50
104
  formId: InternalFormId;
51
105
  field: string;
52
106
  value: unknown;
53
- }) => Promise<void>;
107
+ }) => void;
54
108
  onMount?: (<S extends (update: {
55
109
  formId: InternalFormId;
56
110
  field: string;
57
111
  value: unknown;
58
- }) => Promise<void>>(setAtom: S) => void | (() => void)) | undefined;
112
+ }) => void>(setAtom: S) => void | (() => void)) | undefined;
59
113
  } & {
60
114
  init: null;
61
115
  };
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;
116
+ export declare const useControlledFieldValue: (context: InternalFormContextValue, field: string) => any;
117
+ 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>;
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.useRegisterFieldSerializer = exports.useFieldSerializer = exports.useControllableValue = exports.useControlledFieldValue = exports.useAllControlledFields = exports.setControlledFieldValueAtom = exports.controlledFieldsAtom = void 0;
6
+ exports.useAwaitValue = exports.useUpdateControllableValue = exports.useControllableValue = exports.useControlledFieldValue = exports.setControlledFieldValueAtom = exports.resolveValueUpdateAtom = exports.valueUpdatePromiseAtom = exports.controlledFieldsAtom = void 0;
7
7
  const jotai_1 = require("jotai");
8
+ const utils_1 = require("jotai/utils");
8
9
  const omit_1 = __importDefault(require("lodash/omit"));
9
10
  const react_1 = require("react");
10
11
  const hooks_1 = require("../hooks");
@@ -14,14 +15,8 @@ exports.controlledFieldsAtom = (0, atomUtils_1.formAtomFamily)({});
14
15
  const refCountAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(0));
15
16
  const fieldValueAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
16
17
  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)({
19
- serializer: (value) => {
20
- if (value === undefined)
21
- return "";
22
- return JSON.stringify(value);
23
- },
24
- }));
18
+ exports.valueUpdatePromiseAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
19
+ exports.resolveValueUpdateAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
25
20
  const registerAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) => {
26
21
  set(refCountAtom({ formId, field }), (prev) => prev + 1);
27
22
  const newRefCount = get(refCountAtom({ formId, field }));
@@ -40,24 +35,27 @@ const unregisterAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) =>
40
35
  if (newRefCount === 0) {
41
36
  set((0, exports.controlledFieldsAtom)(formId), (prev) => (0, omit_1.default)(prev, field));
42
37
  fieldValueAtom.remove({ formId, field });
43
- pendingValidateAtom.remove({ formId, field });
38
+ exports.resolveValueUpdateAtom.remove({ formId, field });
44
39
  fieldValueHydratedAtom.remove({ formId, field });
45
40
  }
46
41
  });
47
- exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null, async (_get, set, { formId, field, value, }) => {
42
+ exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null, (_get, set, { formId, field, value, }) => {
48
43
  set(fieldValueAtom({ formId, field }), value);
49
- const pending = pendingValidateAtom({ formId, field });
50
- await new Promise((resolve) => set(pending, resolve));
51
- set(pending, undefined);
44
+ const resolveAtom = (0, exports.resolveValueUpdateAtom)({ formId, field });
45
+ const promiseAtom = (0, exports.valueUpdatePromiseAtom)({ formId, field });
46
+ const promise = new Promise((resolve) => set(resolveAtom, () => {
47
+ resolve();
48
+ set(resolveAtom, undefined);
49
+ set(promiseAtom, undefined);
50
+ }));
51
+ set(promiseAtom, promise);
52
52
  });
53
- const useAllControlledFields = (formId) => (0, hooks_1.useFormAtomValue)((0, exports.controlledFieldsAtom)(formId));
54
- exports.useAllControlledFields = useAllControlledFields;
55
- const useControlledFieldValue = (formId, field) => {
56
- const fieldAtom = fieldValueAtom({ formId, field });
53
+ const useControlledFieldValue = (context, field) => {
54
+ const fieldAtom = fieldValueAtom({ formId: context.formId, field });
57
55
  const [value, setValue] = (0, hooks_1.useFormAtom)(fieldAtom);
58
- const defaultValue = (0, hooks_1.useFieldDefaultValue)(field, { formId });
59
- const isHydrated = (0, hooks_1.useFormAtomValue)((0, state_1.isHydratedAtom)(formId));
60
- const [isFieldHydrated, setIsFieldHydrated] = (0, hooks_1.useFormAtom)(fieldValueHydratedAtom({ formId, field }));
56
+ const defaultValue = (0, hooks_1.useFieldDefaultValue)(field, context);
57
+ const isHydrated = (0, hooks_1.useFormAtomValue)((0, state_1.isHydratedAtom)(context.formId));
58
+ const [isFieldHydrated, setIsFieldHydrated] = (0, hooks_1.useFormAtom)(fieldValueHydratedAtom({ formId: context.formId, field }));
61
59
  (0, react_1.useEffect)(() => {
62
60
  if (isHydrated && !isFieldHydrated) {
63
61
  setValue(defaultValue);
@@ -66,7 +64,7 @@ const useControlledFieldValue = (formId, field) => {
66
64
  }, [
67
65
  defaultValue,
68
66
  field,
69
- formId,
67
+ context.formId,
70
68
  isFieldHydrated,
71
69
  isHydrated,
72
70
  setIsFieldHydrated,
@@ -75,30 +73,31 @@ const useControlledFieldValue = (formId, field) => {
75
73
  return isFieldHydrated ? value : defaultValue;
76
74
  };
77
75
  exports.useControlledFieldValue = useControlledFieldValue;
78
- const useControllableValue = (formId, field) => {
79
- const pending = (0, hooks_1.useFormAtomValue)(pendingValidateAtom({ formId, field }));
76
+ const useControllableValue = (context, field) => {
77
+ const resolveUpdate = (0, hooks_1.useFormAtomValue)((0, exports.resolveValueUpdateAtom)({ formId: context.formId, field }));
80
78
  (0, react_1.useEffect)(() => {
81
- pending === null || pending === void 0 ? void 0 : pending();
82
- }, [pending]);
79
+ resolveUpdate === null || resolveUpdate === void 0 ? void 0 : resolveUpdate();
80
+ }, [resolveUpdate]);
83
81
  const register = (0, hooks_1.useFormUpdateAtom)(registerAtom);
84
82
  const unregister = (0, hooks_1.useFormUpdateAtom)(unregisterAtom);
85
83
  (0, react_1.useEffect)(() => {
86
- register({ formId, field });
87
- return () => unregister({ formId, field });
88
- }, [field, formId, register, unregister]);
84
+ register({ formId: context.formId, field });
85
+ return () => unregister({ formId: context.formId, field });
86
+ }, [context.formId, field, register, unregister]);
89
87
  const setControlledFieldValue = (0, hooks_1.useFormUpdateAtom)(exports.setControlledFieldValueAtom);
90
- const setValue = (0, react_1.useCallback)((value) => setControlledFieldValue({ formId, field, value }), [field, formId, setControlledFieldValue]);
91
- const value = (0, exports.useControlledFieldValue)(formId, field);
88
+ const setValue = (0, react_1.useCallback)((value) => setControlledFieldValue({ formId: context.formId, field, value }), [field, context.formId, setControlledFieldValue]);
89
+ const value = (0, exports.useControlledFieldValue)(context, field);
92
90
  return [value, setValue];
93
91
  };
94
92
  exports.useControllableValue = useControllableValue;
95
- const useFieldSerializer = (formId, field) => (0, hooks_1.useFormAtomValue)(valueSerializerAtom({ formId, field })).serializer;
96
- exports.useFieldSerializer = useFieldSerializer;
97
- const useRegisterFieldSerializer = (formId, field, serializer) => {
98
- const setSerializer = (0, hooks_1.useFormUpdateAtom)(valueSerializerAtom({ formId, field }));
99
- (0, react_1.useEffect)(() => {
100
- if (serializer)
101
- setSerializer({ serializer });
102
- }, [serializer, setSerializer]);
93
+ const useUpdateControllableValue = (formId) => {
94
+ const setControlledFieldValue = (0, hooks_1.useFormUpdateAtom)(exports.setControlledFieldValueAtom);
95
+ return (0, react_1.useCallback)((field, value) => setControlledFieldValue({ formId, field, value }), [formId, setControlledFieldValue]);
96
+ };
97
+ exports.useUpdateControllableValue = useUpdateControllableValue;
98
+ const useAwaitValue = (formId) => {
99
+ return (0, utils_1.useAtomCallback)((0, react_1.useCallback)(async (get, _set, field) => {
100
+ await get((0, exports.valueUpdatePromiseAtom)({ formId, field }));
101
+ }, [formId]));
103
102
  };
104
- exports.useRegisterFieldSerializer = useRegisterFieldSerializer;
103
+ exports.useAwaitValue = useAwaitValue;
@@ -206,33 +206,6 @@ export declare const isValidAtom: {
206
206
  remove(param: InternalFormId): void;
207
207
  setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
208
208
  };
209
- export declare const resetAtom: {
210
- (param: InternalFormId): import("jotai").Atom<null> & {
211
- write: (get: {
212
- <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
213
- <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
214
- <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
215
- } & {
216
- <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
217
- unstable_promise: true;
218
- }): Value_3 | Promise<Value_3>;
219
- <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
220
- unstable_promise: true;
221
- }): Value_4 | Promise<Value_4>;
222
- <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
223
- unstable_promise: true;
224
- }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
225
- }, set: {
226
- <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
227
- <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
228
- }, update: unknown) => void;
229
- onMount?: (<S extends (update?: unknown) => void>(setAtom: S) => void | (() => void)) | undefined;
230
- } & {
231
- init: null;
232
- };
233
- remove(param: InternalFormId): void;
234
- setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
235
- };
236
209
  export declare const startSubmitAtom: {
237
210
  (param: InternalFormId): import("jotai").Atom<null> & {
238
211
  write: (get: {
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.fieldDefaultValueAtom = exports.fieldErrorAtom = exports.fieldTouchedAtom = exports.setFieldErrorAtom = exports.setTouchedAtom = exports.endSubmitAtom = exports.startSubmitAtom = exports.resetAtom = exports.isValidAtom = exports.cleanupFormState = exports.formElementAtom = exports.formPropsAtom = exports.touchedFieldsAtom = exports.fieldErrorsAtom = exports.hasBeenSubmittedAtom = exports.isSubmittingAtom = exports.isHydratedAtom = exports.ATOM_SCOPE = void 0;
6
+ exports.fieldDefaultValueAtom = exports.fieldErrorAtom = exports.fieldTouchedAtom = exports.setFieldErrorAtom = exports.setTouchedAtom = exports.endSubmitAtom = exports.startSubmitAtom = exports.isValidAtom = exports.cleanupFormState = exports.formElementAtom = exports.formPropsAtom = exports.touchedFieldsAtom = exports.fieldErrorsAtom = exports.hasBeenSubmittedAtom = exports.isSubmittingAtom = exports.isHydratedAtom = exports.ATOM_SCOPE = void 0;
7
7
  const jotai_1 = require("jotai");
8
8
  const utils_1 = require("jotai/utils");
9
9
  const omit_1 = __importDefault(require("lodash/omit"));
@@ -33,11 +33,6 @@ const cleanupFormState = (formId) => {
33
33
  };
34
34
  exports.cleanupFormState = cleanupFormState;
35
35
  exports.isValidAtom = (0, utils_1.atomFamily)((formId) => (0, jotai_1.atom)((get) => Object.keys(get((0, exports.fieldErrorsAtom)(formId))).length === 0));
36
- exports.resetAtom = (0, utils_1.atomFamily)((formId) => (0, jotai_1.atom)(null, (_get, set) => {
37
- set((0, exports.fieldErrorsAtom)(formId), {});
38
- set((0, exports.touchedFieldsAtom)(formId), {});
39
- set((0, exports.hasBeenSubmittedAtom)(formId), false);
40
- }));
41
36
  exports.startSubmitAtom = (0, utils_1.atomFamily)((formId) => (0, jotai_1.atom)(null, (_get, set) => {
42
37
  set((0, exports.isSubmittingAtom)(formId), true);
43
38
  set((0, exports.hasBeenSubmittedAtom)(formId), true);
package/build/server.d.ts CHANGED
@@ -14,7 +14,7 @@ import { ValidatorError } from "./validation/types";
14
14
  * if (result.error) return validationError(result.error, result.submittedData);
15
15
  * ```
16
16
  */
17
- export declare function validationError(error: ValidatorError, repopulateFields?: unknown): Response;
17
+ export declare function validationError(error: ValidatorError, repopulateFields?: unknown, init?: ResponseInit): Response;
18
18
  export declare type FormDefaults = {
19
19
  [formDefaultsKey: `${typeof FORM_DEFAULTS_FIELD}_${string}`]: any;
20
20
  };
package/build/server.js CHANGED
@@ -17,13 +17,13 @@ const constants_1 = require("./internal/constants");
17
17
  * if (result.error) return validationError(result.error, result.submittedData);
18
18
  * ```
19
19
  */
20
- function validationError(error, repopulateFields) {
20
+ function validationError(error, repopulateFields, init) {
21
21
  return (0, server_runtime_1.json)({
22
22
  fieldErrors: error.fieldErrors,
23
23
  subaction: error.subaction,
24
24
  repopulateFields,
25
25
  formId: error.formId,
26
- }, { status: 422 });
26
+ }, { status: 422, ...init });
27
27
  }
28
28
  exports.validationError = validationError;
29
29
  const setFormDefaults = (formId, defaultValues) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "4.1.8",
3
+ "version": "4.3.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",
@@ -35,7 +35,7 @@
35
35
  "peerDependencies": {
36
36
  "@remix-run/react": "1.x",
37
37
  "@remix-run/server-runtime": "1.x",
38
- "react": "^17.0.2"
38
+ "react": "^17.0.2 || ^18.0.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@remix-run/react": "^1.2.1",
@@ -1,4 +1,5 @@
1
1
  import { Form as RemixForm, useFetcher, useSubmit } from "@remix-run/react";
2
+ import { useAtomCallback } from "jotai/utils";
2
3
  import uniq from "lodash/uniq";
3
4
  import React, {
4
5
  ComponentProps,
@@ -24,6 +25,7 @@ import {
24
25
  useHasActiveFormSubmit,
25
26
  } from "./internal/hooks";
26
27
  import { MultiValueMap, useMultiValueMap } from "./internal/MultiValueMap";
28
+ import { resetAtom } from "./internal/reset";
27
29
  import {
28
30
  cleanupFormState,
29
31
  endSubmitAtom,
@@ -31,11 +33,11 @@ import {
31
33
  formElementAtom,
32
34
  formPropsAtom,
33
35
  isHydratedAtom,
34
- resetAtom,
35
36
  setFieldErrorAtom,
36
37
  startSubmitAtom,
37
38
  SyncedFormProps,
38
39
  } from "./internal/state";
40
+ import { useAwaitValue } from "./internal/state/controlledFields";
39
41
  import { useSubmitComplete } from "./internal/submissionCallbacks";
40
42
  import {
41
43
  mergeRefs,
@@ -246,9 +248,11 @@ export function ValidatedForm<DataType>({
246
248
  return () => cleanupFormState(formId);
247
249
  }, [formId, setHydrated]);
248
250
 
251
+ const awaitValue = useAwaitValue(formId);
249
252
  const validateField: SyncedFormProps["validateField"] = useCallback(
250
253
  async (field) => {
251
254
  invariant(formRef.current, "Cannot find reference to form");
255
+ await awaitValue(field);
252
256
  const { error } = await validator.validateField(
253
257
  getDataFromForm(formRef.current),
254
258
  field
@@ -262,7 +266,7 @@ export function ValidatedForm<DataType>({
262
266
  return null;
263
267
  }
264
268
  },
265
- [setFieldError, validator]
269
+ [awaitValue, setFieldError, validator]
266
270
  );
267
271
 
268
272
  const customFocusHandlers = useMultiValueMap<string, () => void>();
package/src/hooks.ts CHANGED
@@ -17,6 +17,10 @@ import {
17
17
  isSubmittingAtom,
18
18
  isValidAtom,
19
19
  } from "./internal/state";
20
+ import {
21
+ useControllableValue,
22
+ useUpdateControllableValue,
23
+ } from "./internal/state/controlledFields";
20
24
 
21
25
  /**
22
26
  * Returns whether or not the parent form is currently being submitted.
@@ -148,3 +152,14 @@ export const useField = (
148
152
 
149
153
  return field;
150
154
  };
155
+
156
+ export const useControlField = <T>(name: string, formId?: string) => {
157
+ const context = useInternalFormContext(formId, "useControlField");
158
+ const [value, setValue] = useControllableValue(context, name);
159
+ return [value as T, setValue as (value: T) => void] as const;
160
+ };
161
+
162
+ export const useUpdateControlledField = (formId?: string) => {
163
+ const context = useInternalFormContext(formId, "useControlField");
164
+ return useUpdateControllableValue(context.formId);
165
+ };
@@ -11,6 +11,10 @@ export class MultiValueMap<Key, Value> {
11
11
  }
12
12
  };
13
13
 
14
+ delete = (key: Key) => {
15
+ this.dict.delete(key);
16
+ };
17
+
14
18
  remove = (key: Key, value: Value) => {
15
19
  if (!this.dict.has(key)) return;
16
20
  const array = this.dict.get(key)!;
@@ -25,6 +29,8 @@ export class MultiValueMap<Key, Value> {
25
29
 
26
30
  entries = (): IterableIterator<[Key, Value[]]> => this.dict.entries();
27
31
 
32
+ values = (): IterableIterator<Value[]> => this.dict.values();
33
+
28
34
  has = (key: Key): boolean => this.dict.has(key);
29
35
  }
30
36
 
@@ -84,7 +84,8 @@ export const createGetInputProps = ({
84
84
  inputProps.defaultChecked = getCheckboxChecked(props.value, defaultValue);
85
85
  } else if (props.type === "radio") {
86
86
  inputProps.defaultChecked = getRadioChecked(props.value, defaultValue);
87
- } else {
87
+ } else if (props.value === undefined) {
88
+ // We should only set the defaultValue if the input is uncontrolled.
88
89
  inputProps.defaultValue = defaultValue;
89
90
  }
90
91
 
@@ -0,0 +1,26 @@
1
+ import { atom } from "jotai";
2
+ import { atomFamily } from "jotai/utils";
3
+ import lodashGet from "lodash/get";
4
+ import {
5
+ fieldErrorsAtom,
6
+ formPropsAtom,
7
+ hasBeenSubmittedAtom,
8
+ touchedFieldsAtom,
9
+ } from "./state";
10
+ import { InternalFormId } from "./state/atomUtils";
11
+ import { controlledFieldsAtom } from "./state/controlledFields";
12
+
13
+ export const resetAtom = atomFamily((formId: InternalFormId) =>
14
+ atom(null, (get, set) => {
15
+ set(fieldErrorsAtom(formId), {});
16
+ set(touchedFieldsAtom(formId), {});
17
+ set(hasBeenSubmittedAtom(formId), false);
18
+
19
+ const { defaultValues } = get(formPropsAtom(formId));
20
+
21
+ const controlledFields = get(controlledFieldsAtom(formId));
22
+ Object.entries(controlledFields).forEach(([name, atom]) =>
23
+ set(atom, lodashGet(defaultValues, name))
24
+ );
25
+ })
26
+ );
@@ -0,0 +1,170 @@
1
+ import { atom, PrimitiveAtom } from "jotai";
2
+ import { useAtomCallback } from "jotai/utils";
3
+ import omit from "lodash/omit";
4
+ import { useCallback, useEffect } from "react";
5
+ import { InternalFormContextValue } from "../formContext";
6
+ import {
7
+ useFieldDefaultValue,
8
+ useFormAtomValue,
9
+ useFormAtom,
10
+ useFormUpdateAtom,
11
+ } from "../hooks";
12
+ import { isHydratedAtom } from "../state";
13
+ import {
14
+ fieldAtomFamily,
15
+ FieldAtomKey,
16
+ formAtomFamily,
17
+ InternalFormId,
18
+ } from "./atomUtils";
19
+
20
+ export const controlledFieldsAtom = formAtomFamily<
21
+ Record<string, PrimitiveAtom<unknown>>
22
+ >({});
23
+ const refCountAtom = fieldAtomFamily(() => atom(0));
24
+ const fieldValueAtom = fieldAtomFamily(() => atom<unknown>(undefined));
25
+ const fieldValueHydratedAtom = fieldAtomFamily(() => atom(false));
26
+
27
+ export const valueUpdatePromiseAtom = fieldAtomFamily(() =>
28
+ atom<Promise<void> | undefined>(undefined)
29
+ );
30
+ export const resolveValueUpdateAtom = fieldAtomFamily(() =>
31
+ atom<(() => void) | undefined>(undefined)
32
+ );
33
+
34
+ const registerAtom = atom(null, (get, set, { formId, field }: FieldAtomKey) => {
35
+ set(refCountAtom({ formId, field }), (prev) => prev + 1);
36
+ const newRefCount = get(refCountAtom({ formId, field }));
37
+ // We don't set hydrated here because it gets set when we know
38
+ // we have the right default values
39
+ if (newRefCount === 1) {
40
+ set(controlledFieldsAtom(formId), (prev) => ({
41
+ ...prev,
42
+ [field]: fieldValueAtom({ formId, field }),
43
+ }));
44
+ }
45
+ });
46
+
47
+ const unregisterAtom = atom(
48
+ null,
49
+ (get, set, { formId, field }: FieldAtomKey) => {
50
+ set(refCountAtom({ formId, field }), (prev) => prev - 1);
51
+ const newRefCount = get(refCountAtom({ formId, field }));
52
+ if (newRefCount === 0) {
53
+ set(controlledFieldsAtom(formId), (prev) => omit(prev, field));
54
+ fieldValueAtom.remove({ formId, field });
55
+ resolveValueUpdateAtom.remove({ formId, field });
56
+ fieldValueHydratedAtom.remove({ formId, field });
57
+ }
58
+ }
59
+ );
60
+
61
+ export const setControlledFieldValueAtom = atom(
62
+ null,
63
+ (
64
+ _get,
65
+ set,
66
+ {
67
+ formId,
68
+ field,
69
+ value,
70
+ }: { formId: InternalFormId; field: string; value: unknown }
71
+ ) => {
72
+ set(fieldValueAtom({ formId, field }), value);
73
+ const resolveAtom = resolveValueUpdateAtom({ formId, field });
74
+ const promiseAtom = valueUpdatePromiseAtom({ formId, field });
75
+
76
+ const promise = new Promise<void>((resolve) =>
77
+ set(resolveAtom, () => {
78
+ resolve();
79
+ set(resolveAtom, undefined);
80
+ set(promiseAtom, undefined);
81
+ })
82
+ );
83
+ set(promiseAtom, promise);
84
+ }
85
+ );
86
+
87
+ export const useControlledFieldValue = (
88
+ context: InternalFormContextValue,
89
+ field: string
90
+ ) => {
91
+ const fieldAtom = fieldValueAtom({ formId: context.formId, field });
92
+ const [value, setValue] = useFormAtom(fieldAtom);
93
+
94
+ const defaultValue = useFieldDefaultValue(field, context);
95
+ const isHydrated = useFormAtomValue(isHydratedAtom(context.formId));
96
+ const [isFieldHydrated, setIsFieldHydrated] = useFormAtom(
97
+ fieldValueHydratedAtom({ formId: context.formId, field })
98
+ );
99
+
100
+ useEffect(() => {
101
+ if (isHydrated && !isFieldHydrated) {
102
+ setValue(defaultValue);
103
+ setIsFieldHydrated(true);
104
+ }
105
+ }, [
106
+ defaultValue,
107
+ field,
108
+ context.formId,
109
+ isFieldHydrated,
110
+ isHydrated,
111
+ setIsFieldHydrated,
112
+ setValue,
113
+ ]);
114
+
115
+ return isFieldHydrated ? value : defaultValue;
116
+ };
117
+
118
+ export const useControllableValue = (
119
+ context: InternalFormContextValue,
120
+ field: string
121
+ ) => {
122
+ const resolveUpdate = useFormAtomValue(
123
+ resolveValueUpdateAtom({ formId: context.formId, field })
124
+ );
125
+ useEffect(() => {
126
+ resolveUpdate?.();
127
+ }, [resolveUpdate]);
128
+
129
+ const register = useFormUpdateAtom(registerAtom);
130
+ const unregister = useFormUpdateAtom(unregisterAtom);
131
+ useEffect(() => {
132
+ register({ formId: context.formId, field });
133
+ return () => unregister({ formId: context.formId, field });
134
+ }, [context.formId, field, register, unregister]);
135
+
136
+ const setControlledFieldValue = useFormUpdateAtom(
137
+ setControlledFieldValueAtom
138
+ );
139
+ const setValue = useCallback(
140
+ (value: unknown) =>
141
+ setControlledFieldValue({ formId: context.formId, field, value }),
142
+ [field, context.formId, setControlledFieldValue]
143
+ );
144
+
145
+ const value = useControlledFieldValue(context, field);
146
+
147
+ return [value, setValue] as const;
148
+ };
149
+
150
+ export const useUpdateControllableValue = (formId: InternalFormId) => {
151
+ const setControlledFieldValue = useFormUpdateAtom(
152
+ setControlledFieldValueAtom
153
+ );
154
+ return useCallback(
155
+ (field: string, value: unknown) =>
156
+ setControlledFieldValue({ formId, field, value }),
157
+ [formId, setControlledFieldValue]
158
+ );
159
+ };
160
+
161
+ export const useAwaitValue = (formId: InternalFormId) => {
162
+ return useAtomCallback(
163
+ useCallback(
164
+ async (get, _set, field: string) => {
165
+ await get(valueUpdatePromiseAtom({ formId, field }));
166
+ },
167
+ [formId]
168
+ )
169
+ );
170
+ };
@@ -48,14 +48,6 @@ export const isValidAtom = atomFamily((formId: InternalFormId) =>
48
48
  atom((get) => Object.keys(get(fieldErrorsAtom(formId))).length === 0)
49
49
  );
50
50
 
51
- export const resetAtom = atomFamily((formId: InternalFormId) =>
52
- atom(null, (_get, set) => {
53
- set(fieldErrorsAtom(formId), {});
54
- set(touchedFieldsAtom(formId), {});
55
- set(hasBeenSubmittedAtom(formId), false);
56
- })
57
- );
58
-
59
51
  export const startSubmitAtom = atomFamily((formId: InternalFormId) =>
60
52
  atom(null, (_get, set) => {
61
53
  set(isSubmittingAtom(formId), true);