remix-validated-form 4.2.0 → 4.4.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 +15 -9
- package/README.md +1 -0
- package/browser/ValidatedForm.js +16 -26
- package/browser/hooks.d.ts +2 -0
- package/browser/hooks.js +20 -9
- 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/hooks.d.ts +20 -9
- package/browser/internal/hooks.js +32 -23
- package/browser/internal/logic/getRadioChecked.js +10 -0
- package/browser/internal/reset.d.ts +28 -0
- package/browser/internal/reset.js +13 -0
- package/browser/internal/state/cleanup.d.ts +2 -0
- package/browser/internal/state/cleanup.js +6 -0
- package/browser/internal/state/controlledFieldStore.d.ts +24 -0
- package/browser/internal/state/controlledFieldStore.js +57 -0
- package/browser/internal/state/controlledFields.d.ts +6 -62
- package/browser/internal/state/controlledFields.js +36 -63
- package/browser/internal/state/createFormStore.d.ts +40 -0
- package/browser/internal/state/createFormStore.js +83 -0
- package/browser/internal/state/storeFamily.d.ts +9 -0
- package/browser/internal/state/storeFamily.js +18 -0
- package/browser/internal/state/storeHooks.d.ts +5 -0
- package/browser/internal/state/storeHooks.js +10 -0
- package/browser/internal/state.d.ts +0 -27
- package/browser/internal/state.js +0 -5
- package/browser/unreleased/formStateHooks.d.ts +15 -0
- package/browser/unreleased/formStateHooks.js +23 -14
- package/browser/userFacingFormContext.d.ts +8 -0
- package/browser/userFacingFormContext.js +5 -4
- package/dist/remix-validated-form.cjs.js +17 -0
- package/dist/remix-validated-form.es.js +2844 -0
- package/dist/remix-validated-form.umd.js +17 -0
- package/{build → dist/types}/ValidatedForm.d.ts +0 -0
- package/{build → dist/types}/hooks.d.ts +2 -0
- package/{build → dist/types}/index.d.ts +0 -0
- package/{build → dist/types}/internal/MultiValueMap.d.ts +2 -0
- package/{build → dist/types}/internal/constants.d.ts +0 -0
- package/{build → dist/types}/internal/flatten.d.ts +0 -0
- package/{build → dist/types}/internal/formContext.d.ts +0 -0
- package/{build → dist/types}/internal/getInputProps.d.ts +0 -0
- package/dist/types/internal/hooks.d.ts +32 -0
- package/{build → dist/types}/internal/hydratable.d.ts +0 -0
- package/{build → dist/types}/internal/logic/getCheckboxChecked.d.ts +0 -0
- package/{build → dist/types}/internal/logic/getRadioChecked.d.ts +0 -0
- package/dist/types/internal/state/cleanup.d.ts +2 -0
- package/dist/types/internal/state/controlledFieldStore.d.ts +24 -0
- package/dist/types/internal/state/controlledFields.d.ts +6 -0
- package/dist/types/internal/state/createFormStore.d.ts +40 -0
- package/dist/types/internal/state/storeFamily.d.ts +9 -0
- package/dist/types/internal/state/storeHooks.d.ts +5 -0
- package/{build → dist/types}/internal/submissionCallbacks.d.ts +0 -0
- package/{build → dist/types}/internal/util.d.ts +0 -0
- package/{build → dist/types}/server.d.ts +0 -0
- package/{build → dist/types}/unreleased/formStateHooks.d.ts +15 -0
- package/{build → dist/types}/userFacingFormContext.d.ts +8 -0
- package/{build → dist/types}/validation/createValidator.d.ts +0 -0
- package/{build → dist/types}/validation/types.d.ts +0 -0
- package/package.json +11 -9
- package/src/ValidatedForm.tsx +25 -43
- package/src/hooks.ts +29 -17
- package/src/internal/MultiValueMap.ts +6 -0
- package/src/internal/getInputProps.test.ts +251 -0
- package/src/internal/getInputProps.ts +2 -1
- package/src/internal/hooks.ts +69 -45
- package/src/internal/logic/getRadioChecked.ts +11 -0
- package/src/internal/state/cleanup.ts +8 -0
- package/src/internal/state/controlledFieldStore.ts +91 -0
- package/src/internal/state/controlledFields.ts +78 -0
- package/src/internal/state/createFormStore.ts +152 -0
- package/src/internal/state/storeFamily.ts +24 -0
- package/src/internal/state/storeHooks.ts +22 -0
- package/src/unreleased/formStateHooks.ts +50 -27
- package/src/userFacingFormContext.ts +17 -5
- package/src/validation/validation.test.ts +304 -0
- package/tsconfig.json +4 -1
- package/vite.config.ts +7 -0
- package/.turbo/turbo-test.log +0 -11
- package/browser/components.d.ts +0 -7
- package/browser/components.js +0 -10
- package/browser/internal/SingleTypeMultiValueMap.d.ts +0 -9
- package/browser/internal/SingleTypeMultiValueMap.js +0 -41
- package/browser/internal/customState.d.ts +0 -105
- package/browser/internal/customState.js +0 -46
- package/browser/internal/hooks-valtio.d.ts +0 -18
- package/browser/internal/hooks-valtio.js +0 -110
- package/browser/internal/hooks-zustand.d.ts +0 -16
- package/browser/internal/hooks-zustand.js +0 -100
- package/browser/internal/immerMiddleware.d.ts +0 -6
- package/browser/internal/immerMiddleware.js +0 -7
- package/browser/internal/logic/elementUtils.d.ts +0 -3
- package/browser/internal/logic/elementUtils.js +0 -3
- package/browser/internal/logic/getCheckboxChecked copy.d.ts +0 -1
- package/browser/internal/logic/getCheckboxChecked copy.js +0 -9
- package/browser/internal/logic/setFieldValue.d.ts +0 -1
- package/browser/internal/logic/setFieldValue.js +0 -40
- package/browser/internal/logic/setInputValueInForm.d.ts +0 -1
- package/browser/internal/logic/setInputValueInForm.js +0 -77
- package/browser/internal/setFieldValue.d.ts +0 -20
- package/browser/internal/setFieldValue.js +0 -83
- package/browser/internal/setFormValues.d.ts +0 -2
- package/browser/internal/setFormValues.js +0 -26
- package/browser/internal/state/setFieldValue.d.ts +0 -0
- package/browser/internal/state/setFieldValue.js +0 -1
- package/browser/internal/state-valtio.d.ts +0 -62
- package/browser/internal/state-valtio.js +0 -69
- package/browser/internal/state-zustand.d.ts +0 -47
- package/browser/internal/state-zustand.js +0 -85
- package/browser/internal/test.d.ts +0 -0
- package/browser/internal/test.js +0 -15
- package/browser/internal/useMultiValueMap.d.ts +0 -1
- package/browser/internal/useMultiValueMap.js +0 -11
- package/browser/internal/watch.d.ts +0 -18
- package/browser/internal/watch.js +0 -122
- package/browser/lowLevelHooks.d.ts +0 -0
- package/browser/lowLevelHooks.js +0 -1
- package/browser/test-data/testFormData.d.ts +0 -15
- package/browser/test-data/testFormData.js +0 -46
- package/browser/types.d.ts +0 -1
- package/browser/types.js +0 -1
- package/browser/validation/validation.test.d.ts +0 -1
- package/browser/validation/validation.test.js +0 -274
- package/browser/validation/withYup.d.ts +0 -6
- package/browser/validation/withYup.js +0 -40
- package/browser/validation/withZod.d.ts +0 -6
- package/browser/validation/withZod.js +0 -50
- package/build/ValidatedForm.js +0 -257
- package/build/hooks.js +0 -79
- package/build/index.js +0 -18
- package/build/internal/MultiValueMap.js +0 -44
- package/build/internal/SingleTypeMultiValueMap.d.ts +0 -8
- package/build/internal/SingleTypeMultiValueMap.js +0 -45
- package/build/internal/constants.js +0 -7
- package/build/internal/flatten.js +0 -14
- package/build/internal/formContext.js +0 -5
- package/build/internal/getInputProps.js +0 -57
- package/build/internal/hooks-valtio.d.ts +0 -18
- package/build/internal/hooks-valtio.js +0 -128
- package/build/internal/hooks-zustand.d.ts +0 -16
- package/build/internal/hooks-zustand.js +0 -117
- package/build/internal/hooks.d.ts +0 -21
- package/build/internal/hooks.js +0 -128
- package/build/internal/hydratable.js +0 -17
- package/build/internal/immerMiddleware.d.ts +0 -6
- package/build/internal/immerMiddleware.js +0 -14
- package/build/internal/logic/elementUtils.d.ts +0 -3
- package/build/internal/logic/elementUtils.js +0 -9
- package/build/internal/logic/getCheckboxChecked.js +0 -13
- package/build/internal/logic/getRadioChecked.js +0 -9
- package/build/internal/logic/setFieldValue.d.ts +0 -1
- package/build/internal/logic/setFieldValue.js +0 -47
- package/build/internal/logic/setInputValueInForm.d.ts +0 -1
- package/build/internal/logic/setInputValueInForm.js +0 -84
- package/build/internal/setFormValues.d.ts +0 -2
- package/build/internal/setFormValues.js +0 -33
- package/build/internal/state/atomUtils.d.ts +0 -38
- package/build/internal/state/atomUtils.js +0 -13
- package/build/internal/state/controlledFields.d.ts +0 -62
- package/build/internal/state/controlledFields.js +0 -85
- package/build/internal/state-valtio.d.ts +0 -62
- package/build/internal/state-valtio.js +0 -83
- package/build/internal/state-zustand.d.ts +0 -47
- package/build/internal/state-zustand.js +0 -91
- package/build/internal/state.d.ts +0 -370
- package/build/internal/state.js +0 -76
- package/build/internal/submissionCallbacks.js +0 -17
- package/build/internal/test.d.ts +0 -1
- package/build/internal/test.js +0 -12
- package/build/internal/util.js +0 -41
- package/build/internal/watch.d.ts +0 -20
- package/build/internal/watch.js +0 -126
- package/build/server.js +0 -32
- package/build/types.d.ts +0 -1
- package/build/types.js +0 -2
- package/build/unreleased/formStateHooks.js +0 -59
- package/build/userFacingFormContext.js +0 -30
- package/build/validation/createValidator.js +0 -45
- package/build/validation/types.js +0 -2
- package/src/internal/state/atomUtils.ts +0 -13
- package/src/internal/state.ts +0 -132
package/.turbo/turbo-build.log
CHANGED
@@ -1,9 +1,15 @@
|
|
1
|
-
[2K[1G[2m$
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
1
|
+
[2K[1G[2m$ vite build[22m
|
2
|
+
[36mvite v2.9.5 [32mbuilding for production...[36m[39m
|
3
|
+
transforming...
|
4
|
+
[32m✓[39m 321 modules transformed.
|
5
|
+
rendering chunks...
|
6
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.cjs.js [39m [2m43.86 KiB / gzip: 16.69 KiB[22m
|
7
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.es.js [39m [2m98.55 KiB / gzip: 23.34 KiB[22m
|
8
|
+
[90m[37m[2mdist/[22m[90m[39m[36mremix-validated-form.umd.js [39m [2m44.11 KiB / gzip: 16.80 KiB[22m
|
9
|
+
[32m[39m
|
10
|
+
[32m[36m[vite:dts][39m[32m Start generate declaration files...[39m
|
11
|
+
[32m[36m[vite:dts][39m[32m Declaration files built in 1687ms.[39m
|
12
|
+
[32m[39m
|
13
|
+
No name was provided for external module 'react' in output.globals – guessing 'React'
|
14
|
+
No name was provided for external module '@remix-run/react' in output.globals – guessing 'react'
|
15
|
+
No name was provided for external module '@remix-run/server-runtime' in output.globals – guessing 'serverRuntime'
|
package/README.md
CHANGED
package/browser/ValidatedForm.js
CHANGED
@@ -2,13 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Form as RemixForm, useSubmit } from "@remix-run/react";
|
3
3
|
import uniq from "lodash/uniq";
|
4
4
|
import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
5
|
-
import invariant from "tiny-invariant";
|
6
5
|
import { useIsSubmitting, useIsValid } from "./hooks";
|
7
6
|
import { FORM_ID_FIELD } from "./internal/constants";
|
8
7
|
import { InternalFormContext, } from "./internal/formContext";
|
9
|
-
import { useDefaultValuesFromLoader, useErrorResponseForForm,
|
8
|
+
import { useDefaultValuesFromLoader, useErrorResponseForForm, useHasActiveFormSubmit, useSetFieldErrors, } from "./internal/hooks";
|
10
9
|
import { useMultiValueMap } from "./internal/MultiValueMap";
|
11
|
-
import { cleanupFormState
|
10
|
+
import { cleanupFormState } from "./internal/state/cleanup";
|
11
|
+
import { useControlledFieldStore, useFormStore, } from "./internal/state/storeHooks";
|
12
12
|
import { useSubmitComplete } from "./internal/submissionCallbacks";
|
13
13
|
import { mergeRefs, useDeepEqualsMemo, useIsomorphicLayoutEffect as useLayoutEffect, } from "./internal/util";
|
14
14
|
const getDataFromForm = (el) => new FormData(el);
|
@@ -111,30 +111,19 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
111
111
|
const formRef = useRef(null);
|
112
112
|
const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : RemixForm;
|
113
113
|
const submit = useSubmit();
|
114
|
-
const setFieldErrors =
|
115
|
-
const setFieldError =
|
116
|
-
const reset =
|
117
|
-
const
|
118
|
-
const
|
119
|
-
const
|
120
|
-
const
|
121
|
-
const
|
114
|
+
const setFieldErrors = useSetFieldErrors(formId);
|
115
|
+
const setFieldError = useFormStore(formId, (state) => state.setFieldError);
|
116
|
+
const reset = useFormStore(formId, (state) => state.reset);
|
117
|
+
const resetControlledFields = useControlledFieldStore(formId, (state) => state.reset);
|
118
|
+
const startSubmit = useFormStore(formId, (state) => state.startSubmit);
|
119
|
+
const endSubmit = useFormStore(formId, (state) => state.endSubmit);
|
120
|
+
const syncFormProps = useFormStore(formId, (state) => state.syncFormProps);
|
121
|
+
const setHydrated = useFormStore(formId, (state) => state.setHydrated);
|
122
|
+
const setFormElementInState = useFormStore(formId, (state) => state.setFormElement);
|
122
123
|
useEffect(() => {
|
123
|
-
setHydrated(
|
124
|
+
setHydrated();
|
124
125
|
return () => cleanupFormState(formId);
|
125
126
|
}, [formId, setHydrated]);
|
126
|
-
const validateField = useCallback(async (field) => {
|
127
|
-
invariant(formRef.current, "Cannot find reference to form");
|
128
|
-
const { error } = await validator.validateField(getDataFromForm(formRef.current), field);
|
129
|
-
if (error) {
|
130
|
-
setFieldError({ field, error });
|
131
|
-
return error;
|
132
|
-
}
|
133
|
-
else {
|
134
|
-
setFieldError({ field, error: undefined });
|
135
|
-
return null;
|
136
|
-
}
|
137
|
-
}, [setFieldError, validator]);
|
138
127
|
const customFocusHandlers = useMultiValueMap();
|
139
128
|
const registerReceiveFocus = useCallback((fieldName, handler) => {
|
140
129
|
customFocusHandlers().add(fieldName, handler);
|
@@ -148,8 +137,8 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
148
137
|
action,
|
149
138
|
defaultValues: (_a = providedDefaultValues !== null && providedDefaultValues !== void 0 ? providedDefaultValues : backendDefaultValues) !== null && _a !== void 0 ? _a : {},
|
150
139
|
subaction,
|
151
|
-
validateField,
|
152
140
|
registerReceiveFocus,
|
141
|
+
validator,
|
153
142
|
});
|
154
143
|
}, [
|
155
144
|
action,
|
@@ -157,8 +146,8 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
157
146
|
registerReceiveFocus,
|
158
147
|
subaction,
|
159
148
|
syncFormProps,
|
160
|
-
validateField,
|
161
149
|
backendDefaultValues,
|
150
|
+
validator,
|
162
151
|
]);
|
163
152
|
useEffect(() => {
|
164
153
|
var _a;
|
@@ -227,5 +216,6 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
|
|
227
216
|
if (event.defaultPrevented)
|
228
217
|
return;
|
229
218
|
reset();
|
219
|
+
resetControlledFields();
|
230
220
|
}, children: _jsxs(InternalFormContext.Provider, { value: contextValue, children: [_jsx(FormResetter, { formRef: formRef, resetAfterSubmit: resetAfterSubmit }, void 0), subaction && (_jsx("input", { type: "hidden", value: subaction, name: "subaction" }, void 0)), id && _jsx("input", { type: "hidden", value: id, name: FORM_ID_FIELD }, void 0), children] }, void 0) }, void 0));
|
231
221
|
}
|
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) => (fieldName: string, value: unknown) => void;
|
package/browser/hooks.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { useEffect, useMemo } from "react";
|
2
2
|
import { createGetInputProps, } from "./internal/getInputProps";
|
3
|
-
import { useInternalFormContext, useFieldTouched, useFieldError,
|
4
|
-
import {
|
3
|
+
import { useInternalFormContext, useFieldTouched, useFieldError, useFieldDefaultValue, useClearError, useInternalIsSubmitting, useInternalIsValid, useInternalHasBeenSubmitted, useValidateField, useRegisterReceiveFocus, } from "./internal/hooks";
|
4
|
+
import { useControllableValue, useUpdateControllableValue, } from "./internal/state/controlledFields";
|
5
5
|
/**
|
6
6
|
* Returns whether or not the parent form is currently being submitted.
|
7
7
|
* This is different from remix's `useTransition().submission` in that it
|
@@ -11,7 +11,7 @@ import { formPropsAtom, hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, } f
|
|
11
11
|
*/
|
12
12
|
export const useIsSubmitting = (formId) => {
|
13
13
|
const formContext = useInternalFormContext(formId, "useIsSubmitting");
|
14
|
-
return
|
14
|
+
return useInternalIsSubmitting(formContext.formId);
|
15
15
|
};
|
16
16
|
/**
|
17
17
|
* Returns whether or not the current form is valid.
|
@@ -20,7 +20,7 @@ export const useIsSubmitting = (formId) => {
|
|
20
20
|
*/
|
21
21
|
export const useIsValid = (formId) => {
|
22
22
|
const formContext = useInternalFormContext(formId, "useIsValid");
|
23
|
-
return
|
23
|
+
return useInternalIsValid(formContext.formId);
|
24
24
|
};
|
25
25
|
/**
|
26
26
|
* Provides the data and helpers necessary to set up a field.
|
@@ -30,9 +30,11 @@ export const useField = (name, options) => {
|
|
30
30
|
const formContext = useInternalFormContext(providedFormId, "useField");
|
31
31
|
const defaultValue = useFieldDefaultValue(name, formContext);
|
32
32
|
const [touched, setTouched] = useFieldTouched(name, formContext);
|
33
|
-
const
|
34
|
-
const
|
35
|
-
const
|
33
|
+
const error = useFieldError(name, formContext);
|
34
|
+
const clearError = useClearError(formContext);
|
35
|
+
const hasBeenSubmitted = useInternalHasBeenSubmitted(formContext.formId);
|
36
|
+
const validateField = useValidateField(formContext.formId);
|
37
|
+
const registerReceiveFocus = useRegisterReceiveFocus(formContext.formId);
|
36
38
|
useEffect(() => {
|
37
39
|
if (handleReceiveFocus)
|
38
40
|
return registerReceiveFocus(name, handleReceiveFocus);
|
@@ -40,7 +42,7 @@ export const useField = (name, options) => {
|
|
40
42
|
const field = useMemo(() => {
|
41
43
|
const helpers = {
|
42
44
|
error,
|
43
|
-
clearError: () =>
|
45
|
+
clearError: () => clearError(name),
|
44
46
|
validate: () => {
|
45
47
|
validateField(name);
|
46
48
|
},
|
@@ -60,14 +62,23 @@ export const useField = (name, options) => {
|
|
60
62
|
};
|
61
63
|
}, [
|
62
64
|
error,
|
65
|
+
clearError,
|
63
66
|
defaultValue,
|
64
67
|
touched,
|
65
68
|
setTouched,
|
66
69
|
name,
|
67
70
|
hasBeenSubmitted,
|
68
71
|
options === null || options === void 0 ? void 0 : options.validationBehavior,
|
69
|
-
setError,
|
70
72
|
validateField,
|
71
73
|
]);
|
72
74
|
return field;
|
73
75
|
};
|
76
|
+
export const useControlField = (name, formId) => {
|
77
|
+
const context = useInternalFormContext(formId, "useControlField");
|
78
|
+
const [value, setValue] = useControllableValue(context, name);
|
79
|
+
return [value, setValue];
|
80
|
+
};
|
81
|
+
export const useUpdateControlledField = (formId) => {
|
82
|
+
const context = useInternalFormContext(formId, "useControlField");
|
83
|
+
return useUpdateControllableValue(context.formId);
|
84
|
+
};
|
@@ -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);
|
@@ -1,11 +1,7 @@
|
|
1
|
-
import { Atom, WritableAtom } from "jotai";
|
2
|
-
import { useUpdateAtom } from "jotai/utils";
|
3
1
|
import { FieldErrors, ValidationErrorResponseData } from "..";
|
4
2
|
import { InternalFormContextValue } from "./formContext";
|
5
3
|
import { Hydratable } from "./hydratable";
|
6
|
-
|
7
|
-
export declare const useFormAtom: <Value, Update, Result extends void | Promise<void>>(anAtom: WritableAtom<Value, Update, Result>) => [Value extends Promise<infer V> ? V : Value, import("jotai/core/atom").SetAtom<Update, Result>];
|
8
|
-
export declare const useFormAtomValue: <Value>(anAtom: Atom<Value>) => Value extends Promise<infer V> ? V : Value;
|
4
|
+
import { InternalFormId } from "./state/storeFamily";
|
9
5
|
export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
|
10
6
|
export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
|
11
7
|
export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
|
@@ -14,8 +10,23 @@ export declare const useDefaultValuesForForm: (context: InternalFormContextValue
|
|
14
10
|
[fieldName: string]: any;
|
15
11
|
}>;
|
16
12
|
export declare const useHasActiveFormSubmit: ({ fetcher, }: InternalFormContextValue) => boolean;
|
17
|
-
export declare const useFieldTouched: (field: string, { formId }: InternalFormContextValue) => [boolean, (
|
18
|
-
export declare const useFieldError: (name: string, context: InternalFormContextValue) =>
|
13
|
+
export declare const useFieldTouched: (field: string, { formId }: InternalFormContextValue) => readonly [boolean, (touched: boolean) => void];
|
14
|
+
export declare const useFieldError: (name: string, context: InternalFormContextValue) => string | undefined;
|
15
|
+
export declare const useClearError: (context: InternalFormContextValue) => (field: string) => void;
|
19
16
|
export declare const useFieldDefaultValue: (name: string, context: InternalFormContextValue) => any;
|
20
|
-
export declare const
|
21
|
-
export declare const
|
17
|
+
export declare const useInternalIsSubmitting: (formId: InternalFormId) => boolean;
|
18
|
+
export declare const useInternalIsValid: (formId: InternalFormId) => boolean;
|
19
|
+
export declare const useInternalHasBeenSubmitted: (formId: InternalFormId) => boolean;
|
20
|
+
export declare const useValidateField: (formId: InternalFormId) => (fieldName: string) => Promise<string | null>;
|
21
|
+
export declare const useValidate: (formId: InternalFormId) => () => Promise<void>;
|
22
|
+
export declare const useRegisterReceiveFocus: (formId: InternalFormId) => (fieldName: string, handler: () => void) => () => void;
|
23
|
+
export declare const useSyncedDefaultValues: (formId: InternalFormId) => {
|
24
|
+
[fieldName: string]: any;
|
25
|
+
};
|
26
|
+
export declare const useSetTouched: ({ formId }: InternalFormContextValue) => (field: string, touched: boolean) => void;
|
27
|
+
export declare const useTouchedFields: (formId: InternalFormId) => import("..").TouchedFields;
|
28
|
+
export declare const useFieldErrors: (formId: InternalFormId) => FieldErrors;
|
29
|
+
export declare const useSetFieldErrors: (formId: InternalFormId) => (errors: FieldErrors) => void;
|
30
|
+
export declare const useResetFormElement: (formId: InternalFormId) => () => void;
|
31
|
+
export declare const useFormActionProp: (formId: InternalFormId) => string | undefined;
|
32
|
+
export declare const useFormSubactionProp: (formId: InternalFormId) => string | undefined;
|
@@ -1,16 +1,11 @@
|
|
1
1
|
import { useActionData, useMatches, useTransition } from "@remix-run/react";
|
2
|
-
import { useAtom } from "jotai";
|
3
|
-
import { useAtomValue, useUpdateAtom } from "jotai/utils";
|
4
2
|
import lodashGet from "lodash/get";
|
5
3
|
import { useCallback, useContext } from "react";
|
6
4
|
import invariant from "tiny-invariant";
|
7
5
|
import { formDefaultValuesKey } from "./constants";
|
8
6
|
import { InternalFormContext } from "./formContext";
|
9
7
|
import { hydratable } from "./hydratable";
|
10
|
-
import {
|
11
|
-
export const useFormUpdateAtom = (atom) => useUpdateAtom(atom, ATOM_SCOPE);
|
12
|
-
export const useFormAtom = (anAtom) => useAtom(anAtom, ATOM_SCOPE);
|
13
|
-
export const useFormAtomValue = (anAtom) => useAtomValue(anAtom, ATOM_SCOPE);
|
8
|
+
import { useFormStore } from "./state/storeHooks";
|
14
9
|
export const useInternalFormContext = (formId, hookName) => {
|
15
10
|
const formContext = useContext(InternalFormContext);
|
16
11
|
if (formId)
|
@@ -39,7 +34,7 @@ export function useErrorResponseForForm({ fetcher, subaction, formId, }) {
|
|
39
34
|
}
|
40
35
|
export const useFieldErrorsForForm = (context) => {
|
41
36
|
const response = useErrorResponseForForm(context);
|
42
|
-
const hydrated =
|
37
|
+
const hydrated = useFormStore(context.formId, (state) => state.isHydrated);
|
43
38
|
return hydratable.from(response === null || response === void 0 ? void 0 : response.fieldErrors, hydrated);
|
44
39
|
};
|
45
40
|
export const useDefaultValuesFromLoader = ({ formId, }) => {
|
@@ -57,7 +52,7 @@ export const useDefaultValuesFromLoader = ({ formId, }) => {
|
|
57
52
|
};
|
58
53
|
export const useDefaultValuesForForm = (context) => {
|
59
54
|
const { formId, defaultValuesProp } = context;
|
60
|
-
const hydrated =
|
55
|
+
const hydrated = useFormStore(formId, (state) => state.isHydrated);
|
61
56
|
const errorResponse = useErrorResponseForForm(context);
|
62
57
|
const defaultValuesFromLoader = useDefaultValuesFromLoader(context);
|
63
58
|
// Typical flow is:
|
@@ -82,27 +77,41 @@ export const useHasActiveFormSubmit = ({ fetcher, }) => {
|
|
82
77
|
: !!transition.submission;
|
83
78
|
return hasActiveSubmission;
|
84
79
|
};
|
85
|
-
export const useFieldTouched = (field, { formId }) =>
|
80
|
+
export const useFieldTouched = (field, { formId }) => {
|
81
|
+
const touched = useFormStore(formId, (state) => state.touchedFields[field]);
|
82
|
+
const setFieldTouched = useFormStore(formId, (state) => state.setTouched);
|
83
|
+
const setTouched = useCallback((touched) => setFieldTouched(field, touched), [field, setFieldTouched]);
|
84
|
+
return [touched, setTouched];
|
85
|
+
};
|
86
86
|
export const useFieldError = (name, context) => {
|
87
87
|
const fieldErrors = useFieldErrorsForForm(context);
|
88
|
-
const
|
89
|
-
return [
|
90
|
-
|
91
|
-
|
92
|
-
|
88
|
+
const state = useFormStore(context.formId, (state) => state.fieldErrors[name]);
|
89
|
+
return fieldErrors.map((fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name]).hydrateTo(state);
|
90
|
+
};
|
91
|
+
export const useClearError = (context) => {
|
92
|
+
const { formId } = context;
|
93
|
+
return useFormStore(formId, (state) => state.clearFieldError);
|
93
94
|
};
|
94
95
|
export const useFieldDefaultValue = (name, context) => {
|
95
96
|
const defaultValues = useDefaultValuesForForm(context);
|
96
|
-
const
|
97
|
+
const state = useSyncedDefaultValues(context.formId);
|
97
98
|
return defaultValues
|
98
99
|
.map((val) => lodashGet(val, name))
|
99
100
|
.hydrateTo(lodashGet(state, name));
|
100
101
|
};
|
101
|
-
export const
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
export const
|
106
|
-
|
107
|
-
|
108
|
-
};
|
102
|
+
export const useInternalIsSubmitting = (formId) => useFormStore(formId, (state) => state.isSubmitting);
|
103
|
+
export const useInternalIsValid = (formId) => useFormStore(formId, (state) => state.isValid());
|
104
|
+
export const useInternalHasBeenSubmitted = (formId) => useFormStore(formId, (state) => state.hasBeenSubmitted);
|
105
|
+
export const useValidateField = (formId) => useFormStore(formId, (state) => state.validateField);
|
106
|
+
export const useValidate = (formId) => useFormStore(formId, (state) => state.validate);
|
107
|
+
const noOpReceiver = () => () => { };
|
108
|
+
export const useRegisterReceiveFocus = (formId) => useFormStore(formId, (state) => { var _a, _b; return (_b = (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.registerReceiveFocus) !== null && _b !== void 0 ? _b : noOpReceiver; });
|
109
|
+
const defaultDefaultValues = {};
|
110
|
+
export const useSyncedDefaultValues = (formId) => useFormStore(formId, (state) => { var _a, _b; return (_b = (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.defaultValues) !== null && _b !== void 0 ? _b : defaultDefaultValues; });
|
111
|
+
export const useSetTouched = ({ formId }) => useFormStore(formId, (state) => state.setTouched);
|
112
|
+
export const useTouchedFields = (formId) => useFormStore(formId, (state) => state.touchedFields);
|
113
|
+
export const useFieldErrors = (formId) => useFormStore(formId, (state) => state.fieldErrors);
|
114
|
+
export const useSetFieldErrors = (formId) => useFormStore(formId, (state) => state.setFieldErrors);
|
115
|
+
export const useResetFormElement = (formId) => useFormStore(formId, (state) => state.resetFormElement);
|
116
|
+
export const useFormActionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.action; });
|
117
|
+
export const useFormSubactionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.subaction; });
|
@@ -3,3 +3,13 @@ export const getRadioChecked = (radioValue = "on", newValue) => {
|
|
3
3
|
return newValue === radioValue;
|
4
4
|
return undefined;
|
5
5
|
};
|
6
|
+
if (import.meta.vitest) {
|
7
|
+
const { it, expect } = import.meta.vitest;
|
8
|
+
it("getRadioChecked", () => {
|
9
|
+
expect(getRadioChecked("on", "on")).toBe(true);
|
10
|
+
expect(getRadioChecked("on", undefined)).toBe(undefined);
|
11
|
+
expect(getRadioChecked("trueValue", undefined)).toBe(undefined);
|
12
|
+
expect(getRadioChecked("trueValue", "bob")).toBe(false);
|
13
|
+
expect(getRadioChecked("trueValue", "trueValue")).toBe(true);
|
14
|
+
});
|
15
|
+
}
|
@@ -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
|
+
}));
|
@@ -0,0 +1,24 @@
|
|
1
|
+
export declare type ControlledFieldState = {
|
2
|
+
fields: {
|
3
|
+
[fieldName: string]: {
|
4
|
+
refCount: number;
|
5
|
+
value: unknown;
|
6
|
+
defaultValue?: unknown;
|
7
|
+
hydrated: boolean;
|
8
|
+
valueUpdatePromise: Promise<void> | undefined;
|
9
|
+
resolveValueUpdate: (() => void) | undefined;
|
10
|
+
} | undefined;
|
11
|
+
};
|
12
|
+
register: (fieldName: string) => void;
|
13
|
+
unregister: (fieldName: string) => void;
|
14
|
+
setValue: (fieldName: string, value: unknown) => void;
|
15
|
+
hydrateWithDefault: (fieldName: string, defaultValue: unknown) => void;
|
16
|
+
awaitValueUpdate: (fieldName: string) => Promise<void>;
|
17
|
+
reset: () => void;
|
18
|
+
};
|
19
|
+
export declare const controlledFieldStore: {
|
20
|
+
(formId: import("./storeFamily").InternalFormId): import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ControlledFieldState>, "setState"> & {
|
21
|
+
setState(nextStateOrUpdater: ControlledFieldState | Partial<ControlledFieldState> | ((state: import("immer/dist/internal").WritableDraft<ControlledFieldState>) => void), shouldReplace?: boolean | undefined): void;
|
22
|
+
}>;
|
23
|
+
remove(formId: import("./storeFamily").InternalFormId): void;
|
24
|
+
};
|
@@ -0,0 +1,57 @@
|
|
1
|
+
import create from "zustand";
|
2
|
+
import { immer } from "zustand/middleware/immer";
|
3
|
+
import { storeFamily } from "./storeFamily";
|
4
|
+
export const controlledFieldStore = storeFamily(() => create()(immer((set, get, api) => ({
|
5
|
+
fields: {},
|
6
|
+
register: (field) => set((state) => {
|
7
|
+
if (state.fields[field]) {
|
8
|
+
state.fields[field].refCount++;
|
9
|
+
}
|
10
|
+
else {
|
11
|
+
state.fields[field] = {
|
12
|
+
refCount: 1,
|
13
|
+
value: undefined,
|
14
|
+
hydrated: false,
|
15
|
+
valueUpdatePromise: undefined,
|
16
|
+
resolveValueUpdate: undefined,
|
17
|
+
};
|
18
|
+
}
|
19
|
+
}),
|
20
|
+
unregister: (field) => set((state) => {
|
21
|
+
const fieldState = state.fields[field];
|
22
|
+
if (!fieldState)
|
23
|
+
return;
|
24
|
+
fieldState.refCount--;
|
25
|
+
if (fieldState.refCount === 0)
|
26
|
+
delete state.fields[field];
|
27
|
+
}),
|
28
|
+
setValue: (field, value) => set((state) => {
|
29
|
+
const fieldState = state.fields[field];
|
30
|
+
if (!fieldState)
|
31
|
+
return;
|
32
|
+
fieldState.value = value;
|
33
|
+
const promise = new Promise((resolve) => {
|
34
|
+
fieldState.resolveValueUpdate = resolve;
|
35
|
+
});
|
36
|
+
fieldState.valueUpdatePromise = promise;
|
37
|
+
}),
|
38
|
+
hydrateWithDefault: (field, defaultValue) => set((state) => {
|
39
|
+
const fieldState = state.fields[field];
|
40
|
+
if (!fieldState)
|
41
|
+
return;
|
42
|
+
fieldState.value = defaultValue;
|
43
|
+
fieldState.defaultValue = defaultValue;
|
44
|
+
fieldState.hydrated = true;
|
45
|
+
}),
|
46
|
+
awaitValueUpdate: async (field) => {
|
47
|
+
var _a;
|
48
|
+
await ((_a = get().fields[field]) === null || _a === void 0 ? void 0 : _a.valueUpdatePromise);
|
49
|
+
},
|
50
|
+
reset: () => set((state) => {
|
51
|
+
Object.values(state.fields).forEach((field) => {
|
52
|
+
if (!field)
|
53
|
+
return;
|
54
|
+
field.value = field.defaultValue;
|
55
|
+
});
|
56
|
+
}),
|
57
|
+
}))));
|
@@ -1,62 +1,6 @@
|
|
1
|
-
import {
|
2
|
-
import { InternalFormId } from "./
|
3
|
-
export declare const
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
<Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
|
8
|
-
<Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
|
9
|
-
} & {
|
10
|
-
<Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
|
11
|
-
unstable_promise: true;
|
12
|
-
}): Value_3 | Promise<Value_3>;
|
13
|
-
<Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
|
14
|
-
unstable_promise: true;
|
15
|
-
}): Value_4 | Promise<Value_4>;
|
16
|
-
<Value_5>(atom: import("jotai").Atom<Value_5>, options: {
|
17
|
-
unstable_promise: true;
|
18
|
-
}): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
|
19
|
-
}, set: {
|
20
|
-
<Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
|
21
|
-
<Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
|
22
|
-
}, update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void;
|
23
|
-
onMount?: (<S extends (update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void>(setAtom: S) => void | (() => void)) | undefined;
|
24
|
-
} & {
|
25
|
-
init: Record<string, PrimitiveAtom<unknown>>;
|
26
|
-
};
|
27
|
-
remove(param: InternalFormId): void;
|
28
|
-
setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
|
29
|
-
};
|
30
|
-
export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
|
31
|
-
write: (get: {
|
32
|
-
<Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
|
33
|
-
<Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
|
34
|
-
<Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
|
35
|
-
} & {
|
36
|
-
<Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
|
37
|
-
unstable_promise: true;
|
38
|
-
}): Value_3 | Promise<Value_3>;
|
39
|
-
<Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
|
40
|
-
unstable_promise: true;
|
41
|
-
}): Value_4 | Promise<Value_4>;
|
42
|
-
<Value_5>(atom: import("jotai").Atom<Value_5>, options: {
|
43
|
-
unstable_promise: true;
|
44
|
-
}): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
|
45
|
-
}, set: {
|
46
|
-
<Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
|
47
|
-
<Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
|
48
|
-
}, update: {
|
49
|
-
formId: InternalFormId;
|
50
|
-
field: string;
|
51
|
-
value: unknown;
|
52
|
-
}) => Promise<void>;
|
53
|
-
onMount?: (<S extends (update: {
|
54
|
-
formId: InternalFormId;
|
55
|
-
field: string;
|
56
|
-
value: unknown;
|
57
|
-
}) => Promise<void>>(setAtom: S) => void | (() => void)) | undefined;
|
58
|
-
} & {
|
59
|
-
init: null;
|
60
|
-
};
|
61
|
-
export declare const useControlledFieldValue: (formId: InternalFormId, field: string) => any;
|
62
|
-
export declare const useControllableValue: (formId: InternalFormId, field: string) => readonly [any, (value: unknown) => Promise<void>];
|
1
|
+
import { InternalFormContextValue } from "../formContext";
|
2
|
+
import { InternalFormId } from "./storeFamily";
|
3
|
+
export declare const useControlledFieldValue: (context: InternalFormContextValue, field: string) => any;
|
4
|
+
export declare const useControllableValue: (context: InternalFormContextValue, field: string) => readonly [any, (value: unknown) => void];
|
5
|
+
export declare const useUpdateControllableValue: (formId: InternalFormId) => (fieldName: string, value: unknown) => void;
|
6
|
+
export declare const useAwaitValue: (formId: InternalFormId) => (fieldName: string) => Promise<void>;
|