remix-validated-form 4.1.4-beta.0 → 4.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/browser/ValidatedForm.js +31 -36
  3. package/browser/hooks.js +13 -16
  4. package/browser/internal/customState.d.ts +105 -0
  5. package/browser/internal/customState.js +46 -0
  6. package/browser/internal/getInputProps.js +4 -14
  7. package/browser/internal/hooks.d.ts +14 -15
  8. package/browser/internal/hooks.js +37 -39
  9. package/browser/internal/logic/elementUtils.d.ts +3 -0
  10. package/browser/internal/logic/elementUtils.js +3 -0
  11. package/browser/internal/logic/setInputValueInForm.js +9 -52
  12. package/browser/internal/setFieldValue.d.ts +0 -0
  13. package/browser/internal/setFieldValue.js +0 -0
  14. package/browser/internal/setFormValues.d.ts +0 -0
  15. package/browser/internal/setFormValues.js +0 -0
  16. package/browser/internal/state.d.ts +339 -238
  17. package/browser/internal/state.js +59 -72
  18. package/browser/internal/watch.d.ts +18 -0
  19. package/browser/internal/watch.js +122 -0
  20. package/browser/unreleased/formStateHooks.d.ts +39 -0
  21. package/browser/unreleased/formStateHooks.js +54 -0
  22. package/browser/userFacingFormContext.js +9 -23
  23. package/build/ValidatedForm.js +30 -35
  24. package/build/hooks.js +11 -14
  25. package/build/internal/getInputProps.js +4 -14
  26. package/build/internal/hooks.d.ts +14 -15
  27. package/build/internal/hooks.js +39 -41
  28. package/build/internal/logic/elementUtils.d.ts +3 -0
  29. package/build/internal/logic/elementUtils.js +9 -0
  30. package/build/internal/logic/setInputValueInForm.js +12 -55
  31. package/build/internal/setFormValues.d.ts +0 -0
  32. package/build/internal/setFormValues.js +0 -0
  33. package/build/internal/state/controlledFields.js +11 -2
  34. package/build/internal/state.d.ts +339 -238
  35. package/build/internal/state.js +61 -77
  36. package/build/internal/watch.d.ts +20 -0
  37. package/build/internal/watch.js +126 -0
  38. package/build/unreleased/formStateHooks.d.ts +39 -0
  39. package/build/unreleased/formStateHooks.js +59 -0
  40. package/build/userFacingFormContext.js +9 -23
  41. package/package.json +1 -2
  42. package/src/ValidatedForm.tsx +48 -52
  43. package/src/hooks.ts +15 -26
  44. package/src/internal/getInputProps.ts +4 -14
  45. package/src/internal/hooks.ts +60 -72
  46. package/src/internal/hydratable.ts +28 -0
  47. package/src/internal/logic/getCheckboxChecked.ts +10 -0
  48. package/src/internal/logic/getRadioChecked.ts +7 -0
  49. package/src/internal/state/atomUtils.ts +13 -0
  50. package/src/internal/state.ts +99 -177
  51. package/src/unreleased/formStateHooks.ts +113 -0
  52. package/src/userFacingFormContext.ts +14 -53
@@ -1,82 +1,69 @@
1
1
  import { atom } from "jotai";
2
- import { atomWithImmer } from "jotai/immer";
3
2
  import { atomFamily, selectAtom } from "jotai/utils";
4
- import lodashGet from "lodash/get";
3
+ import omit from "lodash/omit";
4
+ import { fieldAtomFamily, formAtomFamily, } from "./state/atomUtils";
5
5
  export const ATOM_SCOPE = Symbol("remix-validated-form-scope");
6
- export const formRegistry = atomFamily((formId) => atomWithImmer({
7
- hydrated: false,
8
- isSubmitting: false,
9
- hasBeenSubmitted: false,
10
- touchedFields: {},
11
- // The symbol version is just to keep things straight with the `atomFamily`
12
- formId: typeof formId === "string" ? formId : undefined,
13
- // Will change upon hydration -- these will never actually be used
6
+ export const isHydratedAtom = formAtomFamily(false);
7
+ export const isSubmittingAtom = formAtomFamily(false);
8
+ export const hasBeenSubmittedAtom = formAtomFamily(false);
9
+ export const fieldErrorsAtom = formAtomFamily({});
10
+ export const touchedFieldsAtom = formAtomFamily({});
11
+ export const formPropsAtom = formAtomFamily({
14
12
  validateField: () => Promise.resolve(null),
15
13
  registerReceiveFocus: () => () => { },
14
+ defaultValues: {},
15
+ });
16
+ export const formElementAtom = formAtomFamily(null);
17
+ //// Everything below is derived from the above
18
+ export const cleanupFormState = (formId) => {
19
+ [
20
+ isHydratedAtom,
21
+ isSubmittingAtom,
22
+ hasBeenSubmittedAtom,
23
+ fieldErrorsAtom,
24
+ touchedFieldsAtom,
25
+ formPropsAtom,
26
+ ].forEach((formAtom) => formAtom.remove(formId));
27
+ };
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);
16
33
  }));
17
- export const fieldErrorAtom = (name) => (formAtom) => selectAtom(formAtom, (formState) => { var _a; return (_a = formState.fieldErrors) === null || _a === void 0 ? void 0 : _a[name]; });
18
- export const fieldTouchedAtom = (name) => (formAtom) => selectAtom(formAtom, (formState) => formState.touchedFields[name]);
19
- export const fieldDefaultValueAtom = (name) => (formAtom) => selectAtom(formAtom, (formState) => formState.defaultValues && lodashGet(formState.defaultValues, name));
20
- // Selector atoms
21
- export const formSelectorAtom = (selector) => (formAtom) => selectAtom(formAtom, selector);
22
- export const fieldErrorsAtom = formSelectorAtom((state) => state.fieldErrors);
23
- export const touchedFieldsAtom = formSelectorAtom((state) => state.touchedFields);
24
- export const actionAtom = formSelectorAtom((state) => state.action);
25
- export const hasBeenSubmittedAtom = formSelectorAtom((state) => state.hasBeenSubmitted);
26
- export const validateFieldAtom = formSelectorAtom((state) => state.validateField);
27
- export const registerReceiveFocusAtom = formSelectorAtom((state) => state.registerReceiveFocus);
28
- export const isSubmittingAtom = formSelectorAtom((state) => state.isSubmitting);
29
- export const defaultValuesAtom = formSelectorAtom((state) => state.defaultValues);
30
- export const isValidAtom = formSelectorAtom((state) => { var _a; return Object.keys((_a = state.fieldErrors) !== null && _a !== void 0 ? _a : {}).length === 0; });
31
- export const isHydratedAtom = formSelectorAtom((state) => state.hydrated);
32
- export const clearErrorAtom = atom(null, (get, set, { name, formAtom }) => set(formAtom, (state) => {
33
- var _a;
34
- (_a = state.fieldErrors) === null || _a === void 0 ? true : delete _a[name];
35
- return state;
34
+ export const startSubmitAtom = atomFamily((formId) => atom(null, (_get, set) => {
35
+ set(isSubmittingAtom(formId), true);
36
+ set(hasBeenSubmittedAtom(formId), true);
36
37
  }));
37
- export const addErrorAtom = atom(null, (get, set, { name, formAtom, error }) => set(formAtom, (state) => {
38
- if (!state.fieldErrors)
39
- state.fieldErrors = {};
40
- state.fieldErrors[name] = error;
41
- return state;
38
+ export const endSubmitAtom = atomFamily((formId) => atom(null, (_get, set) => {
39
+ set(isSubmittingAtom(formId), false);
42
40
  }));
43
- export const setFieldErrorsAtom = atom(null, (get, set, { formAtom, fieldErrors }) => set(formAtom, (state) => {
44
- state.fieldErrors = fieldErrors;
45
- return state;
41
+ export const setTouchedAtom = atomFamily((formId) => atom(null, (get, set, { field, touched }) => {
42
+ const prev = get(touchedFieldsAtom(formId));
43
+ if (prev[field] !== touched) {
44
+ set(touchedFieldsAtom(formId), {
45
+ ...prev,
46
+ [field]: touched,
47
+ });
48
+ }
46
49
  }));
47
- export const setTouchedAtom = atom(null, (get, set, { name, formAtom, touched }) => set(formAtom, (state) => {
48
- state.touchedFields[name] = touched;
49
- return state;
50
+ export const setFieldErrorAtom = atomFamily((formId) => atom(null, (get, set, { field, error }) => {
51
+ const prev = get(fieldErrorsAtom(formId));
52
+ if (error === undefined && field in prev) {
53
+ set(fieldErrorsAtom(formId), omit(prev, field));
54
+ }
55
+ if (error !== undefined && prev[field] !== error) {
56
+ set(fieldErrorsAtom(formId), {
57
+ ...get(fieldErrorsAtom(formId)),
58
+ [field]: error,
59
+ });
60
+ }
50
61
  }));
51
- export const resetAtom = atom(null, (get, set, { formAtom }) => {
52
- set(formAtom, (state) => {
53
- state.fieldErrors = {};
54
- state.touchedFields = {};
55
- state.hasBeenSubmitted = false;
56
- return state;
57
- });
58
- });
59
- export const startSubmitAtom = atom(null, (get, set, { formAtom }) => {
60
- set(formAtom, (state) => {
61
- state.hasBeenSubmitted = true;
62
- state.isSubmitting = true;
63
- return state;
64
- });
65
- });
66
- export const endSubmitAtom = atom(null, (get, set, { formAtom }) => {
67
- set(formAtom, (state) => {
68
- state.isSubmitting = false;
69
- return state;
70
- });
71
- });
72
- export const syncFormContextAtom = atom(null, (get, set, { defaultValues, action, subaction, formAtom, validateField, registerReceiveFocus, }) => {
73
- set(formAtom, (state) => {
74
- state.defaultValues = defaultValues;
75
- state.action = action;
76
- state.subaction = subaction;
77
- state.registerReceiveFocus = registerReceiveFocus;
78
- state.validateField = validateField;
79
- state.hydrated = true;
80
- return state;
81
- });
82
- });
62
+ //// Field specific
63
+ export const fieldTouchedAtom = fieldAtomFamily(({ formId, field }) => atom((get) => get(touchedFieldsAtom(formId))[field], (_get, set, touched) => {
64
+ set(setTouchedAtom(formId), { field, touched });
65
+ }));
66
+ export const fieldErrorAtom = fieldAtomFamily(({ formId, field }) => atom((get) => get(fieldErrorsAtom(formId))[field], (_get, set, error) => {
67
+ set(setFieldErrorAtom(formId), { field, error });
68
+ }));
69
+ export const fieldDefaultValueAtom = fieldAtomFamily(({ formId, field }) => selectAtom(formPropsAtom(formId), (state) => state.defaultValues[field]));
@@ -0,0 +1,18 @@
1
+ declare type ParseInfo = {
2
+ value: unknown;
3
+ type: string;
4
+ isRepeated: boolean;
5
+ checked?: boolean;
6
+ };
7
+ declare type BaseWatchOptions<T> = {
8
+ formId?: string;
9
+ parse?: (info: ParseInfo) => T;
10
+ };
11
+ declare type UseWatchType = {
12
+ <T, U extends boolean | undefined>(name: string, options?: BaseWatchOptions<T> & {
13
+ repeatable: U | undefined;
14
+ }): U extends true ? T[] : T;
15
+ <T>(name: string, options?: BaseWatchOptions<T>): T;
16
+ };
17
+ export declare const useWatch: UseWatchType;
18
+ export {};
@@ -0,0 +1,122 @@
1
+ import { atom } from "jotai";
2
+ import { selectAtom } from "jotai/utils";
3
+ import isNaN from "lodash/isNaN";
4
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
5
+ import invariant from "tiny-invariant";
6
+ import { useInternalFormContext, useFieldDefaultValue, useFormAtomValue, } from "./hooks";
7
+ import { isCheckbox, isMultiselect, isRadio } from "./logic/elementUtils";
8
+ import { formElementAtom } from "./state";
9
+ import { fieldAtomFamily } from "./state/atomUtils";
10
+ import { controlledFieldsAtom } from "./state/controlledFields";
11
+ const emptyAtom = atom(undefined); // Always empty -- just used as a default value
12
+ const watchControlledFieldAtom = fieldAtomFamily(({ field, formId }) => selectAtom(controlledFieldsAtom(formId), (fields) => {
13
+ var _a;
14
+ return ({
15
+ present: field in fields,
16
+ valueAtom: (_a = fields[field]) !== null && _a !== void 0 ? _a : emptyAtom,
17
+ });
18
+ }, (a, b) => a.present === b.present && a.valueAtom === b.valueAtom));
19
+ const defaultParse = ({ value, type, isRepeated, checked, }) => {
20
+ if (type === "number") {
21
+ if (value === "")
22
+ return undefined;
23
+ const result = Number(value);
24
+ if (isNaN(result))
25
+ throw new Error("Value is not a number");
26
+ return result;
27
+ }
28
+ if (type === "checkbox" && !isRepeated) {
29
+ return checked;
30
+ }
31
+ return value;
32
+ };
33
+ const getInputValues = (node, multipleInputs = false) => {
34
+ if (!node)
35
+ return [];
36
+ if (node instanceof RadioNodeList) {
37
+ return [...node].flatMap((el) => getInputValues(el, true));
38
+ }
39
+ if (isMultiselect(node)) {
40
+ return [
41
+ {
42
+ type: "select",
43
+ value: [...node.options]
44
+ .filter((opt) => opt.selected)
45
+ .map((opt) => opt.value),
46
+ },
47
+ ];
48
+ }
49
+ if (isCheckbox(node)) {
50
+ if (node.checked || !multipleInputs)
51
+ return [{ type: "checkbox", value: node.value, checked: node.checked }];
52
+ return [];
53
+ }
54
+ if (isRadio(node)) {
55
+ if (node.checked)
56
+ return [{ type: "radio", value: node.value, checked: node.checked }];
57
+ return [];
58
+ }
59
+ const input = node;
60
+ return [{ type: input.type, value: input.value, checked: input.checked }];
61
+ };
62
+ export const useWatch = (field, options) => {
63
+ const { formId, parse = defaultParse, repeatable = false, } = options !== null && options !== void 0 ? options : {};
64
+ const context = useInternalFormContext(formId, "useWatch");
65
+ const defaultValue = useFieldDefaultValue(field, context);
66
+ const hasSynced = useRef(false);
67
+ const [inputValues, setValue] = useState([]);
68
+ const formElement = useFormAtomValue(formElementAtom(context.formId));
69
+ const controlledField = useFormAtomValue(watchControlledFieldAtom({ formId: context.formId, field }));
70
+ const controlledValue = useFormAtomValue(controlledField.valueAtom);
71
+ const shouldSyncNativeInputValue = !controlledField.present && formElement;
72
+ const syncFieldValue = useCallback(() => {
73
+ invariant(formElement, `Unable to find form element for form. Watching field ${field}`);
74
+ hasSynced.current = true;
75
+ // We pull the values out using `form.elements` instead of `FormData`
76
+ // so that we can access the `type` of the input.
77
+ setValue(getInputValues(formElement.elements.namedItem(field)));
78
+ }, [field, formElement]);
79
+ // Should set the field values after the initial render
80
+ useEffect(() => {
81
+ if (shouldSyncNativeInputValue)
82
+ syncFieldValue();
83
+ }, [
84
+ controlledField.present,
85
+ formElement,
86
+ shouldSyncNativeInputValue,
87
+ syncFieldValue,
88
+ ]);
89
+ useEffect(() => {
90
+ if (shouldSyncNativeInputValue) {
91
+ const listener = async (event) => {
92
+ if (!(event.target instanceof HTMLElement))
93
+ return;
94
+ const target = event.target;
95
+ const { form: targetForm, name: targetName } = target;
96
+ if (targetForm === formElement && targetName === field) {
97
+ syncFieldValue();
98
+ }
99
+ };
100
+ window.addEventListener("change", listener);
101
+ window.addEventListener("input", listener);
102
+ return () => {
103
+ window.removeEventListener("change", listener);
104
+ window.removeEventListener("input", listener);
105
+ };
106
+ }
107
+ }, [field, formElement, shouldSyncNativeInputValue, syncFieldValue]);
108
+ const parsedValue = useMemo(() => {
109
+ const parsed = inputValues.map(({ type, value, checked }) => parse({
110
+ value,
111
+ type,
112
+ isRepeated: inputValues.length > 1 || repeatable,
113
+ checked,
114
+ }));
115
+ return parsed.length > 1 || repeatable ? parsed : parsed[0];
116
+ }, [parse, repeatable, inputValues]);
117
+ if (controlledField.present)
118
+ return controlledValue;
119
+ if (!hasSynced.current)
120
+ return defaultValue !== null && defaultValue !== void 0 ? defaultValue : (repeatable ? [] : undefined);
121
+ return parsedValue;
122
+ };
@@ -0,0 +1,39 @@
1
+ import { FieldErrors, TouchedFields } from "../validation/types";
2
+ export declare type FormState = {
3
+ fieldErrors: FieldErrors;
4
+ isSubmitting: boolean;
5
+ hasBeenSubmitted: boolean;
6
+ touchedFields: TouchedFields;
7
+ defaultValues: {
8
+ [fieldName: string]: any;
9
+ };
10
+ action?: string;
11
+ subaction?: string;
12
+ isValid: boolean;
13
+ };
14
+ /**
15
+ * Returns information about the form.
16
+ *
17
+ * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
18
+ */
19
+ export declare const useFormState: (formId?: string | undefined) => FormState;
20
+ export declare type FormHelpers = {
21
+ /**
22
+ * Clear the error of the specified field.
23
+ */
24
+ clearError: (fieldName: string) => void;
25
+ /**
26
+ * Validate the specified field.
27
+ */
28
+ validateField: (fieldName: string) => Promise<string | null>;
29
+ /**
30
+ * Change the touched state of the specified field.
31
+ */
32
+ setTouched: (fieldName: string, touched: boolean) => void;
33
+ };
34
+ /**
35
+ * Returns helpers that can be used to update the form state.
36
+ *
37
+ * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
38
+ */
39
+ export declare const useFormHelpers: (formId?: string | undefined) => FormHelpers;
@@ -0,0 +1,54 @@
1
+ import { useMemo } from "react";
2
+ import { useInternalFormContext, useClearError, useSetTouched, useDefaultValuesForForm, useFieldErrorsForForm, useFormAtomValue, } from "../internal/hooks";
3
+ import { fieldErrorsAtom, formPropsAtom, hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, touchedFieldsAtom, } from "../internal/state";
4
+ /**
5
+ * Returns information about the form.
6
+ *
7
+ * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
8
+ */
9
+ export const useFormState = (formId) => {
10
+ const formContext = useInternalFormContext(formId, "useIsValid");
11
+ const formProps = useFormAtomValue(formPropsAtom(formContext.formId));
12
+ const isSubmitting = useFormAtomValue(isSubmittingAtom(formContext.formId));
13
+ const hasBeenSubmitted = useFormAtomValue(hasBeenSubmittedAtom(formContext.formId));
14
+ const touchedFields = useFormAtomValue(touchedFieldsAtom(formContext.formId));
15
+ const isValid = useFormAtomValue(isValidAtom(formContext.formId));
16
+ const defaultValuesToUse = useDefaultValuesForForm(formContext);
17
+ const hydratedDefaultValues = defaultValuesToUse.hydrateTo(formProps.defaultValues);
18
+ const fieldErrorsFromState = useFormAtomValue(fieldErrorsAtom(formContext.formId));
19
+ const fieldErrorsToUse = useFieldErrorsForForm(formContext);
20
+ const hydratedFieldErrors = fieldErrorsToUse.hydrateTo(fieldErrorsFromState);
21
+ return useMemo(() => ({
22
+ ...formProps,
23
+ defaultValues: hydratedDefaultValues,
24
+ fieldErrors: hydratedFieldErrors !== null && hydratedFieldErrors !== void 0 ? hydratedFieldErrors : {},
25
+ hasBeenSubmitted,
26
+ isSubmitting,
27
+ touchedFields,
28
+ isValid,
29
+ }), [
30
+ formProps,
31
+ hasBeenSubmitted,
32
+ hydratedDefaultValues,
33
+ hydratedFieldErrors,
34
+ isSubmitting,
35
+ isValid,
36
+ touchedFields,
37
+ ]);
38
+ };
39
+ /**
40
+ * Returns helpers that can be used to update the form state.
41
+ *
42
+ * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
43
+ */
44
+ export const useFormHelpers = (formId) => {
45
+ const formContext = useInternalFormContext(formId, "useFormHelpers");
46
+ const setTouched = useSetTouched(formContext);
47
+ const { validateField } = useFormAtomValue(formPropsAtom(formContext.formId));
48
+ const clearError = useClearError(formContext);
49
+ return useMemo(() => ({
50
+ setTouched,
51
+ validateField,
52
+ clearError,
53
+ }), [clearError, setTouched, validateField]);
54
+ };
@@ -1,40 +1,26 @@
1
1
  import { useCallback } from "react";
2
- import { useIsSubmitting, useIsValid } from "./hooks";
3
- import { useClearError, useContextSelectAtom, useDefaultValuesForForm, useFieldErrorsForForm, useHydratableSelector, useInternalFormContext, useSetTouched, } from "./internal/hooks";
4
- import { actionAtom, defaultValuesAtom, fieldErrorsAtom, hasBeenSubmittedAtom, registerReceiveFocusAtom, touchedFieldsAtom, validateFieldAtom, } from "./internal/state";
2
+ import { useFormAtomValue, useInternalFormContext } from "./internal/hooks";
3
+ import { formPropsAtom } from "./internal/state";
4
+ import { useFormHelpers, useFormState } from "./unreleased/formStateHooks";
5
5
  /**
6
6
  * Provides access to some of the internal state of the form.
7
7
  */
8
8
  export const useFormContext = (formId) => {
9
9
  // Try to access context so we get our error specific to this hook if it's not there
10
10
  const context = useInternalFormContext(formId, "useFormContext");
11
- const action = useContextSelectAtom(context.formId, actionAtom);
12
- const isSubmitting = useIsSubmitting(formId);
13
- const hasBeenSubmitted = useContextSelectAtom(context.formId, hasBeenSubmittedAtom);
14
- const isValid = useIsValid(formId);
15
- const defaultValues = useHydratableSelector(context, defaultValuesAtom, useDefaultValuesForForm(context));
16
- const fieldErrors = useHydratableSelector(context, fieldErrorsAtom, useFieldErrorsForForm(context));
17
- const setFieldTouched = useSetTouched(context);
18
- const touchedFields = useContextSelectAtom(context.formId, touchedFieldsAtom);
19
- const validateField = useContextSelectAtom(context.formId, validateFieldAtom);
20
- const registerReceiveFocus = useContextSelectAtom(context.formId, registerReceiveFocusAtom);
21
- const internalClearError = useClearError(context);
11
+ const state = useFormState(formId);
12
+ const { clearError: internalClearError, setTouched, validateField, } = useFormHelpers(formId);
13
+ const { registerReceiveFocus } = useFormAtomValue(formPropsAtom(context.formId));
22
14
  const clearError = useCallback((...names) => {
23
15
  names.forEach((name) => {
24
16
  internalClearError(name);
25
17
  });
26
18
  }, [internalClearError]);
27
19
  return {
28
- isSubmitting,
29
- hasBeenSubmitted,
30
- isValid,
31
- defaultValues,
32
- clearError,
33
- fieldErrors: fieldErrors !== null && fieldErrors !== void 0 ? fieldErrors : {},
34
- action,
35
- setFieldTouched,
36
- touchedFields,
20
+ ...state,
21
+ setFieldTouched: setTouched,
37
22
  validateField,
23
+ clearError,
38
24
  registerReceiveFocus,
39
25
  };
40
26
  };
@@ -116,18 +116,12 @@ function formEventProxy(event) {
116
116
  },
117
117
  });
118
118
  }
119
- const useFormAtom = (formId) => {
120
- const formAtom = (0, state_1.formRegistry)(formId);
121
- (0, react_2.useEffect)(() => () => state_1.formRegistry.remove(formId), [formId]);
122
- return formAtom;
123
- };
124
119
  /**
125
120
  * The primary form component of `remix-validated-form`.
126
121
  */
127
122
  function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues: unMemoizedDefaults, formRef: formRefProp, onReset, subaction, resetAfterSubmit = false, disableFocusOnError, method, replace, id, ...rest }) {
128
123
  var _a;
129
124
  const formId = useFormId(id);
130
- const formAtom = useFormAtom(formId);
131
125
  const providedDefaultValues = (0, util_1.useDeepEqualsMemo)(unMemoizedDefaults);
132
126
  const contextValue = (0, react_2.useMemo)(() => ({
133
127
  formId,
@@ -142,25 +136,30 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
142
136
  const formRef = (0, react_2.useRef)(null);
143
137
  const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : react_1.Form;
144
138
  const submit = (0, react_1.useSubmit)();
145
- const clearError = (0, hooks_2.useFormUpdateAtom)(state_1.clearErrorAtom);
146
- const addError = (0, hooks_2.useFormUpdateAtom)(state_1.addErrorAtom);
147
- const setFieldErrors = (0, hooks_2.useFormUpdateAtom)(state_1.setFieldErrorsAtom);
148
- const reset = (0, hooks_2.useFormUpdateAtom)(state_1.resetAtom);
149
- const startSubmit = (0, hooks_2.useFormUpdateAtom)(state_1.startSubmitAtom);
150
- const endSubmit = (0, hooks_2.useFormUpdateAtom)(state_1.endSubmitAtom);
151
- const syncFormContext = (0, hooks_2.useFormUpdateAtom)(state_1.syncFormContextAtom);
152
- const validateField = (0, react_2.useCallback)(async (fieldName) => {
139
+ const setFieldErrors = (0, hooks_2.useFormUpdateAtom)((0, state_1.fieldErrorsAtom)(formId));
140
+ const setFieldError = (0, hooks_2.useFormUpdateAtom)((0, state_1.setFieldErrorAtom)(formId));
141
+ const reset = (0, hooks_2.useFormUpdateAtom)((0, state_1.resetAtom)(formId));
142
+ const startSubmit = (0, hooks_2.useFormUpdateAtom)((0, state_1.startSubmitAtom)(formId));
143
+ const endSubmit = (0, hooks_2.useFormUpdateAtom)((0, state_1.endSubmitAtom)(formId));
144
+ const syncFormProps = (0, hooks_2.useFormUpdateAtom)((0, state_1.formPropsAtom)(formId));
145
+ const setHydrated = (0, hooks_2.useFormUpdateAtom)((0, state_1.isHydratedAtom)(formId));
146
+ const setFormElementInState = (0, hooks_2.useFormUpdateAtom)((0, state_1.formElementAtom)(formId));
147
+ (0, react_2.useEffect)(() => {
148
+ setHydrated(true);
149
+ return () => (0, state_1.cleanupFormState)(formId);
150
+ }, [formId, setHydrated]);
151
+ const validateField = (0, react_2.useCallback)(async (field) => {
153
152
  (0, tiny_invariant_1.default)(formRef.current, "Cannot find reference to form");
154
- const { error } = await validator.validateField(getDataFromForm(formRef.current), fieldName);
153
+ const { error } = await validator.validateField(getDataFromForm(formRef.current), field);
155
154
  if (error) {
156
- addError({ formAtom, name: fieldName, error });
155
+ setFieldError({ field, error });
157
156
  return error;
158
157
  }
159
158
  else {
160
- clearError({ name: fieldName, formAtom });
159
+ setFieldError({ field, error: undefined });
161
160
  return null;
162
161
  }
163
- }, [addError, clearError, formAtom, validator]);
162
+ }, [setFieldError, validator]);
164
163
  const customFocusHandlers = (0, MultiValueMap_1.useMultiValueMap)();
165
164
  const registerReceiveFocus = (0, react_2.useCallback)((fieldName, handler) => {
166
165
  customFocusHandlers().add(fieldName, handler);
@@ -169,33 +168,29 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
169
168
  };
170
169
  }, [customFocusHandlers]);
171
170
  (0, util_1.useIsomorphicLayoutEffect)(() => {
172
- syncFormContext({
173
- formAtom,
171
+ var _a;
172
+ syncFormProps({
174
173
  action,
175
- defaultValues: providedDefaultValues !== null && providedDefaultValues !== void 0 ? providedDefaultValues : backendDefaultValues,
174
+ defaultValues: (_a = providedDefaultValues !== null && providedDefaultValues !== void 0 ? providedDefaultValues : backendDefaultValues) !== null && _a !== void 0 ? _a : {},
176
175
  subaction,
177
176
  validateField,
178
177
  registerReceiveFocus,
179
178
  });
180
179
  }, [
181
180
  action,
182
- formAtom,
183
181
  providedDefaultValues,
184
182
  registerReceiveFocus,
185
183
  subaction,
186
- syncFormContext,
184
+ syncFormProps,
187
185
  validateField,
188
186
  backendDefaultValues,
189
187
  ]);
190
188
  (0, react_2.useEffect)(() => {
191
189
  var _a;
192
- setFieldErrors({
193
- fieldErrors: (_a = backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors) !== null && _a !== void 0 ? _a : {},
194
- formAtom,
195
- });
196
- }, [backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors, formAtom, setFieldErrors]);
190
+ setFieldErrors((_a = backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors) !== null && _a !== void 0 ? _a : {});
191
+ }, [backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors, setFieldErrors, setFieldError]);
197
192
  (0, submissionCallbacks_1.useSubmitComplete)(hasActiveSubmission, () => {
198
- endSubmit({ formAtom });
193
+ endSubmit();
199
194
  });
200
195
  let clickedButtonRef = react_2.default.useRef();
201
196
  (0, react_2.useEffect)(() => {
@@ -218,11 +213,11 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
218
213
  };
219
214
  }, []);
220
215
  const handleSubmit = async (e) => {
221
- startSubmit({ formAtom });
216
+ startSubmit();
222
217
  const result = await validator.validate(getDataFromForm(e.currentTarget));
223
218
  if (result.error) {
224
- endSubmit({ formAtom });
225
- setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
219
+ endSubmit();
220
+ setFieldErrors(result.error.fieldErrors);
226
221
  if (!disableFocusOnError) {
227
222
  focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
228
223
  }
@@ -231,7 +226,7 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
231
226
  const eventProxy = formEventProxy(e);
232
227
  await (onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, eventProxy));
233
228
  if (eventProxy.defaultPrevented) {
234
- endSubmit({ formAtom });
229
+ endSubmit();
235
230
  return;
236
231
  }
237
232
  // We deviate from the remix code here a bit because of our async submit.
@@ -249,14 +244,14 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
249
244
  clickedButtonRef.current = null;
250
245
  }
251
246
  };
252
- return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
247
+ return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp, setFormElementInState]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
253
248
  e.preventDefault();
254
249
  handleSubmit(e);
255
250
  }, onReset: (event) => {
256
251
  onReset === null || onReset === void 0 ? void 0 : onReset(event);
257
252
  if (event.defaultPrevented)
258
253
  return;
259
- reset({ formAtom });
254
+ reset();
260
255
  }, children: (0, jsx_runtime_1.jsxs)(formContext_1.InternalFormContext.Provider, { value: contextValue, children: [(0, jsx_runtime_1.jsx)(FormResetter, { formRef: formRef, resetAfterSubmit: resetAfterSubmit }, void 0), subaction && ((0, jsx_runtime_1.jsx)("input", { type: "hidden", value: subaction, name: "subaction" }, void 0)), id && (0, jsx_runtime_1.jsx)("input", { type: "hidden", value: id, name: constants_1.FORM_ID_FIELD }, void 0), children] }, void 0) }, void 0));
261
256
  }
262
257
  exports.ValidatedForm = ValidatedForm;
package/build/hooks.js CHANGED
@@ -14,7 +14,7 @@ const state_1 = require("./internal/state");
14
14
  */
15
15
  const useIsSubmitting = (formId) => {
16
16
  const formContext = (0, hooks_1.useInternalFormContext)(formId, "useIsSubmitting");
17
- return (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.isSubmittingAtom);
17
+ return (0, hooks_1.useFormAtomValue)((0, state_1.isSubmittingAtom)(formContext.formId));
18
18
  };
19
19
  exports.useIsSubmitting = useIsSubmitting;
20
20
  /**
@@ -24,23 +24,20 @@ exports.useIsSubmitting = useIsSubmitting;
24
24
  */
25
25
  const useIsValid = (formId) => {
26
26
  const formContext = (0, hooks_1.useInternalFormContext)(formId, "useIsValid");
27
- return (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.isValidAtom);
27
+ return (0, hooks_1.useFormAtomValue)((0, state_1.isValidAtom)(formContext.formId));
28
28
  };
29
29
  exports.useIsValid = useIsValid;
30
30
  /**
31
31
  * Provides the data and helpers necessary to set up a field.
32
32
  */
33
33
  const useField = (name, options) => {
34
- const { handleReceiveFocus, formId: providedFormId } = options !== null && options !== void 0 ? options : {};
34
+ const { formId: providedFormId, handleReceiveFocus } = options !== null && options !== void 0 ? options : {};
35
35
  const formContext = (0, hooks_1.useInternalFormContext)(providedFormId, "useField");
36
36
  const defaultValue = (0, hooks_1.useFieldDefaultValue)(name, formContext);
37
- const touched = (0, hooks_1.useFieldTouched)(name, formContext);
38
- const error = (0, hooks_1.useFieldError)(name, formContext);
39
- const clearError = (0, hooks_1.useClearError)(formContext);
40
- const setTouched = (0, hooks_1.useSetTouched)(formContext);
41
- const hasBeenSubmitted = (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.hasBeenSubmittedAtom);
42
- const validateField = (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.validateFieldAtom);
43
- const registerReceiveFocus = (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.registerReceiveFocusAtom);
37
+ const [touched, setTouched] = (0, hooks_1.useFieldTouched)(name, formContext);
38
+ const [error, setError] = (0, hooks_1.useFieldError)(name, formContext);
39
+ const hasBeenSubmitted = (0, hooks_1.useFormAtomValue)((0, state_1.hasBeenSubmittedAtom)(formContext.formId));
40
+ const { validateField, registerReceiveFocus } = (0, hooks_1.useFormAtomValue)((0, state_1.formPropsAtom)(formContext.formId));
44
41
  (0, react_1.useEffect)(() => {
45
42
  if (handleReceiveFocus)
46
43
  return registerReceiveFocus(name, handleReceiveFocus);
@@ -48,13 +45,13 @@ const useField = (name, options) => {
48
45
  const field = (0, react_1.useMemo)(() => {
49
46
  const helpers = {
50
47
  error,
51
- clearError: () => clearError(name),
48
+ clearError: () => setError(undefined),
52
49
  validate: () => {
53
50
  validateField(name);
54
51
  },
55
52
  defaultValue,
56
53
  touched,
57
- setTouched: (touched) => setTouched(name, touched),
54
+ setTouched,
58
55
  };
59
56
  const getInputProps = (0, getInputProps_1.createGetInputProps)({
60
57
  ...helpers,
@@ -70,12 +67,12 @@ const useField = (name, options) => {
70
67
  error,
71
68
  defaultValue,
72
69
  touched,
70
+ setTouched,
73
71
  name,
74
72
  hasBeenSubmitted,
75
73
  options === null || options === void 0 ? void 0 : options.validationBehavior,
76
- clearError,
74
+ setError,
77
75
  validateField,
78
- setTouched,
79
76
  ]);
80
77
  return field;
81
78
  };