remix-validated-form 4.3.0 → 4.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. package/.turbo/turbo-build.log +15 -9
  2. package/README.md +1 -0
  3. package/browser/ValidatedForm.js +16 -30
  4. package/browser/hooks.d.ts +1 -1
  5. package/browser/hooks.js +10 -9
  6. package/browser/internal/hooks.d.ts +20 -9
  7. package/browser/internal/hooks.js +32 -23
  8. package/browser/internal/logic/getRadioChecked.js +10 -0
  9. package/browser/internal/reset.d.ts +0 -0
  10. package/browser/internal/reset.js +0 -0
  11. package/browser/internal/state/cleanup.d.ts +2 -0
  12. package/browser/internal/state/cleanup.js +6 -0
  13. package/browser/internal/state/controlledFieldStore.d.ts +24 -0
  14. package/browser/internal/state/controlledFieldStore.js +57 -0
  15. package/browser/internal/state/controlledFields.d.ts +3 -116
  16. package/browser/internal/state/controlledFields.js +25 -68
  17. package/browser/internal/state/createFormStore.d.ts +40 -0
  18. package/browser/internal/state/createFormStore.js +83 -0
  19. package/browser/internal/state/storeFamily.d.ts +9 -0
  20. package/browser/internal/state/storeFamily.js +18 -0
  21. package/browser/internal/state/storeHooks.d.ts +5 -0
  22. package/browser/internal/state/storeHooks.js +10 -0
  23. package/browser/unreleased/formStateHooks.d.ts +15 -0
  24. package/browser/unreleased/formStateHooks.js +23 -14
  25. package/browser/userFacingFormContext.d.ts +8 -0
  26. package/browser/userFacingFormContext.js +5 -4
  27. package/dist/remix-validated-form.cjs.js +17 -0
  28. package/dist/remix-validated-form.es.js +2846 -0
  29. package/dist/remix-validated-form.umd.js +17 -0
  30. package/{build → dist/types}/ValidatedForm.d.ts +0 -0
  31. package/{build → dist/types}/hooks.d.ts +1 -1
  32. package/{build → dist/types}/index.d.ts +0 -0
  33. package/{build → dist/types}/internal/MultiValueMap.d.ts +0 -0
  34. package/{build → dist/types}/internal/constants.d.ts +0 -0
  35. package/{build → dist/types}/internal/flatten.d.ts +0 -0
  36. package/{build → dist/types}/internal/formContext.d.ts +0 -0
  37. package/{build → dist/types}/internal/getInputProps.d.ts +0 -0
  38. package/dist/types/internal/hooks.d.ts +32 -0
  39. package/{build → dist/types}/internal/hydratable.d.ts +0 -0
  40. package/{build → dist/types}/internal/logic/getCheckboxChecked.d.ts +0 -0
  41. package/{build → dist/types}/internal/logic/getRadioChecked.d.ts +0 -0
  42. package/dist/types/internal/state/cleanup.d.ts +2 -0
  43. package/dist/types/internal/state/controlledFieldStore.d.ts +24 -0
  44. package/dist/types/internal/state/controlledFields.d.ts +6 -0
  45. package/dist/types/internal/state/createFormStore.d.ts +40 -0
  46. package/dist/types/internal/state/storeFamily.d.ts +9 -0
  47. package/dist/types/internal/state/storeHooks.d.ts +5 -0
  48. package/{build → dist/types}/internal/submissionCallbacks.d.ts +0 -0
  49. package/{build → dist/types}/internal/util.d.ts +0 -0
  50. package/{build → dist/types}/server.d.ts +0 -0
  51. package/{build → dist/types}/unreleased/formStateHooks.d.ts +15 -0
  52. package/{build → dist/types}/userFacingFormContext.d.ts +15 -0
  53. package/{build → dist/types}/validation/createValidator.d.ts +0 -0
  54. package/{build → dist/types}/validation/types.d.ts +0 -0
  55. package/package.json +11 -9
  56. package/src/ValidatedForm.tsx +25 -47
  57. package/src/hooks.ts +15 -18
  58. package/src/internal/getInputProps.test.ts +251 -0
  59. package/src/internal/hooks.ts +69 -45
  60. package/src/internal/logic/getRadioChecked.ts +11 -0
  61. package/src/internal/state/cleanup.ts +8 -0
  62. package/src/internal/state/controlledFieldStore.ts +91 -0
  63. package/src/internal/state/controlledFields.ts +31 -123
  64. package/src/internal/state/createFormStore.ts +152 -0
  65. package/src/internal/state/storeFamily.ts +24 -0
  66. package/src/internal/state/storeHooks.ts +22 -0
  67. package/src/unreleased/formStateHooks.ts +50 -27
  68. package/src/userFacingFormContext.ts +26 -5
  69. package/src/validation/validation.test.ts +304 -0
  70. package/tsconfig.json +4 -1
  71. package/vite.config.ts +7 -0
  72. package/.turbo/turbo-test.log +0 -11
  73. package/browser/components.d.ts +0 -7
  74. package/browser/components.js +0 -10
  75. package/browser/internal/SingleTypeMultiValueMap.d.ts +0 -9
  76. package/browser/internal/SingleTypeMultiValueMap.js +0 -41
  77. package/browser/internal/customState.d.ts +0 -105
  78. package/browser/internal/customState.js +0 -46
  79. package/browser/internal/hooks-valtio.d.ts +0 -18
  80. package/browser/internal/hooks-valtio.js +0 -110
  81. package/browser/internal/hooks-zustand.d.ts +0 -16
  82. package/browser/internal/hooks-zustand.js +0 -100
  83. package/browser/internal/immerMiddleware.d.ts +0 -6
  84. package/browser/internal/immerMiddleware.js +0 -7
  85. package/browser/internal/logic/elementUtils.d.ts +0 -3
  86. package/browser/internal/logic/elementUtils.js +0 -3
  87. package/browser/internal/logic/getCheckboxChecked copy.d.ts +0 -1
  88. package/browser/internal/logic/getCheckboxChecked copy.js +0 -9
  89. package/browser/internal/logic/setFieldValue.d.ts +0 -1
  90. package/browser/internal/logic/setFieldValue.js +0 -40
  91. package/browser/internal/logic/setInputValueInForm.d.ts +0 -1
  92. package/browser/internal/logic/setInputValueInForm.js +0 -77
  93. package/browser/internal/setFieldValue.d.ts +0 -20
  94. package/browser/internal/setFieldValue.js +0 -83
  95. package/browser/internal/setFormValues.d.ts +0 -2
  96. package/browser/internal/setFormValues.js +0 -26
  97. package/browser/internal/state/setFieldValue.d.ts +0 -0
  98. package/browser/internal/state/setFieldValue.js +0 -1
  99. package/browser/internal/state-valtio.d.ts +0 -62
  100. package/browser/internal/state-valtio.js +0 -69
  101. package/browser/internal/state-zustand.d.ts +0 -47
  102. package/browser/internal/state-zustand.js +0 -85
  103. package/browser/internal/test.d.ts +0 -0
  104. package/browser/internal/test.js +0 -15
  105. package/browser/internal/useMultiValueMap.d.ts +0 -1
  106. package/browser/internal/useMultiValueMap.js +0 -11
  107. package/browser/internal/watch.d.ts +0 -18
  108. package/browser/internal/watch.js +0 -122
  109. package/browser/lowLevelHooks.d.ts +0 -0
  110. package/browser/lowLevelHooks.js +0 -1
  111. package/browser/test-data/testFormData.d.ts +0 -15
  112. package/browser/test-data/testFormData.js +0 -46
  113. package/browser/types.d.ts +0 -1
  114. package/browser/types.js +0 -1
  115. package/browser/validation/validation.test.d.ts +0 -1
  116. package/browser/validation/validation.test.js +0 -274
  117. package/browser/validation/withYup.d.ts +0 -6
  118. package/browser/validation/withYup.js +0 -40
  119. package/browser/validation/withZod.d.ts +0 -6
  120. package/browser/validation/withZod.js +0 -50
  121. package/build/ValidatedForm.js +0 -261
  122. package/build/hooks.js +0 -91
  123. package/build/index.js +0 -18
  124. package/build/internal/MultiValueMap.js +0 -48
  125. package/build/internal/SingleTypeMultiValueMap.d.ts +0 -8
  126. package/build/internal/SingleTypeMultiValueMap.js +0 -45
  127. package/build/internal/constants.js +0 -7
  128. package/build/internal/flatten.js +0 -14
  129. package/build/internal/formContext.js +0 -5
  130. package/build/internal/getInputProps.js +0 -58
  131. package/build/internal/hooks-valtio.d.ts +0 -18
  132. package/build/internal/hooks-valtio.js +0 -128
  133. package/build/internal/hooks-zustand.d.ts +0 -16
  134. package/build/internal/hooks-zustand.js +0 -117
  135. package/build/internal/hooks.d.ts +0 -21
  136. package/build/internal/hooks.js +0 -128
  137. package/build/internal/hydratable.js +0 -17
  138. package/build/internal/immerMiddleware.d.ts +0 -6
  139. package/build/internal/immerMiddleware.js +0 -14
  140. package/build/internal/logic/elementUtils.d.ts +0 -3
  141. package/build/internal/logic/elementUtils.js +0 -9
  142. package/build/internal/logic/getCheckboxChecked.js +0 -13
  143. package/build/internal/logic/getRadioChecked.js +0 -9
  144. package/build/internal/logic/setFieldValue.d.ts +0 -1
  145. package/build/internal/logic/setFieldValue.js +0 -47
  146. package/build/internal/logic/setInputValueInForm.d.ts +0 -1
  147. package/build/internal/logic/setInputValueInForm.js +0 -84
  148. package/build/internal/reset.d.ts +0 -28
  149. package/build/internal/reset.js +0 -19
  150. package/build/internal/setFormValues.d.ts +0 -2
  151. package/build/internal/setFormValues.js +0 -33
  152. package/build/internal/state/atomUtils.d.ts +0 -38
  153. package/build/internal/state/atomUtils.js +0 -13
  154. package/build/internal/state/controlledFields.d.ts +0 -119
  155. package/build/internal/state/controlledFields.js +0 -103
  156. package/build/internal/state-valtio.d.ts +0 -62
  157. package/build/internal/state-valtio.js +0 -83
  158. package/build/internal/state-zustand.d.ts +0 -47
  159. package/build/internal/state-zustand.js +0 -91
  160. package/build/internal/state.d.ts +0 -343
  161. package/build/internal/state.js +0 -71
  162. package/build/internal/submissionCallbacks.js +0 -17
  163. package/build/internal/test.d.ts +0 -1
  164. package/build/internal/test.js +0 -12
  165. package/build/internal/util.js +0 -41
  166. package/build/internal/watch.d.ts +0 -20
  167. package/build/internal/watch.js +0 -126
  168. package/build/server.js +0 -32
  169. package/build/types.d.ts +0 -1
  170. package/build/types.js +0 -2
  171. package/build/unreleased/formStateHooks.js +0 -59
  172. package/build/userFacingFormContext.js +0 -30
  173. package/build/validation/createValidator.js +0 -45
  174. package/build/validation/types.js +0 -2
  175. package/src/internal/reset.ts +0 -26
  176. package/src/internal/state/atomUtils.ts +0 -13
  177. package/src/internal/state.ts +0 -124
@@ -1,9 +1,15 @@
1
- $ npm run build:browser && npm run build:main
2
-
3
- > remix-validated-form@4.2.0 build:browser
4
- > tsc --module ESNext --outDir ./browser
5
-
6
-
7
- > remix-validated-form@4.2.0 build:main
8
- > tsc --module CommonJS --outDir ./build
9
-
1
+ $ vite build
2
+ vite v2.9.5 building for production...
3
+ transforming...
4
+ ✓ 321 modules transformed.
5
+ rendering chunks...
6
+ dist/remix-validated-form.cjs.js  43.85 KiB / gzip: 16.69 KiB
7
+ dist/remix-validated-form.es.js  98.54 KiB / gzip: 23.34 KiB
8
+ dist/remix-validated-form.umd.js  44.10 KiB / gzip: 16.81 KiB
9
+ 
10
+ [vite:dts] Start generate declaration files...
11
+ [vite:dts] Declaration files built in 1824ms.
12
+ 
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
@@ -24,6 +24,7 @@ To run `sample-app`:
24
24
  git clone https://github.com/airjp73/remix-validated-form
25
25
  cd ./remix-validated-form
26
26
  yarn install
27
+ yarn build
27
28
  yarn sample-app
28
29
  ```
29
30
 
@@ -2,15 +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, useFormUpdateAtom, useHasActiveFormSubmit, } from "./internal/hooks";
8
+ import { useDefaultValuesFromLoader, useErrorResponseForForm, useHasActiveFormSubmit, useSetFieldErrors, } from "./internal/hooks";
10
9
  import { useMultiValueMap } from "./internal/MultiValueMap";
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";
10
+ import { cleanupFormState } from "./internal/state/cleanup";
11
+ import { useControlledFieldStore, useFormStore, } from "./internal/state/storeHooks";
14
12
  import { useSubmitComplete } from "./internal/submissionCallbacks";
15
13
  import { mergeRefs, useDeepEqualsMemo, useIsomorphicLayoutEffect as useLayoutEffect, } from "./internal/util";
16
14
  const getDataFromForm = (el) => new FormData(el);
@@ -113,32 +111,19 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
113
111
  const formRef = useRef(null);
114
112
  const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : RemixForm;
115
113
  const submit = useSubmit();
116
- const setFieldErrors = useFormUpdateAtom(fieldErrorsAtom(formId));
117
- const setFieldError = useFormUpdateAtom(setFieldErrorAtom(formId));
118
- const reset = useFormUpdateAtom(resetAtom(formId));
119
- const startSubmit = useFormUpdateAtom(startSubmitAtom(formId));
120
- const endSubmit = useFormUpdateAtom(endSubmitAtom(formId));
121
- const syncFormProps = useFormUpdateAtom(formPropsAtom(formId));
122
- const setHydrated = useFormUpdateAtom(isHydratedAtom(formId));
123
- const setFormElementInState = useFormUpdateAtom(formElementAtom(formId));
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);
124
123
  useEffect(() => {
125
- setHydrated(true);
124
+ setHydrated();
126
125
  return () => cleanupFormState(formId);
127
126
  }, [formId, setHydrated]);
128
- const awaitValue = useAwaitValue(formId);
129
- const validateField = useCallback(async (field) => {
130
- invariant(formRef.current, "Cannot find reference to form");
131
- await awaitValue(field);
132
- const { error } = await validator.validateField(getDataFromForm(formRef.current), field);
133
- if (error) {
134
- setFieldError({ field, error });
135
- return error;
136
- }
137
- else {
138
- setFieldError({ field, error: undefined });
139
- return null;
140
- }
141
- }, [awaitValue, setFieldError, validator]);
142
127
  const customFocusHandlers = useMultiValueMap();
143
128
  const registerReceiveFocus = useCallback((fieldName, handler) => {
144
129
  customFocusHandlers().add(fieldName, handler);
@@ -152,8 +137,8 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
152
137
  action,
153
138
  defaultValues: (_a = providedDefaultValues !== null && providedDefaultValues !== void 0 ? providedDefaultValues : backendDefaultValues) !== null && _a !== void 0 ? _a : {},
154
139
  subaction,
155
- validateField,
156
140
  registerReceiveFocus,
141
+ validator,
157
142
  });
158
143
  }, [
159
144
  action,
@@ -161,8 +146,8 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
161
146
  registerReceiveFocus,
162
147
  subaction,
163
148
  syncFormProps,
164
- validateField,
165
149
  backendDefaultValues,
150
+ validator,
166
151
  ]);
167
152
  useEffect(() => {
168
153
  var _a;
@@ -231,5 +216,6 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
231
216
  if (event.defaultPrevented)
232
217
  return;
233
218
  reset();
219
+ resetControlledFields();
234
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));
235
221
  }
@@ -64,4 +64,4 @@ export declare const useField: (name: string, options?: {
64
64
  formId?: string | undefined;
65
65
  } | undefined) => FieldProps;
66
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;
67
+ export declare const useUpdateControlledField: (formId?: string | undefined) => (fieldName: string, value: unknown) => void;
package/browser/hooks.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { useEffect, useMemo } from "react";
2
2
  import { createGetInputProps, } from "./internal/getInputProps";
3
- import { useInternalFormContext, useFieldTouched, useFieldError, useFormAtomValue, useFieldDefaultValue, } from "./internal/hooks";
4
- import { formPropsAtom, hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, } from "./internal/state";
3
+ import { useInternalFormContext, useFieldTouched, useFieldError, useFieldDefaultValue, useClearError, useInternalIsSubmitting, useInternalIsValid, useInternalHasBeenSubmitted, useValidateField, useRegisterReceiveFocus, } from "./internal/hooks";
5
4
  import { useControllableValue, useUpdateControllableValue, } from "./internal/state/controlledFields";
6
5
  /**
7
6
  * Returns whether or not the parent form is currently being submitted.
@@ -12,7 +11,7 @@ import { useControllableValue, useUpdateControllableValue, } from "./internal/st
12
11
  */
13
12
  export const useIsSubmitting = (formId) => {
14
13
  const formContext = useInternalFormContext(formId, "useIsSubmitting");
15
- return useFormAtomValue(isSubmittingAtom(formContext.formId));
14
+ return useInternalIsSubmitting(formContext.formId);
16
15
  };
17
16
  /**
18
17
  * Returns whether or not the current form is valid.
@@ -21,7 +20,7 @@ export const useIsSubmitting = (formId) => {
21
20
  */
22
21
  export const useIsValid = (formId) => {
23
22
  const formContext = useInternalFormContext(formId, "useIsValid");
24
- return useFormAtomValue(isValidAtom(formContext.formId));
23
+ return useInternalIsValid(formContext.formId);
25
24
  };
26
25
  /**
27
26
  * Provides the data and helpers necessary to set up a field.
@@ -31,9 +30,11 @@ export const useField = (name, options) => {
31
30
  const formContext = useInternalFormContext(providedFormId, "useField");
32
31
  const defaultValue = useFieldDefaultValue(name, formContext);
33
32
  const [touched, setTouched] = useFieldTouched(name, formContext);
34
- const [error, setError] = useFieldError(name, formContext);
35
- const hasBeenSubmitted = useFormAtomValue(hasBeenSubmittedAtom(formContext.formId));
36
- const { validateField, registerReceiveFocus } = useFormAtomValue(formPropsAtom(formContext.formId));
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);
37
38
  useEffect(() => {
38
39
  if (handleReceiveFocus)
39
40
  return registerReceiveFocus(name, handleReceiveFocus);
@@ -41,7 +42,7 @@ export const useField = (name, options) => {
41
42
  const field = useMemo(() => {
42
43
  const helpers = {
43
44
  error,
44
- clearError: () => setError(undefined),
45
+ clearError: () => clearError(name),
45
46
  validate: () => {
46
47
  validateField(name);
47
48
  },
@@ -61,13 +62,13 @@ export const useField = (name, options) => {
61
62
  };
62
63
  }, [
63
64
  error,
65
+ clearError,
64
66
  defaultValue,
65
67
  touched,
66
68
  setTouched,
67
69
  name,
68
70
  hasBeenSubmitted,
69
71
  options === null || options === void 0 ? void 0 : options.validationBehavior,
70
- setError,
71
72
  validateField,
72
73
  ]);
73
74
  return field;
@@ -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
- export declare const useFormUpdateAtom: typeof useUpdateAtom;
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, (update: boolean) => void];
18
- export declare const useFieldError: (name: string, context: InternalFormContextValue) => readonly [string | undefined, (update?: string | undefined) => void];
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 useClearError: ({ formId }: InternalFormContextValue) => (name: string) => void;
21
- export declare const useSetTouched: ({ formId }: InternalFormContextValue) => (name: string, touched: boolean) => void;
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 { ATOM_SCOPE, fieldErrorAtom, fieldTouchedAtom, formPropsAtom, isHydratedAtom, setFieldErrorAtom, setTouchedAtom, } from "./state";
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 = useFormAtomValue(isHydratedAtom(context.formId));
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 = useFormAtomValue(isHydratedAtom(formId));
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 }) => useFormAtom(fieldTouchedAtom({ formId, field }));
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 [state, set] = useFormAtom(fieldErrorAtom({ formId: context.formId, field: name }));
89
- return [
90
- fieldErrors.map((fieldErrors) => fieldErrors === null || fieldErrors === void 0 ? void 0 : fieldErrors[name]).hydrateTo(state),
91
- set,
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 { defaultValues: state } = useFormAtomValue(formPropsAtom(context.formId));
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 useClearError = ({ formId }) => {
102
- const updateError = useFormUpdateAtom(setFieldErrorAtom(formId));
103
- return useCallback((name) => updateError({ field: name, error: undefined }), [updateError]);
104
- };
105
- export const useSetTouched = ({ formId }) => {
106
- const setTouched = useFormUpdateAtom(setTouchedAtom(formId));
107
- return useCallback((name, touched) => setTouched({ field: name, touched }), [setTouched]);
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
+ }
File without changes
File without changes
@@ -0,0 +1,2 @@
1
+ import { InternalFormId } from "./storeFamily";
2
+ export declare const cleanupFormState: (formId: InternalFormId) => void;
@@ -0,0 +1,6 @@
1
+ import { controlledFieldStore } from "./controlledFieldStore";
2
+ import { formStore } from "./createFormStore";
3
+ export const cleanupFormState = (formId) => {
4
+ formStore.remove(formId);
5
+ controlledFieldStore.remove(formId);
6
+ };
@@ -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,119 +1,6 @@
1
- import { PrimitiveAtom } from "jotai";
2
1
  import { InternalFormContextValue } from "../formContext";
3
- import { FieldAtomKey, InternalFormId } from "./atomUtils";
4
- export declare const controlledFieldsAtom: {
5
- (param: InternalFormId): import("jotai").Atom<Record<string, PrimitiveAtom<unknown>>> & {
6
- write: (get: {
7
- <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
8
- <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
9
- <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
10
- } & {
11
- <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
12
- unstable_promise: true;
13
- }): Value_3 | Promise<Value_3>;
14
- <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
15
- unstable_promise: true;
16
- }): Value_4 | Promise<Value_4>;
17
- <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
18
- unstable_promise: true;
19
- }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
20
- }, set: {
21
- <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
22
- <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
23
- }, update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void;
24
- onMount?: (<S extends (update: Record<string, PrimitiveAtom<unknown>> | ((prev: Record<string, PrimitiveAtom<unknown>>) => Record<string, PrimitiveAtom<unknown>>)) => void>(setAtom: S) => void | (() => void)) | undefined;
25
- } & {
26
- init: Record<string, PrimitiveAtom<unknown>>;
27
- };
28
- remove(param: InternalFormId): void;
29
- setShouldRemove(shouldRemove: ((createdAt: number, param: InternalFormId) => boolean) | null): void;
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
- };
85
- export declare const setControlledFieldValueAtom: import("jotai").Atom<null> & {
86
- write: (get: {
87
- <Value>(atom: import("jotai").Atom<Value | Promise<Value>>): Value;
88
- <Value_1>(atom: import("jotai").Atom<Promise<Value_1>>): Value_1;
89
- <Value_2>(atom: import("jotai").Atom<Value_2>): Value_2 extends Promise<infer V> ? V : Value_2;
90
- } & {
91
- <Value_3>(atom: import("jotai").Atom<Value_3 | Promise<Value_3>>, options: {
92
- unstable_promise: true;
93
- }): Value_3 | Promise<Value_3>;
94
- <Value_4>(atom: import("jotai").Atom<Promise<Value_4>>, options: {
95
- unstable_promise: true;
96
- }): Value_4 | Promise<Value_4>;
97
- <Value_5>(atom: import("jotai").Atom<Value_5>, options: {
98
- unstable_promise: true;
99
- }): (Value_5 extends Promise<infer V> ? V : Value_5) | Promise<Value_5 extends Promise<infer V> ? V : Value_5>;
100
- }, set: {
101
- <Value_6, Result extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_6, undefined, Result>): Result;
102
- <Value_7, Update, Result_1 extends void | Promise<void>>(atom: import("jotai").WritableAtom<Value_7, Update, Result_1>, update: Update): Result_1;
103
- }, update: {
104
- formId: InternalFormId;
105
- field: string;
106
- value: unknown;
107
- }) => void;
108
- onMount?: (<S extends (update: {
109
- formId: InternalFormId;
110
- field: string;
111
- value: unknown;
112
- }) => void>(setAtom: S) => void | (() => void)) | undefined;
113
- } & {
114
- init: null;
115
- };
2
+ import { InternalFormId } from "./storeFamily";
116
3
  export declare const useControlledFieldValue: (context: InternalFormContextValue, field: string) => any;
117
4
  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>;
5
+ export declare const useUpdateControllableValue: (formId: InternalFormId) => (fieldName: string, value: unknown) => void;
6
+ export declare const useAwaitValue: (formId: InternalFormId) => (fieldName: string) => Promise<void>;