remix-validated-form 4.1.9 → 4.3.1-beta.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.
Files changed (157) hide show
  1. package/.turbo/turbo-build.log +15 -9
  2. package/README.md +8 -5
  3. package/browser/ValidatedForm.js +6 -2
  4. package/browser/hooks.d.ts +2 -0
  5. package/browser/hooks.js +10 -0
  6. package/browser/internal/MultiValueMap.d.ts +2 -0
  7. package/browser/internal/MultiValueMap.js +4 -0
  8. package/browser/internal/getInputProps.js +2 -1
  9. package/browser/internal/logic/getRadioChecked.js +10 -0
  10. package/browser/internal/reset.d.ts +28 -0
  11. package/browser/internal/reset.js +13 -0
  12. package/browser/internal/state/controlledFields.d.ts +62 -5
  13. package/browser/internal/state/controlledFields.js +37 -21
  14. package/browser/internal/state.d.ts +0 -27
  15. package/browser/internal/state.js +0 -5
  16. package/browser/server.d.ts +1 -1
  17. package/browser/server.js +2 -2
  18. package/browser/unreleased/formStateHooks.d.ts +0 -0
  19. package/browser/unreleased/formStateHooks.js +0 -0
  20. package/dist/remix-validated-form.cjs.js +1 -0
  21. package/dist/remix-validated-form.es.js +3535 -0
  22. package/dist/remix-validated-form.umd.js +1 -0
  23. package/{build → dist/types}/ValidatedForm.d.ts +0 -0
  24. package/{build → dist/types}/hooks.d.ts +2 -0
  25. package/{build → dist/types}/index.d.ts +0 -0
  26. package/{build → dist/types}/internal/MultiValueMap.d.ts +2 -0
  27. package/{build → dist/types}/internal/constants.d.ts +0 -0
  28. package/{build → dist/types}/internal/flatten.d.ts +0 -0
  29. package/{build → dist/types}/internal/formContext.d.ts +0 -0
  30. package/{build → dist/types}/internal/getInputProps.d.ts +0 -0
  31. package/{build → dist/types}/internal/hooks.d.ts +0 -0
  32. package/{build → dist/types}/internal/hydratable.d.ts +0 -0
  33. package/{build → dist/types}/internal/logic/getCheckboxChecked.d.ts +0 -0
  34. package/{build → dist/types}/internal/logic/getRadioChecked.d.ts +0 -0
  35. package/dist/types/internal/reset.d.ts +28 -0
  36. package/{build → dist/types}/internal/state/atomUtils.d.ts +0 -0
  37. package/dist/types/internal/state/controlledFields.d.ts +119 -0
  38. package/{build → dist/types}/internal/state.d.ts +0 -27
  39. package/{build → dist/types}/internal/submissionCallbacks.d.ts +0 -0
  40. package/{build → dist/types}/internal/util.d.ts +0 -0
  41. package/{build → dist/types}/server.d.ts +1 -1
  42. package/{build → dist/types}/unreleased/formStateHooks.d.ts +0 -0
  43. package/{build → dist/types}/userFacingFormContext.d.ts +0 -0
  44. package/{build → dist/types}/validation/createValidator.d.ts +0 -0
  45. package/{build → dist/types}/validation/types.d.ts +0 -0
  46. package/package.json +8 -7
  47. package/src/ValidatedForm.tsx +6 -2
  48. package/src/hooks.ts +15 -0
  49. package/src/internal/MultiValueMap.ts +6 -0
  50. package/src/internal/getInputProps.test.ts +251 -0
  51. package/src/internal/getInputProps.ts +2 -1
  52. package/src/internal/logic/getRadioChecked.ts +11 -0
  53. package/src/internal/reset.ts +26 -0
  54. package/src/internal/state/controlledFields.ts +170 -0
  55. package/src/internal/state.ts +0 -8
  56. package/src/server.ts +3 -2
  57. package/src/validation/validation.test.ts +304 -0
  58. package/tsconfig.json +4 -1
  59. package/vite.config.ts +7 -0
  60. package/.turbo/turbo-test.log +0 -11
  61. package/browser/components.d.ts +0 -7
  62. package/browser/components.js +0 -10
  63. package/browser/internal/SingleTypeMultiValueMap.d.ts +0 -9
  64. package/browser/internal/SingleTypeMultiValueMap.js +0 -41
  65. package/browser/internal/customState.d.ts +0 -105
  66. package/browser/internal/customState.js +0 -46
  67. package/browser/internal/hooks-valtio.d.ts +0 -18
  68. package/browser/internal/hooks-valtio.js +0 -110
  69. package/browser/internal/hooks-zustand.d.ts +0 -16
  70. package/browser/internal/hooks-zustand.js +0 -100
  71. package/browser/internal/immerMiddleware.d.ts +0 -6
  72. package/browser/internal/immerMiddleware.js +0 -7
  73. package/browser/internal/logic/elementUtils.d.ts +0 -3
  74. package/browser/internal/logic/elementUtils.js +0 -3
  75. package/browser/internal/logic/getCheckboxChecked copy.d.ts +0 -1
  76. package/browser/internal/logic/getCheckboxChecked copy.js +0 -9
  77. package/browser/internal/logic/setFieldValue.d.ts +0 -1
  78. package/browser/internal/logic/setFieldValue.js +0 -40
  79. package/browser/internal/logic/setInputValueInForm.d.ts +0 -1
  80. package/browser/internal/logic/setInputValueInForm.js +0 -77
  81. package/browser/internal/setFieldValue.d.ts +0 -20
  82. package/browser/internal/setFieldValue.js +0 -83
  83. package/browser/internal/setFormValues.d.ts +0 -2
  84. package/browser/internal/setFormValues.js +0 -26
  85. package/browser/internal/state/setFieldValue.d.ts +0 -0
  86. package/browser/internal/state/setFieldValue.js +0 -1
  87. package/browser/internal/state-valtio.d.ts +0 -62
  88. package/browser/internal/state-valtio.js +0 -69
  89. package/browser/internal/state-zustand.d.ts +0 -47
  90. package/browser/internal/state-zustand.js +0 -85
  91. package/browser/internal/test.d.ts +0 -0
  92. package/browser/internal/test.js +0 -15
  93. package/browser/internal/useMultiValueMap.d.ts +0 -1
  94. package/browser/internal/useMultiValueMap.js +0 -11
  95. package/browser/internal/watch.d.ts +0 -18
  96. package/browser/internal/watch.js +0 -122
  97. package/browser/lowLevelHooks.d.ts +0 -0
  98. package/browser/lowLevelHooks.js +0 -1
  99. package/browser/test-data/testFormData.d.ts +0 -15
  100. package/browser/test-data/testFormData.js +0 -46
  101. package/browser/types.d.ts +0 -1
  102. package/browser/types.js +0 -1
  103. package/browser/validation/validation.test.d.ts +0 -1
  104. package/browser/validation/validation.test.js +0 -274
  105. package/browser/validation/withYup.d.ts +0 -6
  106. package/browser/validation/withYup.js +0 -40
  107. package/browser/validation/withZod.d.ts +0 -6
  108. package/browser/validation/withZod.js +0 -50
  109. package/build/ValidatedForm.js +0 -257
  110. package/build/hooks.js +0 -79
  111. package/build/index.js +0 -18
  112. package/build/internal/MultiValueMap.js +0 -44
  113. package/build/internal/SingleTypeMultiValueMap.d.ts +0 -8
  114. package/build/internal/SingleTypeMultiValueMap.js +0 -45
  115. package/build/internal/constants.js +0 -7
  116. package/build/internal/flatten.js +0 -14
  117. package/build/internal/formContext.js +0 -5
  118. package/build/internal/getInputProps.js +0 -57
  119. package/build/internal/hooks-valtio.d.ts +0 -18
  120. package/build/internal/hooks-valtio.js +0 -128
  121. package/build/internal/hooks-zustand.d.ts +0 -16
  122. package/build/internal/hooks-zustand.js +0 -117
  123. package/build/internal/hooks.js +0 -128
  124. package/build/internal/hydratable.js +0 -17
  125. package/build/internal/immerMiddleware.d.ts +0 -6
  126. package/build/internal/immerMiddleware.js +0 -14
  127. package/build/internal/logic/elementUtils.d.ts +0 -3
  128. package/build/internal/logic/elementUtils.js +0 -9
  129. package/build/internal/logic/getCheckboxChecked.js +0 -13
  130. package/build/internal/logic/getRadioChecked.js +0 -9
  131. package/build/internal/logic/setFieldValue.d.ts +0 -1
  132. package/build/internal/logic/setFieldValue.js +0 -47
  133. package/build/internal/logic/setInputValueInForm.d.ts +0 -1
  134. package/build/internal/logic/setInputValueInForm.js +0 -84
  135. package/build/internal/setFormValues.d.ts +0 -2
  136. package/build/internal/setFormValues.js +0 -33
  137. package/build/internal/state/atomUtils.js +0 -13
  138. package/build/internal/state/controlledFields.d.ts +0 -62
  139. package/build/internal/state/controlledFields.js +0 -85
  140. package/build/internal/state-valtio.d.ts +0 -62
  141. package/build/internal/state-valtio.js +0 -83
  142. package/build/internal/state-zustand.d.ts +0 -47
  143. package/build/internal/state-zustand.js +0 -91
  144. package/build/internal/state.js +0 -76
  145. package/build/internal/submissionCallbacks.js +0 -17
  146. package/build/internal/test.d.ts +0 -1
  147. package/build/internal/test.js +0 -12
  148. package/build/internal/util.js +0 -41
  149. package/build/internal/watch.d.ts +0 -20
  150. package/build/internal/watch.js +0 -126
  151. package/build/server.js +0 -32
  152. package/build/types.d.ts +0 -1
  153. package/build/types.js +0 -2
  154. package/build/unreleased/formStateHooks.js +0 -59
  155. package/build/userFacingFormContext.js +0 -30
  156. package/build/validation/createValidator.js +0 -45
  157. package/build/validation/types.js +0 -2
@@ -0,0 +1,251 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import {
3
+ createGetInputProps,
4
+ CreateGetInputPropsOptions,
5
+ } from "./getInputProps";
6
+
7
+ const fakeEvent = { fake: "event" } as any;
8
+
9
+ describe("getInputProps", () => {
10
+ describe("initial", () => {
11
+ it("should validate on blur by default", () => {
12
+ const options: CreateGetInputPropsOptions = {
13
+ name: "some-field",
14
+ defaultValue: "test default value",
15
+ touched: false,
16
+ hasBeenSubmitted: false,
17
+ setTouched: vi.fn(),
18
+ clearError: vi.fn(),
19
+ validate: vi.fn(),
20
+ };
21
+ const getInputProps = createGetInputProps(options);
22
+
23
+ const provided = {
24
+ onBlur: vi.fn(),
25
+ onChange: vi.fn(),
26
+ };
27
+ const { onChange, onBlur } = getInputProps(provided);
28
+
29
+ onChange!(fakeEvent);
30
+ expect(provided.onChange).toBeCalledTimes(1);
31
+ expect(provided.onChange).toBeCalledWith(fakeEvent);
32
+ expect(options.setTouched).not.toBeCalled();
33
+ expect(options.validate).not.toBeCalled();
34
+
35
+ onBlur!(fakeEvent);
36
+ expect(provided.onBlur).toBeCalledTimes(1);
37
+ expect(provided.onBlur).toBeCalledWith(fakeEvent);
38
+ expect(options.setTouched).toBeCalledTimes(1);
39
+ expect(options.setTouched).toBeCalledWith(true);
40
+ expect(options.validate).toBeCalledTimes(1);
41
+ });
42
+
43
+ it("should respect provided validation behavior", () => {
44
+ const options: CreateGetInputPropsOptions = {
45
+ name: "some-field",
46
+ defaultValue: "test default value",
47
+ touched: false,
48
+ hasBeenSubmitted: false,
49
+ setTouched: vi.fn(),
50
+ clearError: vi.fn(),
51
+ validate: vi.fn(),
52
+ validationBehavior: {
53
+ initial: "onChange",
54
+ },
55
+ };
56
+ const getInputProps = createGetInputProps(options);
57
+
58
+ const provided = {
59
+ onBlur: vi.fn(),
60
+ onChange: vi.fn(),
61
+ };
62
+ const { onChange, onBlur } = getInputProps(provided);
63
+
64
+ onChange!(fakeEvent);
65
+ expect(provided.onChange).toBeCalledTimes(1);
66
+ expect(provided.onChange).toBeCalledWith(fakeEvent);
67
+ expect(options.setTouched).not.toBeCalled();
68
+ expect(options.validate).toBeCalledTimes(1);
69
+
70
+ onBlur!(fakeEvent);
71
+ expect(provided.onBlur).toBeCalledTimes(1);
72
+ expect(provided.onBlur).toBeCalledWith(fakeEvent);
73
+ expect(options.setTouched).toBeCalledTimes(1);
74
+ expect(options.setTouched).toBeCalledWith(true);
75
+ expect(options.validate).toBeCalledTimes(1);
76
+ });
77
+
78
+ it("should not validate when behavior is onSubmit", () => {
79
+ const options: CreateGetInputPropsOptions = {
80
+ name: "some-field",
81
+ defaultValue: "test default value",
82
+ touched: false,
83
+ hasBeenSubmitted: false,
84
+ setTouched: vi.fn(),
85
+ clearError: vi.fn(),
86
+ validate: vi.fn(),
87
+ validationBehavior: {
88
+ initial: "onSubmit",
89
+ },
90
+ };
91
+ const getInputProps = createGetInputProps(options);
92
+
93
+ const provided = {
94
+ onBlur: vi.fn(),
95
+ onChange: vi.fn(),
96
+ };
97
+ const { onChange, onBlur } = getInputProps(provided);
98
+
99
+ onChange!(fakeEvent);
100
+ expect(provided.onChange).toBeCalledTimes(1);
101
+ expect(provided.onChange).toBeCalledWith(fakeEvent);
102
+ expect(options.setTouched).not.toBeCalled();
103
+ expect(options.validate).not.toBeCalled();
104
+
105
+ onBlur!(fakeEvent);
106
+ expect(provided.onBlur).toBeCalledTimes(1);
107
+ expect(provided.onBlur).toBeCalledWith(fakeEvent);
108
+ expect(options.setTouched).toBeCalledTimes(1);
109
+ expect(options.setTouched).toBeCalledWith(true);
110
+ expect(options.validate).not.toBeCalled();
111
+ });
112
+ });
113
+
114
+ describe("whenTouched", () => {
115
+ it("should validate on change by default", () => {
116
+ const options: CreateGetInputPropsOptions = {
117
+ name: "some-field",
118
+ defaultValue: "test default value",
119
+ touched: true,
120
+ hasBeenSubmitted: false,
121
+ setTouched: vi.fn(),
122
+ clearError: vi.fn(),
123
+ validate: vi.fn(),
124
+ };
125
+ const getInputProps = createGetInputProps(options);
126
+
127
+ const provided = {
128
+ onBlur: vi.fn(),
129
+ onChange: vi.fn(),
130
+ };
131
+ const { onChange, onBlur } = getInputProps(provided);
132
+
133
+ onChange!(fakeEvent);
134
+ expect(provided.onChange).toBeCalledTimes(1);
135
+ expect(provided.onChange).toBeCalledWith(fakeEvent);
136
+ expect(options.setTouched).not.toBeCalled();
137
+ expect(options.validate).toBeCalledTimes(1);
138
+
139
+ onBlur!(fakeEvent);
140
+ expect(provided.onBlur).toBeCalledTimes(1);
141
+ expect(provided.onBlur).toBeCalledWith(fakeEvent);
142
+ expect(options.setTouched).toBeCalledTimes(1);
143
+ expect(options.setTouched).toBeCalledWith(true);
144
+ expect(options.validate).toBeCalledTimes(1);
145
+ });
146
+
147
+ it("should respect provided validation behavior", () => {
148
+ const options: CreateGetInputPropsOptions = {
149
+ name: "some-field",
150
+ defaultValue: "test default value",
151
+ touched: true,
152
+ hasBeenSubmitted: false,
153
+ setTouched: vi.fn(),
154
+ clearError: vi.fn(),
155
+ validate: vi.fn(),
156
+ validationBehavior: {
157
+ whenTouched: "onBlur",
158
+ },
159
+ };
160
+ const getInputProps = createGetInputProps(options);
161
+
162
+ const provided = {
163
+ onBlur: vi.fn(),
164
+ onChange: vi.fn(),
165
+ };
166
+ const { onChange, onBlur } = getInputProps(provided);
167
+
168
+ onChange!(fakeEvent);
169
+ expect(provided.onChange).toBeCalledTimes(1);
170
+ expect(provided.onChange).toBeCalledWith(fakeEvent);
171
+ expect(options.setTouched).not.toBeCalled();
172
+ expect(options.validate).not.toBeCalled();
173
+
174
+ onBlur!(fakeEvent);
175
+ expect(provided.onBlur).toBeCalledTimes(1);
176
+ expect(provided.onBlur).toBeCalledWith(fakeEvent);
177
+ expect(options.setTouched).toBeCalledTimes(1);
178
+ expect(options.setTouched).toBeCalledWith(true);
179
+ expect(options.validate).toBeCalledTimes(1);
180
+ });
181
+ });
182
+
183
+ describe("whenSubmitted", () => {
184
+ it("should validate on change by default", () => {
185
+ const options: CreateGetInputPropsOptions = {
186
+ name: "some-field",
187
+ defaultValue: "test default value",
188
+ touched: true,
189
+ hasBeenSubmitted: true,
190
+ setTouched: vi.fn(),
191
+ clearError: vi.fn(),
192
+ validate: vi.fn(),
193
+ };
194
+ const getInputProps = createGetInputProps(options);
195
+
196
+ const provided = {
197
+ onBlur: vi.fn(),
198
+ onChange: vi.fn(),
199
+ };
200
+ const { onChange, onBlur } = getInputProps(provided);
201
+
202
+ onChange!(fakeEvent);
203
+ expect(provided.onChange).toBeCalledTimes(1);
204
+ expect(provided.onChange).toBeCalledWith(fakeEvent);
205
+ expect(options.setTouched).not.toBeCalled();
206
+ expect(options.validate).toBeCalledTimes(1);
207
+
208
+ onBlur!(fakeEvent);
209
+ expect(provided.onBlur).toBeCalledTimes(1);
210
+ expect(provided.onBlur).toBeCalledWith(fakeEvent);
211
+ expect(options.setTouched).toBeCalledTimes(1);
212
+ expect(options.setTouched).toBeCalledWith(true);
213
+ expect(options.validate).toBeCalledTimes(1);
214
+ });
215
+
216
+ it("should respect provided validation behavior", () => {
217
+ const options: CreateGetInputPropsOptions = {
218
+ name: "some-field",
219
+ defaultValue: "test default value",
220
+ touched: true,
221
+ hasBeenSubmitted: true,
222
+ setTouched: vi.fn(),
223
+ clearError: vi.fn(),
224
+ validate: vi.fn(),
225
+ validationBehavior: {
226
+ whenSubmitted: "onBlur",
227
+ },
228
+ };
229
+ const getInputProps = createGetInputProps(options);
230
+
231
+ const provided = {
232
+ onBlur: vi.fn(),
233
+ onChange: vi.fn(),
234
+ };
235
+ const { onChange, onBlur } = getInputProps(provided);
236
+
237
+ onChange!(fakeEvent);
238
+ expect(provided.onChange).toBeCalledTimes(1);
239
+ expect(provided.onChange).toBeCalledWith(fakeEvent);
240
+ expect(options.setTouched).not.toBeCalled();
241
+ expect(options.validate).not.toBeCalled();
242
+
243
+ onBlur!(fakeEvent);
244
+ expect(provided.onBlur).toBeCalledTimes(1);
245
+ expect(provided.onBlur).toBeCalledWith(fakeEvent);
246
+ expect(options.setTouched).toBeCalledTimes(1);
247
+ expect(options.setTouched).toBeCalledWith(true);
248
+ expect(options.validate).toBeCalledTimes(1);
249
+ });
250
+ });
251
+ });
@@ -84,7 +84,8 @@ export const createGetInputProps = ({
84
84
  inputProps.defaultChecked = getCheckboxChecked(props.value, defaultValue);
85
85
  } else if (props.type === "radio") {
86
86
  inputProps.defaultChecked = getRadioChecked(props.value, defaultValue);
87
- } else {
87
+ } else if (props.value === undefined) {
88
+ // We should only set the defaultValue if the input is uncontrolled.
88
89
  inputProps.defaultValue = defaultValue;
89
90
  }
90
91
 
@@ -5,3 +5,14 @@ export const getRadioChecked = (
5
5
  if (typeof newValue === "string") return newValue === radioValue;
6
6
  return undefined;
7
7
  };
8
+
9
+ if (import.meta.vitest) {
10
+ const { it, expect } = import.meta.vitest;
11
+ it("add", () => {
12
+ expect(getRadioChecked("on", "on")).toBe(true);
13
+ expect(getRadioChecked("on", undefined)).toBe(undefined);
14
+ expect(getRadioChecked("trueValue", undefined)).toBe(undefined);
15
+ expect(getRadioChecked("trueValue", "bob")).toBe(false);
16
+ expect(getRadioChecked("trueValue", "trueValue")).toBe(true);
17
+ });
18
+ }
@@ -0,0 +1,26 @@
1
+ import { atom } from "jotai";
2
+ import { atomFamily } from "jotai/utils";
3
+ import lodashGet from "lodash/get";
4
+ import {
5
+ fieldErrorsAtom,
6
+ formPropsAtom,
7
+ hasBeenSubmittedAtom,
8
+ touchedFieldsAtom,
9
+ } from "./state";
10
+ import { InternalFormId } from "./state/atomUtils";
11
+ import { controlledFieldsAtom } from "./state/controlledFields";
12
+
13
+ export const resetAtom = atomFamily((formId: InternalFormId) =>
14
+ atom(null, (get, set) => {
15
+ set(fieldErrorsAtom(formId), {});
16
+ set(touchedFieldsAtom(formId), {});
17
+ set(hasBeenSubmittedAtom(formId), false);
18
+
19
+ const { defaultValues } = get(formPropsAtom(formId));
20
+
21
+ const controlledFields = get(controlledFieldsAtom(formId));
22
+ Object.entries(controlledFields).forEach(([name, atom]) =>
23
+ set(atom, lodashGet(defaultValues, name))
24
+ );
25
+ })
26
+ );
@@ -0,0 +1,170 @@
1
+ import { atom, PrimitiveAtom } from "jotai";
2
+ import { useAtomCallback } from "jotai/utils";
3
+ import omit from "lodash/omit";
4
+ import { useCallback, useEffect } from "react";
5
+ import { InternalFormContextValue } from "../formContext";
6
+ import {
7
+ useFieldDefaultValue,
8
+ useFormAtomValue,
9
+ useFormAtom,
10
+ useFormUpdateAtom,
11
+ } from "../hooks";
12
+ import { isHydratedAtom } from "../state";
13
+ import {
14
+ fieldAtomFamily,
15
+ FieldAtomKey,
16
+ formAtomFamily,
17
+ InternalFormId,
18
+ } from "./atomUtils";
19
+
20
+ export const controlledFieldsAtom = formAtomFamily<
21
+ Record<string, PrimitiveAtom<unknown>>
22
+ >({});
23
+ const refCountAtom = fieldAtomFamily(() => atom(0));
24
+ const fieldValueAtom = fieldAtomFamily(() => atom<unknown>(undefined));
25
+ const fieldValueHydratedAtom = fieldAtomFamily(() => atom(false));
26
+
27
+ export const valueUpdatePromiseAtom = fieldAtomFamily(() =>
28
+ atom<Promise<void> | undefined>(undefined)
29
+ );
30
+ export const resolveValueUpdateAtom = fieldAtomFamily(() =>
31
+ atom<(() => void) | undefined>(undefined)
32
+ );
33
+
34
+ const registerAtom = atom(null, (get, set, { formId, field }: FieldAtomKey) => {
35
+ set(refCountAtom({ formId, field }), (prev) => prev + 1);
36
+ const newRefCount = get(refCountAtom({ formId, field }));
37
+ // We don't set hydrated here because it gets set when we know
38
+ // we have the right default values
39
+ if (newRefCount === 1) {
40
+ set(controlledFieldsAtom(formId), (prev) => ({
41
+ ...prev,
42
+ [field]: fieldValueAtom({ formId, field }),
43
+ }));
44
+ }
45
+ });
46
+
47
+ const unregisterAtom = atom(
48
+ null,
49
+ (get, set, { formId, field }: FieldAtomKey) => {
50
+ set(refCountAtom({ formId, field }), (prev) => prev - 1);
51
+ const newRefCount = get(refCountAtom({ formId, field }));
52
+ if (newRefCount === 0) {
53
+ set(controlledFieldsAtom(formId), (prev) => omit(prev, field));
54
+ fieldValueAtom.remove({ formId, field });
55
+ resolveValueUpdateAtom.remove({ formId, field });
56
+ fieldValueHydratedAtom.remove({ formId, field });
57
+ }
58
+ }
59
+ );
60
+
61
+ export const setControlledFieldValueAtom = atom(
62
+ null,
63
+ (
64
+ _get,
65
+ set,
66
+ {
67
+ formId,
68
+ field,
69
+ value,
70
+ }: { formId: InternalFormId; field: string; value: unknown }
71
+ ) => {
72
+ set(fieldValueAtom({ formId, field }), value);
73
+ const resolveAtom = resolveValueUpdateAtom({ formId, field });
74
+ const promiseAtom = valueUpdatePromiseAtom({ formId, field });
75
+
76
+ const promise = new Promise<void>((resolve) =>
77
+ set(resolveAtom, () => {
78
+ resolve();
79
+ set(resolveAtom, undefined);
80
+ set(promiseAtom, undefined);
81
+ })
82
+ );
83
+ set(promiseAtom, promise);
84
+ }
85
+ );
86
+
87
+ export const useControlledFieldValue = (
88
+ context: InternalFormContextValue,
89
+ field: string
90
+ ) => {
91
+ const fieldAtom = fieldValueAtom({ formId: context.formId, field });
92
+ const [value, setValue] = useFormAtom(fieldAtom);
93
+
94
+ const defaultValue = useFieldDefaultValue(field, context);
95
+ const isHydrated = useFormAtomValue(isHydratedAtom(context.formId));
96
+ const [isFieldHydrated, setIsFieldHydrated] = useFormAtom(
97
+ fieldValueHydratedAtom({ formId: context.formId, field })
98
+ );
99
+
100
+ useEffect(() => {
101
+ if (isHydrated && !isFieldHydrated) {
102
+ setValue(defaultValue);
103
+ setIsFieldHydrated(true);
104
+ }
105
+ }, [
106
+ defaultValue,
107
+ field,
108
+ context.formId,
109
+ isFieldHydrated,
110
+ isHydrated,
111
+ setIsFieldHydrated,
112
+ setValue,
113
+ ]);
114
+
115
+ return isFieldHydrated ? value : defaultValue;
116
+ };
117
+
118
+ export const useControllableValue = (
119
+ context: InternalFormContextValue,
120
+ field: string
121
+ ) => {
122
+ const resolveUpdate = useFormAtomValue(
123
+ resolveValueUpdateAtom({ formId: context.formId, field })
124
+ );
125
+ useEffect(() => {
126
+ resolveUpdate?.();
127
+ }, [resolveUpdate]);
128
+
129
+ const register = useFormUpdateAtom(registerAtom);
130
+ const unregister = useFormUpdateAtom(unregisterAtom);
131
+ useEffect(() => {
132
+ register({ formId: context.formId, field });
133
+ return () => unregister({ formId: context.formId, field });
134
+ }, [context.formId, field, register, unregister]);
135
+
136
+ const setControlledFieldValue = useFormUpdateAtom(
137
+ setControlledFieldValueAtom
138
+ );
139
+ const setValue = useCallback(
140
+ (value: unknown) =>
141
+ setControlledFieldValue({ formId: context.formId, field, value }),
142
+ [field, context.formId, setControlledFieldValue]
143
+ );
144
+
145
+ const value = useControlledFieldValue(context, field);
146
+
147
+ return [value, setValue] as const;
148
+ };
149
+
150
+ export const useUpdateControllableValue = (formId: InternalFormId) => {
151
+ const setControlledFieldValue = useFormUpdateAtom(
152
+ setControlledFieldValueAtom
153
+ );
154
+ return useCallback(
155
+ (field: string, value: unknown) =>
156
+ setControlledFieldValue({ formId, field, value }),
157
+ [formId, setControlledFieldValue]
158
+ );
159
+ };
160
+
161
+ export const useAwaitValue = (formId: InternalFormId) => {
162
+ return useAtomCallback(
163
+ useCallback(
164
+ async (get, _set, field: string) => {
165
+ await get(valueUpdatePromiseAtom({ formId, field }));
166
+ },
167
+ [formId]
168
+ )
169
+ );
170
+ };
@@ -48,14 +48,6 @@ export const isValidAtom = atomFamily((formId: InternalFormId) =>
48
48
  atom((get) => Object.keys(get(fieldErrorsAtom(formId))).length === 0)
49
49
  );
50
50
 
51
- export const resetAtom = atomFamily((formId: InternalFormId) =>
52
- atom(null, (_get, set) => {
53
- set(fieldErrorsAtom(formId), {});
54
- set(touchedFieldsAtom(formId), {});
55
- set(hasBeenSubmittedAtom(formId), false);
56
- })
57
- );
58
-
59
51
  export const startSubmitAtom = atomFamily((formId: InternalFormId) =>
60
52
  atom(null, (_get, set) => {
61
53
  set(isSubmittingAtom(formId), true);
package/src/server.ts CHANGED
@@ -24,7 +24,8 @@ import {
24
24
  */
25
25
  export function validationError(
26
26
  error: ValidatorError,
27
- repopulateFields?: unknown
27
+ repopulateFields?: unknown,
28
+ init?: ResponseInit
28
29
  ): Response {
29
30
  return json<ValidationErrorResponseData>(
30
31
  {
@@ -33,7 +34,7 @@ export function validationError(
33
34
  repopulateFields,
34
35
  formId: error.formId,
35
36
  },
36
- { status: 422 }
37
+ { status: 422, ...init }
37
38
  );
38
39
  }
39
40