remix-validated-form 4.2.0 → 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/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 -5
- package/browser/internal/state/controlledFields.js +37 -21
- package/browser/internal/state.d.ts +0 -27
- package/browser/internal/state.js +0 -5
- 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 -5
- package/build/internal/state/controlledFields.js +40 -22
- package/build/internal/state.d.ts +0 -27
- package/build/internal/state.js +1 -6
- package/package.json +1 -1
- 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/.turbo/turbo-build.log
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
[2K[1G[2m$ npm run build:browser && npm run build:main[22m
|
2
2
|
|
3
|
-
> remix-validated-form@4.
|
3
|
+
> remix-validated-form@4.2.0 build:browser
|
4
4
|
> tsc --module ESNext --outDir ./browser
|
5
5
|
|
6
6
|
|
7
|
-
> remix-validated-form@4.
|
7
|
+
> remix-validated-form@4.2.0 build:main
|
8
8
|
> tsc --module CommonJS --outDir ./build
|
9
9
|
|
package/browser/ValidatedForm.js
CHANGED
@@ -8,7 +8,9 @@ import { FORM_ID_FIELD } from "./internal/constants";
|
|
8
8
|
import { InternalFormContext, } from "./internal/formContext";
|
9
9
|
import { useDefaultValuesFromLoader, useErrorResponseForForm, useFormUpdateAtom, useHasActiveFormSubmit, } from "./internal/hooks";
|
10
10
|
import { useMultiValueMap } from "./internal/MultiValueMap";
|
11
|
-
import {
|
11
|
+
import { resetAtom } from "./internal/reset";
|
12
|
+
import { cleanupFormState, endSubmitAtom, fieldErrorsAtom, formElementAtom, formPropsAtom, isHydratedAtom, setFieldErrorAtom, startSubmitAtom, } from "./internal/state";
|
13
|
+
import { useAwaitValue } from "./internal/state/controlledFields";
|
12
14
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
13
15
|
import { mergeRefs, useDeepEqualsMemo, useIsomorphicLayoutEffect as useLayoutEffect, } from "./internal/util";
|
14
16
|
const getDataFromForm = (el) => new FormData(el);
|
@@ -123,8 +125,10 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
123
125
|
setHydrated(true);
|
124
126
|
return () => cleanupFormState(formId);
|
125
127
|
}, [formId, setHydrated]);
|
128
|
+
const awaitValue = useAwaitValue(formId);
|
126
129
|
const validateField = useCallback(async (field) => {
|
127
130
|
invariant(formRef.current, "Cannot find reference to form");
|
131
|
+
await awaitValue(field);
|
128
132
|
const { error } = await validator.validateField(getDataFromForm(formRef.current), field);
|
129
133
|
if (error) {
|
130
134
|
setFieldError({ field, error });
|
@@ -134,7 +138,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
134
138
|
setFieldError({ field, error: undefined });
|
135
139
|
return null;
|
136
140
|
}
|
137
|
-
}, [setFieldError, validator]);
|
141
|
+
}, [awaitValue, setFieldError, validator]);
|
138
142
|
const customFocusHandlers = useMultiValueMap();
|
139
143
|
const registerReceiveFocus = useCallback((fieldName, handler) => {
|
140
144
|
customFocusHandlers().add(fieldName, handler);
|
package/browser/hooks.d.ts
CHANGED
@@ -63,3 +63,5 @@ export declare const useField: (name: string, options?: {
|
|
63
63
|
*/
|
64
64
|
formId?: string | undefined;
|
65
65
|
} | undefined) => FieldProps;
|
66
|
+
export declare const useControlField: <T>(name: string, formId?: string | undefined) => readonly [T, (value: T) => void];
|
67
|
+
export declare const useUpdateControlledField: (formId?: string | undefined) => (field: string, value: unknown) => void;
|
package/browser/hooks.js
CHANGED
@@ -2,6 +2,7 @@ import { useEffect, useMemo } from "react";
|
|
2
2
|
import { createGetInputProps, } from "./internal/getInputProps";
|
3
3
|
import { useInternalFormContext, useFieldTouched, useFieldError, useFormAtomValue, useFieldDefaultValue, } from "./internal/hooks";
|
4
4
|
import { formPropsAtom, hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, } from "./internal/state";
|
5
|
+
import { useControllableValue, useUpdateControllableValue, } from "./internal/state/controlledFields";
|
5
6
|
/**
|
6
7
|
* Returns whether or not the parent form is currently being submitted.
|
7
8
|
* This is different from remix's `useTransition().submission` in that it
|
@@ -71,3 +72,12 @@ export const useField = (name, options) => {
|
|
71
72
|
]);
|
72
73
|
return field;
|
73
74
|
};
|
75
|
+
export const useControlField = (name, formId) => {
|
76
|
+
const context = useInternalFormContext(formId, "useControlField");
|
77
|
+
const [value, setValue] = useControllableValue(context, name);
|
78
|
+
return [value, setValue];
|
79
|
+
};
|
80
|
+
export const useUpdateControlledField = (formId) => {
|
81
|
+
const context = useInternalFormContext(formId, "useControlField");
|
82
|
+
return useUpdateControllableValue(context.formId);
|
83
|
+
};
|
@@ -1,9 +1,11 @@
|
|
1
1
|
export declare class MultiValueMap<Key, Value> {
|
2
2
|
private dict;
|
3
3
|
add: (key: Key, value: Value) => void;
|
4
|
+
delete: (key: Key) => void;
|
4
5
|
remove: (key: Key, value: Value) => void;
|
5
6
|
getAll: (key: Key) => Value[];
|
6
7
|
entries: () => IterableIterator<[Key, Value[]]>;
|
8
|
+
values: () => IterableIterator<Value[]>;
|
7
9
|
has: (key: Key) => boolean;
|
8
10
|
}
|
9
11
|
export declare const useMultiValueMap: <Key, Value>() => () => MultiValueMap<Key, Value>;
|
@@ -10,6 +10,9 @@ export class MultiValueMap {
|
|
10
10
|
this.dict.set(key, [value]);
|
11
11
|
}
|
12
12
|
};
|
13
|
+
this.delete = (key) => {
|
14
|
+
this.dict.delete(key);
|
15
|
+
};
|
13
16
|
this.remove = (key, value) => {
|
14
17
|
if (!this.dict.has(key))
|
15
18
|
return;
|
@@ -25,6 +28,7 @@ export class MultiValueMap {
|
|
25
28
|
return (_a = this.dict.get(key)) !== null && _a !== void 0 ? _a : [];
|
26
29
|
};
|
27
30
|
this.entries = () => this.dict.entries();
|
31
|
+
this.values = () => this.dict.values();
|
28
32
|
this.has = (key) => this.dict.has(key);
|
29
33
|
}
|
30
34
|
}
|
@@ -42,7 +42,8 @@ export const createGetInputProps = ({ clearError, validate, defaultValue, touche
|
|
42
42
|
else if (props.type === "radio") {
|
43
43
|
inputProps.defaultChecked = getRadioChecked(props.value, defaultValue);
|
44
44
|
}
|
45
|
-
else {
|
45
|
+
else if (props.value === undefined) {
|
46
|
+
// We should only set the defaultValue if the input is uncontrolled.
|
46
47
|
inputProps.defaultValue = defaultValue;
|
47
48
|
}
|
48
49
|
return omitBy(inputProps, (value) => value === undefined);
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { InternalFormId } from "./state/atomUtils";
|
2
|
+
export declare const resetAtom: {
|
3
|
+
(param: InternalFormId): import("jotai").Atom<null> & {
|
4
|
+
write: (get: {
|
5
|
+
<Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
|
6
|
+
<Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
|
7
|
+
<Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
|
8
|
+
} & {
|
9
|
+
<Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
|
10
|
+
unstable_promise: true;
|
11
|
+
}): Value_3 | Promise<Value_3>;
|
12
|
+
<Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
|
13
|
+
unstable_promise: true;
|
14
|
+
}): Value_4 | Promise<Value_4>;
|
15
|
+
<Value_5>(atom: import("jotai").Atom<Value_5>, options: {
|
16
|
+
unstable_promise: true;
|
17
|
+
}): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
|
18
|
+
}, set: {
|
19
|
+
<Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
|
20
|
+
<Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
|
21
|
+
}, update: unknown) => void;
|
22
|
+
onMount?: (<S extends (update?: unknown) => void>(setAtom: S) => void | (() => void)) | undefined;
|
23
|
+
} & {
|
24
|
+
init: null;
|
25
|
+
};
|
26
|
+
remove(param: InternalFormId): void;
|
27
|
+
setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
|
28
|
+
};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { atom } from "jotai";
|
2
|
+
import { atomFamily } from "jotai/utils";
|
3
|
+
import lodashGet from "lodash/get";
|
4
|
+
import { fieldErrorsAtom, formPropsAtom, hasBeenSubmittedAtom, touchedFieldsAtom, } from "./state";
|
5
|
+
import { controlledFieldsAtom } from "./state/controlledFields";
|
6
|
+
export const resetAtom = atomFamily((formId) => atom(null, (get, set) => {
|
7
|
+
set(fieldErrorsAtom(formId), {});
|
8
|
+
set(touchedFieldsAtom(formId), {});
|
9
|
+
set(hasBeenSubmittedAtom(formId), false);
|
10
|
+
const { defaultValues } = get(formPropsAtom(formId));
|
11
|
+
const controlledFields = get(controlledFieldsAtom(formId));
|
12
|
+
Object.entries(controlledFields).forEach(([name, atom]) => set(atom, lodashGet(defaultValues, name)));
|
13
|
+
}));
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import { PrimitiveAtom } from "jotai";
|
2
|
-
import {
|
2
|
+
import { InternalFormContextValue } from "../formContext";
|
3
|
+
import { FieldAtomKey, InternalFormId } from "./atomUtils";
|
3
4
|
export declare const controlledFieldsAtom: {
|
4
5
|
(param: InternalFormId): import("jotai").Atom<Record<string, PrimitiveAtom<unknown>>> & {
|
5
6
|
write: (get: {
|
@@ -27,6 +28,60 @@ export declare const controlledFieldsAtom: {
|
|
27
28
|
remove(param: InternalFormId): void;
|
28
29
|
setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
|
29
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
|
+
};
|
30
85
|
export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
|
31
86
|
write: (get: {
|
32
87
|
<Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
|
@@ -49,14 +104,16 @@ export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
|
|
49
104
|
formId: InternalFormId;
|
50
105
|
field: string;
|
51
106
|
value: unknown;
|
52
|
-
}) =>
|
107
|
+
}) => void;
|
53
108
|
onMount?: (<S extends (update: {
|
54
109
|
formId: InternalFormId;
|
55
110
|
field: string;
|
56
111
|
value: unknown;
|
57
|
-
}) =>
|
112
|
+
}) => void>(setAtom: S) => void | (() => void)) | undefined;
|
58
113
|
} & {
|
59
114
|
init: null;
|
60
115
|
};
|
61
|
-
export declare const useControlledFieldValue: (
|
62
|
-
export declare const useControllableValue: (
|
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>;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { atom } from "jotai";
|
2
|
+
import { useAtomCallback } from "jotai/utils";
|
2
3
|
import omit from "lodash/omit";
|
3
4
|
import { useCallback, useEffect } from "react";
|
4
5
|
import { useFieldDefaultValue, useFormAtomValue, useFormAtom, useFormUpdateAtom, } from "../hooks";
|
@@ -8,7 +9,8 @@ export const controlledFieldsAtom = formAtomFamily({});
|
|
8
9
|
const refCountAtom = fieldAtomFamily(() => atom(0));
|
9
10
|
const fieldValueAtom = fieldAtomFamily(() => atom(undefined));
|
10
11
|
const fieldValueHydratedAtom = fieldAtomFamily(() => atom(false));
|
11
|
-
const
|
12
|
+
export const valueUpdatePromiseAtom = fieldAtomFamily(() => atom(undefined));
|
13
|
+
export const resolveValueUpdateAtom = fieldAtomFamily(() => atom(undefined));
|
12
14
|
const registerAtom = atom(null, (get, set, { formId, field }) => {
|
13
15
|
set(refCountAtom({ formId, field }), (prev) => prev + 1);
|
14
16
|
const newRefCount = get(refCountAtom({ formId, field }));
|
@@ -27,22 +29,27 @@ const unregisterAtom = atom(null, (get, set, { formId, field }) => {
|
|
27
29
|
if (newRefCount === 0) {
|
28
30
|
set(controlledFieldsAtom(formId), (prev) => omit(prev, field));
|
29
31
|
fieldValueAtom.remove({ formId, field });
|
30
|
-
|
32
|
+
resolveValueUpdateAtom.remove({ formId, field });
|
31
33
|
fieldValueHydratedAtom.remove({ formId, field });
|
32
34
|
}
|
33
35
|
});
|
34
|
-
export const setControlledFieldValueAtom = atom(null,
|
36
|
+
export const setControlledFieldValueAtom = atom(null, (_get, set, { formId, field, value, }) => {
|
35
37
|
set(fieldValueAtom({ formId, field }), value);
|
36
|
-
const
|
37
|
-
|
38
|
-
set(
|
38
|
+
const resolveAtom = resolveValueUpdateAtom({ formId, field });
|
39
|
+
const promiseAtom = valueUpdatePromiseAtom({ formId, field });
|
40
|
+
const promise = new Promise((resolve) => set(resolveAtom, () => {
|
41
|
+
resolve();
|
42
|
+
set(resolveAtom, undefined);
|
43
|
+
set(promiseAtom, undefined);
|
44
|
+
}));
|
45
|
+
set(promiseAtom, promise);
|
39
46
|
});
|
40
|
-
export const useControlledFieldValue = (
|
41
|
-
const fieldAtom = fieldValueAtom({ formId, field });
|
47
|
+
export const useControlledFieldValue = (context, field) => {
|
48
|
+
const fieldAtom = fieldValueAtom({ formId: context.formId, field });
|
42
49
|
const [value, setValue] = useFormAtom(fieldAtom);
|
43
|
-
const defaultValue = useFieldDefaultValue(field,
|
44
|
-
const isHydrated = useFormAtomValue(isHydratedAtom(formId));
|
45
|
-
const [isFieldHydrated, setIsFieldHydrated] = useFormAtom(fieldValueHydratedAtom({ formId, field }));
|
50
|
+
const defaultValue = useFieldDefaultValue(field, context);
|
51
|
+
const isHydrated = useFormAtomValue(isHydratedAtom(context.formId));
|
52
|
+
const [isFieldHydrated, setIsFieldHydrated] = useFormAtom(fieldValueHydratedAtom({ formId: context.formId, field }));
|
46
53
|
useEffect(() => {
|
47
54
|
if (isHydrated && !isFieldHydrated) {
|
48
55
|
setValue(defaultValue);
|
@@ -51,7 +58,7 @@ export const useControlledFieldValue = (formId, field) => {
|
|
51
58
|
}, [
|
52
59
|
defaultValue,
|
53
60
|
field,
|
54
|
-
formId,
|
61
|
+
context.formId,
|
55
62
|
isFieldHydrated,
|
56
63
|
isHydrated,
|
57
64
|
setIsFieldHydrated,
|
@@ -59,19 +66,28 @@ export const useControlledFieldValue = (formId, field) => {
|
|
59
66
|
]);
|
60
67
|
return isFieldHydrated ? value : defaultValue;
|
61
68
|
};
|
62
|
-
export const useControllableValue = (
|
63
|
-
const
|
69
|
+
export const useControllableValue = (context, field) => {
|
70
|
+
const resolveUpdate = useFormAtomValue(resolveValueUpdateAtom({ formId: context.formId, field }));
|
64
71
|
useEffect(() => {
|
65
|
-
|
66
|
-
}, [
|
72
|
+
resolveUpdate === null || resolveUpdate === void 0 ? void 0 : resolveUpdate();
|
73
|
+
}, [resolveUpdate]);
|
67
74
|
const register = useFormUpdateAtom(registerAtom);
|
68
75
|
const unregister = useFormUpdateAtom(unregisterAtom);
|
69
76
|
useEffect(() => {
|
70
|
-
register({ formId, field });
|
71
|
-
return () => unregister({ formId, field });
|
72
|
-
}, [
|
77
|
+
register({ formId: context.formId, field });
|
78
|
+
return () => unregister({ formId: context.formId, field });
|
79
|
+
}, [context.formId, field, register, unregister]);
|
73
80
|
const setControlledFieldValue = useFormUpdateAtom(setControlledFieldValueAtom);
|
74
|
-
const setValue = useCallback((value) => setControlledFieldValue({ formId, field, value }), [field, formId, setControlledFieldValue]);
|
75
|
-
const value = useControlledFieldValue(
|
81
|
+
const setValue = useCallback((value) => setControlledFieldValue({ formId: context.formId, field, value }), [field, context.formId, setControlledFieldValue]);
|
82
|
+
const value = useControlledFieldValue(context, field);
|
76
83
|
return [value, setValue];
|
77
84
|
};
|
85
|
+
export const useUpdateControllableValue = (formId) => {
|
86
|
+
const setControlledFieldValue = useFormUpdateAtom(setControlledFieldValueAtom);
|
87
|
+
return useCallback((field, value) => setControlledFieldValue({ formId, field, value }), [formId, setControlledFieldValue]);
|
88
|
+
};
|
89
|
+
export const useAwaitValue = (formId) => {
|
90
|
+
return useAtomCallback(useCallback(async (get, _set, field) => {
|
91
|
+
await get(valueUpdatePromiseAtom({ formId, field }));
|
92
|
+
}, [formId]));
|
93
|
+
};
|
@@ -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: {
|
@@ -26,11 +26,6 @@ export const cleanupFormState = (formId) => {
|
|
26
26
|
].forEach((formAtom) => formAtom.remove(formId));
|
27
27
|
};
|
28
28
|
export const isValidAtom = atomFamily((formId) => atom((get) => Object.keys(get(fieldErrorsAtom(formId))).length === 0));
|
29
|
-
export const resetAtom = atomFamily((formId) => atom(null, (_get, set) => {
|
30
|
-
set(fieldErrorsAtom(formId), {});
|
31
|
-
set(touchedFieldsAtom(formId), {});
|
32
|
-
set(hasBeenSubmittedAtom(formId), false);
|
33
|
-
}));
|
34
29
|
export const startSubmitAtom = atomFamily((formId) => atom(null, (_get, set) => {
|
35
30
|
set(isSubmittingAtom(formId), true);
|
36
31
|
set(hasBeenSubmittedAtom(formId), true);
|
package/build/ValidatedForm.js
CHANGED
@@ -33,7 +33,9 @@ const constants_1 = require("./internal/constants");
|
|
33
33
|
const formContext_1 = require("./internal/formContext");
|
34
34
|
const hooks_2 = require("./internal/hooks");
|
35
35
|
const MultiValueMap_1 = require("./internal/MultiValueMap");
|
36
|
+
const reset_1 = require("./internal/reset");
|
36
37
|
const state_1 = require("./internal/state");
|
38
|
+
const controlledFields_1 = require("./internal/state/controlledFields");
|
37
39
|
const submissionCallbacks_1 = require("./internal/submissionCallbacks");
|
38
40
|
const util_1 = require("./internal/util");
|
39
41
|
const getDataFromForm = (el) => new FormData(el);
|
@@ -138,7 +140,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
138
140
|
const submit = (0, react_1.useSubmit)();
|
139
141
|
const setFieldErrors = (0, hooks_2.useFormUpdateAtom)((0, state_1.fieldErrorsAtom)(formId));
|
140
142
|
const setFieldError = (0, hooks_2.useFormUpdateAtom)((0, state_1.setFieldErrorAtom)(formId));
|
141
|
-
const reset = (0, hooks_2.useFormUpdateAtom)((0,
|
143
|
+
const reset = (0, hooks_2.useFormUpdateAtom)((0, reset_1.resetAtom)(formId));
|
142
144
|
const startSubmit = (0, hooks_2.useFormUpdateAtom)((0, state_1.startSubmitAtom)(formId));
|
143
145
|
const endSubmit = (0, hooks_2.useFormUpdateAtom)((0, state_1.endSubmitAtom)(formId));
|
144
146
|
const syncFormProps = (0, hooks_2.useFormUpdateAtom)((0, state_1.formPropsAtom)(formId));
|
@@ -148,8 +150,10 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
148
150
|
setHydrated(true);
|
149
151
|
return () => (0, state_1.cleanupFormState)(formId);
|
150
152
|
}, [formId, setHydrated]);
|
153
|
+
const awaitValue = (0, controlledFields_1.useAwaitValue)(formId);
|
151
154
|
const validateField = (0, react_2.useCallback)(async (field) => {
|
152
155
|
(0, tiny_invariant_1.default)(formRef.current, "Cannot find reference to form");
|
156
|
+
await awaitValue(field);
|
153
157
|
const { error } = await validator.validateField(getDataFromForm(formRef.current), field);
|
154
158
|
if (error) {
|
155
159
|
setFieldError({ field, error });
|
@@ -159,7 +163,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
|
|
159
163
|
setFieldError({ field, error: undefined });
|
160
164
|
return null;
|
161
165
|
}
|
162
|
-
}, [setFieldError, validator]);
|
166
|
+
}, [awaitValue, setFieldError, validator]);
|
163
167
|
const customFocusHandlers = (0, MultiValueMap_1.useMultiValueMap)();
|
164
168
|
const registerReceiveFocus = (0, react_2.useCallback)((fieldName, handler) => {
|
165
169
|
customFocusHandlers().add(fieldName, handler);
|
package/build/hooks.d.ts
CHANGED
@@ -63,3 +63,5 @@ export declare const useField: (name: string, options?: {
|
|
63
63
|
*/
|
64
64
|
formId?: string | undefined;
|
65
65
|
} | undefined) => FieldProps;
|
66
|
+
export declare const useControlField: <T>(name: string, formId?: string | undefined) => readonly [T, (value: T) => void];
|
67
|
+
export declare const useUpdateControlledField: (formId?: string | undefined) => (field: string, value: unknown) => void;
|
package/build/hooks.js
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
exports.useField = exports.useIsValid = exports.useIsSubmitting = void 0;
|
3
|
+
exports.useUpdateControlledField = exports.useControlField = exports.useField = exports.useIsValid = exports.useIsSubmitting = void 0;
|
4
4
|
const react_1 = require("react");
|
5
5
|
const getInputProps_1 = require("./internal/getInputProps");
|
6
6
|
const hooks_1 = require("./internal/hooks");
|
7
7
|
const state_1 = require("./internal/state");
|
8
|
+
const controlledFields_1 = require("./internal/state/controlledFields");
|
8
9
|
/**
|
9
10
|
* Returns whether or not the parent form is currently being submitted.
|
10
11
|
* This is different from remix's `useTransition().submission` in that it
|
@@ -77,3 +78,14 @@ const useField = (name, options) => {
|
|
77
78
|
return field;
|
78
79
|
};
|
79
80
|
exports.useField = useField;
|
81
|
+
const useControlField = (name, formId) => {
|
82
|
+
const context = (0, hooks_1.useInternalFormContext)(formId, "useControlField");
|
83
|
+
const [value, setValue] = (0, controlledFields_1.useControllableValue)(context, name);
|
84
|
+
return [value, setValue];
|
85
|
+
};
|
86
|
+
exports.useControlField = useControlField;
|
87
|
+
const useUpdateControlledField = (formId) => {
|
88
|
+
const context = (0, hooks_1.useInternalFormContext)(formId, "useControlField");
|
89
|
+
return (0, controlledFields_1.useUpdateControllableValue)(context.formId);
|
90
|
+
};
|
91
|
+
exports.useUpdateControlledField = useUpdateControlledField;
|
@@ -1,9 +1,11 @@
|
|
1
1
|
export declare class MultiValueMap<Key, Value> {
|
2
2
|
private dict;
|
3
3
|
add: (key: Key, value: Value) => void;
|
4
|
+
delete: (key: Key) => void;
|
4
5
|
remove: (key: Key, value: Value) => void;
|
5
6
|
getAll: (key: Key) => Value[];
|
6
7
|
entries: () => IterableIterator<[Key, Value[]]>;
|
8
|
+
values: () => IterableIterator<Value[]>;
|
7
9
|
has: (key: Key) => boolean;
|
8
10
|
}
|
9
11
|
export declare const useMultiValueMap: <Key, Value>() => () => MultiValueMap<Key, Value>;
|
@@ -13,6 +13,9 @@ class MultiValueMap {
|
|
13
13
|
this.dict.set(key, [value]);
|
14
14
|
}
|
15
15
|
};
|
16
|
+
this.delete = (key) => {
|
17
|
+
this.dict.delete(key);
|
18
|
+
};
|
16
19
|
this.remove = (key, value) => {
|
17
20
|
if (!this.dict.has(key))
|
18
21
|
return;
|
@@ -28,6 +31,7 @@ class MultiValueMap {
|
|
28
31
|
return (_a = this.dict.get(key)) !== null && _a !== void 0 ? _a : [];
|
29
32
|
};
|
30
33
|
this.entries = () => this.dict.entries();
|
34
|
+
this.values = () => this.dict.values();
|
31
35
|
this.has = (key) => this.dict.has(key);
|
32
36
|
}
|
33
37
|
}
|
@@ -48,7 +48,8 @@ const createGetInputProps = ({ clearError, validate, defaultValue, touched, setT
|
|
48
48
|
else if (props.type === "radio") {
|
49
49
|
inputProps.defaultChecked = (0, getRadioChecked_1.getRadioChecked)(props.value, defaultValue);
|
50
50
|
}
|
51
|
-
else {
|
51
|
+
else if (props.value === undefined) {
|
52
|
+
// We should only set the defaultValue if the input is uncontrolled.
|
52
53
|
inputProps.defaultValue = defaultValue;
|
53
54
|
}
|
54
55
|
return (0, omitBy_1.default)(inputProps, (value) => value === undefined);
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { InternalFormId } from "./state/atomUtils";
|
2
|
+
export declare const resetAtom: {
|
3
|
+
(param: InternalFormId): import("jotai").Atom<null> & {
|
4
|
+
write: (get: {
|
5
|
+
<Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
|
6
|
+
<Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
|
7
|
+
<Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
|
8
|
+
} & {
|
9
|
+
<Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
|
10
|
+
unstable_promise: true;
|
11
|
+
}): Value_3 | Promise<Value_3>;
|
12
|
+
<Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
|
13
|
+
unstable_promise: true;
|
14
|
+
}): Value_4 | Promise<Value_4>;
|
15
|
+
<Value_5>(atom: import("jotai").Atom<Value_5>, options: {
|
16
|
+
unstable_promise: true;
|
17
|
+
}): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
|
18
|
+
}, set: {
|
19
|
+
<Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
|
20
|
+
<Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
|
21
|
+
}, update: unknown) => void;
|
22
|
+
onMount?: (<S extends (update?: unknown) => void>(setAtom: S) => void | (() => void)) | undefined;
|
23
|
+
} & {
|
24
|
+
init: null;
|
25
|
+
};
|
26
|
+
remove(param: InternalFormId): void;
|
27
|
+
setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
|
28
|
+
};
|
@@ -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,5 +1,6 @@
|
|
1
1
|
import { PrimitiveAtom } from "jotai";
|
2
|
-
import {
|
2
|
+
import { InternalFormContextValue } from "../formContext";
|
3
|
+
import { FieldAtomKey, InternalFormId } from "./atomUtils";
|
3
4
|
export declare const controlledFieldsAtom: {
|
4
5
|
(param: InternalFormId): import("jotai").Atom<Record<string, PrimitiveAtom<unknown>>> & {
|
5
6
|
write: (get: {
|
@@ -27,6 +28,60 @@ export declare const controlledFieldsAtom: {
|
|
27
28
|
remove(param: InternalFormId): void;
|
28
29
|
setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
|
29
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
|
+
};
|
30
85
|
export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
|
31
86
|
write: (get: {
|
32
87
|
<Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
|
@@ -49,14 +104,16 @@ export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
|
|
49
104
|
formId: InternalFormId;
|
50
105
|
field: string;
|
51
106
|
value: unknown;
|
52
|
-
}) =>
|
107
|
+
}) => void;
|
53
108
|
onMount?: (<S extends (update: {
|
54
109
|
formId: InternalFormId;
|
55
110
|
field: string;
|
56
111
|
value: unknown;
|
57
|
-
}) =>
|
112
|
+
}) => void>(setAtom: S) => void | (() => void)) | undefined;
|
58
113
|
} & {
|
59
114
|
init: null;
|
60
115
|
};
|
61
|
-
export declare const useControlledFieldValue: (
|
62
|
-
export declare const useControllableValue: (
|
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.useControllableValue = exports.useControlledFieldValue = 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,7 +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
|
+
exports.valueUpdatePromiseAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
|
19
|
+
exports.resolveValueUpdateAtom = (0, atomUtils_1.fieldAtomFamily)(() => (0, jotai_1.atom)(undefined));
|
18
20
|
const registerAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) => {
|
19
21
|
set(refCountAtom({ formId, field }), (prev) => prev + 1);
|
20
22
|
const newRefCount = get(refCountAtom({ formId, field }));
|
@@ -33,22 +35,27 @@ const unregisterAtom = (0, jotai_1.atom)(null, (get, set, { formId, field }) =>
|
|
33
35
|
if (newRefCount === 0) {
|
34
36
|
set((0, exports.controlledFieldsAtom)(formId), (prev) => (0, omit_1.default)(prev, field));
|
35
37
|
fieldValueAtom.remove({ formId, field });
|
36
|
-
|
38
|
+
exports.resolveValueUpdateAtom.remove({ formId, field });
|
37
39
|
fieldValueHydratedAtom.remove({ formId, field });
|
38
40
|
}
|
39
41
|
});
|
40
|
-
exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null,
|
42
|
+
exports.setControlledFieldValueAtom = (0, jotai_1.atom)(null, (_get, set, { formId, field, value, }) => {
|
41
43
|
set(fieldValueAtom({ formId, field }), value);
|
42
|
-
const
|
43
|
-
|
44
|
-
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);
|
45
52
|
});
|
46
|
-
const useControlledFieldValue = (
|
47
|
-
const fieldAtom = fieldValueAtom({ formId, field });
|
53
|
+
const useControlledFieldValue = (context, field) => {
|
54
|
+
const fieldAtom = fieldValueAtom({ formId: context.formId, field });
|
48
55
|
const [value, setValue] = (0, hooks_1.useFormAtom)(fieldAtom);
|
49
|
-
const defaultValue = (0, hooks_1.useFieldDefaultValue)(field,
|
50
|
-
const isHydrated = (0, hooks_1.useFormAtomValue)((0, state_1.isHydratedAtom)(formId));
|
51
|
-
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 }));
|
52
59
|
(0, react_1.useEffect)(() => {
|
53
60
|
if (isHydrated && !isFieldHydrated) {
|
54
61
|
setValue(defaultValue);
|
@@ -57,7 +64,7 @@ const useControlledFieldValue = (formId, field) => {
|
|
57
64
|
}, [
|
58
65
|
defaultValue,
|
59
66
|
field,
|
60
|
-
formId,
|
67
|
+
context.formId,
|
61
68
|
isFieldHydrated,
|
62
69
|
isHydrated,
|
63
70
|
setIsFieldHydrated,
|
@@ -66,20 +73,31 @@ const useControlledFieldValue = (formId, field) => {
|
|
66
73
|
return isFieldHydrated ? value : defaultValue;
|
67
74
|
};
|
68
75
|
exports.useControlledFieldValue = useControlledFieldValue;
|
69
|
-
const useControllableValue = (
|
70
|
-
const
|
76
|
+
const useControllableValue = (context, field) => {
|
77
|
+
const resolveUpdate = (0, hooks_1.useFormAtomValue)((0, exports.resolveValueUpdateAtom)({ formId: context.formId, field }));
|
71
78
|
(0, react_1.useEffect)(() => {
|
72
|
-
|
73
|
-
}, [
|
79
|
+
resolveUpdate === null || resolveUpdate === void 0 ? void 0 : resolveUpdate();
|
80
|
+
}, [resolveUpdate]);
|
74
81
|
const register = (0, hooks_1.useFormUpdateAtom)(registerAtom);
|
75
82
|
const unregister = (0, hooks_1.useFormUpdateAtom)(unregisterAtom);
|
76
83
|
(0, react_1.useEffect)(() => {
|
77
|
-
register({ formId, field });
|
78
|
-
return () => unregister({ formId, field });
|
79
|
-
}, [
|
84
|
+
register({ formId: context.formId, field });
|
85
|
+
return () => unregister({ formId: context.formId, field });
|
86
|
+
}, [context.formId, field, register, unregister]);
|
80
87
|
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)(
|
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);
|
83
90
|
return [value, setValue];
|
84
91
|
};
|
85
92
|
exports.useControllableValue = useControllableValue;
|
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]));
|
102
|
+
};
|
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/package.json
CHANGED
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);
|