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.
- package/.turbo/turbo-build.log +2 -2
- package/README.md +7 -5
- package/browser/ValidatedForm.js +6 -2
- package/browser/hooks.d.ts +2 -0
- package/browser/hooks.js +10 -0
- package/browser/internal/MultiValueMap.d.ts +2 -0
- package/browser/internal/MultiValueMap.js +4 -0
- package/browser/internal/getInputProps.js +2 -1
- package/browser/internal/reset.d.ts +28 -0
- package/browser/internal/reset.js +13 -0
- package/browser/internal/state/controlledFields.d.ts +62 -9
- package/browser/internal/state/controlledFields.js +36 -36
- package/browser/internal/state.d.ts +0 -27
- package/browser/internal/state.js +0 -5
- package/browser/server.d.ts +1 -1
- package/browser/server.js +2 -2
- package/build/ValidatedForm.js +6 -2
- package/build/hooks.d.ts +2 -0
- package/build/hooks.js +13 -1
- package/build/internal/MultiValueMap.d.ts +2 -0
- package/build/internal/MultiValueMap.js +4 -0
- package/build/internal/getInputProps.js +2 -1
- package/build/internal/reset.d.ts +28 -0
- package/build/internal/reset.js +19 -0
- package/build/internal/state/controlledFields.d.ts +62 -9
- package/build/internal/state/controlledFields.js +39 -40
- package/build/internal/state.d.ts +0 -27
- package/build/internal/state.js +1 -6
- package/build/server.d.ts +1 -1
- package/build/server.js +2 -2
- package/package.json +2 -2
- package/src/ValidatedForm.tsx +6 -2
- package/src/hooks.ts +15 -0
- package/src/internal/MultiValueMap.ts +6 -0
- package/src/internal/getInputProps.ts +2 -1
- package/src/internal/reset.ts +26 -0
- package/src/internal/state/controlledFields.ts +170 -0
- package/src/internal/state.ts +0 -8
- 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 {
|
3
|
-
|
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
|
-
}) =>
|
107
|
+
}) => void;
|
54
108
|
onMount?: (<S extends (update: {
|
55
109
|
formId: InternalFormId;
|
56
110
|
field: string;
|
57
111
|
value: unknown;
|
58
|
-
}) =>
|
112
|
+
}) => void>(setAtom: S) => void | (() => void)) | undefined;
|
59
113
|
} & {
|
60
114
|
init: null;
|
61
115
|
};
|
62
|
-
export declare const
|
63
|
-
export declare const
|
64
|
-
export declare const
|
65
|
-
export declare const
|
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.
|
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
|
-
|
18
|
-
|
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
|
-
|
38
|
+
exports.resolveValueUpdateAtom.remove({ formId, field });
|
44
39
|
fieldValueHydratedAtom.remove({ formId, field });
|
45
40
|
}
|
46
41
|
});
|
47
|
-
exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null,
|
42
|
+
exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null, (_get, set, { formId, field, value, }) => {
|
48
43
|
set(fieldValueAtom({ formId, field }), value);
|
49
|
-
const
|
50
|
-
|
51
|
-
set(
|
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
|
54
|
-
|
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,
|
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 = (
|
79
|
-
const
|
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
|
-
|
82
|
-
}, [
|
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
|
-
}, [
|
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)(
|
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
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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.
|
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: {
|
package/build/internal/state.js
CHANGED
@@ -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.
|
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.
|
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",
|
package/src/ValidatedForm.tsx
CHANGED
@@ -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
|
+
};
|
package/src/internal/state.ts
CHANGED
@@ -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);
|