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
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/README.md
CHANGED
@@ -71,9 +71,7 @@ export const MyInput = ({ name, label }: MyInputProps) => {
|
|
71
71
|
<div>
|
72
72
|
<label htmlFor={name}>{label}</label>
|
73
73
|
<input {...getInputProps({ id: name })} />
|
74
|
-
{error &&
|
75
|
-
<span className="my-error-class">{error}</span>
|
76
|
-
)}
|
74
|
+
{error && <span className="my-error-class">{error}</span>}
|
77
75
|
</div>
|
78
76
|
);
|
79
77
|
};
|
@@ -92,14 +90,17 @@ export const MySubmitButton = () => {
|
|
92
90
|
const disabled = isSubmitting || !isValid;
|
93
91
|
|
94
92
|
return (
|
95
|
-
<button
|
93
|
+
<button
|
94
|
+
type="submit"
|
95
|
+
disabled={disabled}
|
96
|
+
className={disabled ? "disabled-btn" : "btn"}
|
97
|
+
>
|
96
98
|
{isSubmitting ? "Submitting..." : "Submit"}
|
97
99
|
</button>
|
98
100
|
);
|
99
101
|
};
|
100
102
|
```
|
101
103
|
|
102
|
-
|
103
104
|
## Use the form!
|
104
105
|
|
105
106
|
Now that we have our components, making a form is easy!
|
@@ -258,5 +259,6 @@ Problem: how do we trigger a toast message on success if the action redirects aw
|
|
258
259
|
See the [Remix](https://remix.run/docs/en/v1/api/remix#sessionflashkey-value) documentation for more information.
|
259
260
|
|
260
261
|
## Why is my cancel button triggering form submission?
|
262
|
+
|
261
263
|
Problem: the cancel button has an onClick handler to navigate away from the form route but instead it is submitting the form.
|
262
264
|
A button defaults to `type="submit"` in a form which will submit the form by default. If you want to prevent this you can add `type="reset"` or `type="button"` to the cancel button.
|
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,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>;
|
@@ -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,14 +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
|
-
const
|
13
|
-
serializer: (value) => {
|
14
|
-
if (value === undefined)
|
15
|
-
return "";
|
16
|
-
return JSON.stringify(value);
|
17
|
-
},
|
18
|
-
}));
|
12
|
+
export const valueUpdatePromiseAtom = fieldAtomFamily(() => atom(undefined));
|
13
|
+
export const resolveValueUpdateAtom = fieldAtomFamily(() => atom(undefined));
|
19
14
|
const registerAtom = atom(null, (get, set, { formId, field }) => {
|
20
15
|
set(refCountAtom({ formId, field }), (prev) => prev + 1);
|
21
16
|
const newRefCount = get(refCountAtom({ formId, field }));
|
@@ -34,23 +29,27 @@ const unregisterAtom = atom(null, (get, set, { formId, field }) => {
|
|
34
29
|
if (newRefCount === 0) {
|
35
30
|
set(controlledFieldsAtom(formId), (prev) => omit(prev, field));
|
36
31
|
fieldValueAtom.remove({ formId, field });
|
37
|
-
|
32
|
+
resolveValueUpdateAtom.remove({ formId, field });
|
38
33
|
fieldValueHydratedAtom.remove({ formId, field });
|
39
34
|
}
|
40
35
|
});
|
41
|
-
export const setControlledFieldValueAtom = atom(null,
|
36
|
+
export const setControlledFieldValueAtom = atom(null, (_get, set, { formId, field, value, }) => {
|
42
37
|
set(fieldValueAtom({ formId, field }), value);
|
43
|
-
const
|
44
|
-
|
45
|
-
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);
|
46
46
|
});
|
47
|
-
export const
|
48
|
-
|
49
|
-
const fieldAtom = fieldValueAtom({ formId, field });
|
47
|
+
export const useControlledFieldValue = (context, field) => {
|
48
|
+
const fieldAtom = fieldValueAtom({ formId: context.formId, field });
|
50
49
|
const [value, setValue] = useFormAtom(fieldAtom);
|
51
|
-
const defaultValue = useFieldDefaultValue(field,
|
52
|
-
const isHydrated = useFormAtomValue(isHydratedAtom(formId));
|
53
|
-
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 }));
|
54
53
|
useEffect(() => {
|
55
54
|
if (isHydrated && !isFieldHydrated) {
|
56
55
|
setValue(defaultValue);
|
@@ -59,7 +58,7 @@ export const useControlledFieldValue = (formId, field) => {
|
|
59
58
|
}, [
|
60
59
|
defaultValue,
|
61
60
|
field,
|
62
|
-
formId,
|
61
|
+
context.formId,
|
63
62
|
isFieldHydrated,
|
64
63
|
isHydrated,
|
65
64
|
setIsFieldHydrated,
|
@@ -67,27 +66,28 @@ export const useControlledFieldValue = (formId, field) => {
|
|
67
66
|
]);
|
68
67
|
return isFieldHydrated ? value : defaultValue;
|
69
68
|
};
|
70
|
-
export const useControllableValue = (
|
71
|
-
const
|
69
|
+
export const useControllableValue = (context, field) => {
|
70
|
+
const resolveUpdate = useFormAtomValue(resolveValueUpdateAtom({ formId: context.formId, field }));
|
72
71
|
useEffect(() => {
|
73
|
-
|
74
|
-
}, [
|
72
|
+
resolveUpdate === null || resolveUpdate === void 0 ? void 0 : resolveUpdate();
|
73
|
+
}, [resolveUpdate]);
|
75
74
|
const register = useFormUpdateAtom(registerAtom);
|
76
75
|
const unregister = useFormUpdateAtom(unregisterAtom);
|
77
76
|
useEffect(() => {
|
78
|
-
register({ formId, field });
|
79
|
-
return () => unregister({ formId, field });
|
80
|
-
}, [
|
77
|
+
register({ formId: context.formId, field });
|
78
|
+
return () => unregister({ formId: context.formId, field });
|
79
|
+
}, [context.formId, field, register, unregister]);
|
81
80
|
const setControlledFieldValue = useFormUpdateAtom(setControlledFieldValueAtom);
|
82
|
-
const setValue = useCallback((value) => setControlledFieldValue({ formId, field, value }), [field, formId, setControlledFieldValue]);
|
83
|
-
const value = useControlledFieldValue(
|
81
|
+
const setValue = useCallback((value) => setControlledFieldValue({ formId: context.formId, field, value }), [field, context.formId, setControlledFieldValue]);
|
82
|
+
const value = useControlledFieldValue(context, field);
|
84
83
|
return [value, setValue];
|
85
84
|
};
|
86
|
-
export const
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
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/browser/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/browser/server.js
CHANGED
@@ -14,13 +14,13 @@ import { formDefaultValuesKey, } from "./internal/constants";
|
|
14
14
|
* if (result.error) return validationError(result.error, result.submittedData);
|
15
15
|
* ```
|
16
16
|
*/
|
17
|
-
export function validationError(error, repopulateFields) {
|
17
|
+
export function validationError(error, repopulateFields, init) {
|
18
18
|
return json({
|
19
19
|
fieldErrors: error.fieldErrors,
|
20
20
|
subaction: error.subaction,
|
21
21
|
repopulateFields,
|
22
22
|
formId: error.formId,
|
23
|
-
}, { status: 422 });
|
23
|
+
}, { status: 422, ...init });
|
24
24
|
}
|
25
25
|
export const setFormDefaults = (formId, defaultValues) => ({
|
26
26
|
[formDefaultValuesKey(formId)]: defaultValues,
|
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
|
+
};
|