remix-validated-form 4.4.2 → 4.5.0-beta.1

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 (48) hide show
  1. package/.turbo/turbo-build.log +13 -10
  2. package/browser/ValidatedForm.js +24 -39
  3. package/browser/hooks.d.ts +1 -1
  4. package/browser/internal/hooks.d.ts +3 -2
  5. package/browser/internal/hooks.js +1 -0
  6. package/browser/internal/state/controlledFieldStore.d.ts +23 -21
  7. package/browser/internal/state/controlledFieldStore.js +32 -19
  8. package/browser/internal/state/controlledFields.d.ts +3 -3
  9. package/browser/internal/state/controlledFields.js +19 -21
  10. package/browser/internal/state/createFormStore.d.ts +16 -8
  11. package/browser/internal/state/createFormStore.js +62 -8
  12. package/browser/internal/state/storeHooks.d.ts +1 -3
  13. package/browser/internal/state/storeHooks.js +2 -8
  14. package/browser/internal/state/types.d.ts +1 -0
  15. package/browser/internal/state/types.js +1 -0
  16. package/browser/unreleased/formStateHooks.d.ts +8 -2
  17. package/browser/unreleased/formStateHooks.js +12 -2
  18. package/browser/userFacingFormContext.d.ts +8 -2
  19. package/browser/userFacingFormContext.js +15 -4
  20. package/dist/remix-validated-form.cjs.js +3 -3
  21. package/dist/remix-validated-form.cjs.js.map +1 -1
  22. package/dist/remix-validated-form.es.js +169 -113
  23. package/dist/remix-validated-form.es.js.map +1 -1
  24. package/dist/remix-validated-form.umd.js +3 -3
  25. package/dist/remix-validated-form.umd.js.map +1 -1
  26. package/dist/types/hooks.d.ts +1 -1
  27. package/dist/types/internal/hooks.d.ts +3 -2
  28. package/dist/types/internal/state/controlledFieldStore.d.ts +23 -21
  29. package/dist/types/internal/state/controlledFields.d.ts +3 -3
  30. package/dist/types/internal/state/createFormStore.d.ts +16 -8
  31. package/dist/types/internal/state/storeHooks.d.ts +1 -3
  32. package/dist/types/internal/state/types.d.ts +1 -0
  33. package/dist/types/unreleased/formStateHooks.d.ts +8 -2
  34. package/dist/types/userFacingFormContext.d.ts +8 -2
  35. package/package.json +4 -4
  36. package/src/ValidatedForm.tsx +41 -56
  37. package/src/internal/hooks.ts +4 -1
  38. package/src/internal/state/controlledFieldStore.ts +95 -74
  39. package/src/internal/state/controlledFields.ts +38 -26
  40. package/src/internal/state/createFormStore.ts +199 -115
  41. package/src/internal/state/storeHooks.ts +3 -16
  42. package/src/internal/state/types.ts +1 -0
  43. package/src/unreleased/formStateHooks.ts +24 -3
  44. package/src/userFacingFormContext.ts +38 -13
  45. package/dist/types/internal/state/cleanup.d.ts +0 -2
  46. package/dist/types/internal/state/storeFamily.d.ts +0 -9
  47. package/src/internal/state/cleanup.ts +0 -8
  48. package/src/internal/state/storeFamily.ts +0 -24
@@ -1,15 +1,18 @@
1
- $ vite build
2
- vite v2.9.5 building for production...
1
+ $ vite build
2
+ vite v2.9.5 building for production...
3
3
  transforming...
4
- 321 modules transformed.
4
+ ✓ 319 modules transformed.
5
5
  rendering chunks...
6
- dist/remix-validated-form.cjs.js 43.88 KiB / gzip: 16.70 KiB
7
- dist/remix-validated-form.es.js 98.54 KiB / gzip: 23.35 KiB
8
- dist/remix-validated-form.umd.js 44.13 KiB / gzip: 16.84 KiB
9
-
10
- [vite:dts] Start generate declaration files...
11
- [vite:dts] Declaration files built in 1804ms.
12
-
6
+ dist/remix-validated-form.cjs.js  45.48 KiB / gzip: 17.10 KiB
7
+ dist/remix-validated-form.cjs.js.map 252.52 KiB
8
+ dist/remix-validated-form.es.js  101.16 KiB / gzip: 23.79 KiB
9
+ dist/remix-validated-form.es.js.map 260.47 KiB
10
+ dist/remix-validated-form.umd.js  45.73 KiB / gzip: 17.22 KiB
11
+ dist/remix-validated-form.umd.js.map 252.50 KiB
12
+ 
13
+ [vite:dts] Start generate declaration files...
14
+ [vite:dts] Declaration files built in 1849ms.
15
+ 
13
16
  No name was provided for external module 'react' in output.globals – guessing 'React'
14
17
  No name was provided for external module '@remix-run/react' in output.globals – guessing 'react'
15
18
  No name was provided for external module '@remix-run/server-runtime' in output.globals – guessing 'serverRuntime'
@@ -1,14 +1,15 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, 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
- import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react";
4
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
5
5
  import { useIsSubmitting, useIsValid } from "./hooks";
6
6
  import { FORM_ID_FIELD } from "./internal/constants";
7
7
  import { InternalFormContext, } from "./internal/formContext";
8
8
  import { useDefaultValuesFromLoader, useErrorResponseForForm, useHasActiveFormSubmit, useSetFieldErrors, } from "./internal/hooks";
9
9
  import { useMultiValueMap } from "./internal/MultiValueMap";
10
- import { cleanupFormState } from "./internal/state/cleanup";
11
- import { useControlledFieldStore, useFormStore, } from "./internal/state/storeHooks";
10
+ import { useControlledFieldStore } from "./internal/state/controlledFieldStore";
11
+ import { useRootFormStore, } from "./internal/state/createFormStore";
12
+ import { useFormStore } from "./internal/state/storeHooks";
12
13
  import { useSubmitComplete } from "./internal/submissionCallbacks";
13
14
  import { mergeRefs, useDeepEqualsMemo, useIsomorphicLayoutEffect as useLayoutEffect, } from "./internal/util";
14
15
  const getDataFromForm = (el) => new FormData(el);
@@ -114,16 +115,13 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
114
115
  const setFieldErrors = useSetFieldErrors(formId);
115
116
  const setFieldError = useFormStore(formId, (state) => state.setFieldError);
116
117
  const reset = useFormStore(formId, (state) => state.reset);
117
- const resetControlledFields = useControlledFieldStore(formId, (state) => state.reset);
118
+ const resetControlledFields = useControlledFieldStore((state) => state.reset);
118
119
  const startSubmit = useFormStore(formId, (state) => state.startSubmit);
119
120
  const endSubmit = useFormStore(formId, (state) => state.endSubmit);
120
121
  const syncFormProps = useFormStore(formId, (state) => state.syncFormProps);
121
- const setHydrated = useFormStore(formId, (state) => state.setHydrated);
122
122
  const setFormElementInState = useFormStore(formId, (state) => state.setFormElement);
123
- useEffect(() => {
124
- setHydrated();
125
- return () => cleanupFormState(formId);
126
- }, [formId, setHydrated]);
123
+ const cleanupForm = useRootFormStore((state) => state.cleanupForm);
124
+ const registerForm = useRootFormStore((state) => state.registerForm);
127
125
  const customFocusHandlers = useMultiValueMap();
128
126
  const registerReceiveFocus = useCallback((fieldName, handler) => {
129
127
  customFocusHandlers().add(fieldName, handler);
@@ -131,6 +129,12 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
131
129
  customFocusHandlers().remove(fieldName, handler);
132
130
  };
133
131
  }, [customFocusHandlers]);
132
+ // TODO: all these hooks running at startup cause extra, unnecessary renders
133
+ // There must be a nice way to avoid this.
134
+ useLayoutEffect(() => {
135
+ registerForm(formId);
136
+ return () => cleanupForm(formId);
137
+ }, [cleanupForm, formId, registerForm]);
134
138
  useLayoutEffect(() => {
135
139
  var _a;
136
140
  syncFormProps({
@@ -149,6 +153,9 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
149
153
  backendDefaultValues,
150
154
  validator,
151
155
  ]);
156
+ useLayoutEffect(() => {
157
+ setFormElementInState(formRef.current);
158
+ }, [setFormElementInState]);
152
159
  useEffect(() => {
153
160
  var _a;
154
161
  setFieldErrors((_a = backendError === null || backendError === void 0 ? void 0 : backendError.fieldErrors) !== null && _a !== void 0 ? _a : {});
@@ -156,27 +163,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
156
163
  useSubmitComplete(hasActiveSubmission, () => {
157
164
  endSubmit();
158
165
  });
159
- let clickedButtonRef = React.useRef();
160
- useEffect(() => {
161
- let form = formRef.current;
162
- if (!form)
163
- return;
164
- function handleClick(event) {
165
- if (!(event.target instanceof HTMLElement))
166
- return;
167
- let submitButton = event.target.closest("button,input[type=submit]");
168
- if (submitButton &&
169
- submitButton.form === form &&
170
- submitButton.type === "submit") {
171
- clickedButtonRef.current = submitButton;
172
- }
173
- }
174
- window.addEventListener("click", handleClick, { capture: true });
175
- return () => {
176
- window.removeEventListener("click", handleClick, { capture: true });
177
- };
178
- }, []);
179
- const handleSubmit = async (e) => {
166
+ const handleSubmit = async (e, target, nativeEvent) => {
180
167
  startSubmit();
181
168
  const result = await validator.validate(getDataFromForm(e.currentTarget));
182
169
  if (result.error) {
@@ -193,8 +180,7 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
193
180
  endSubmit();
194
181
  return;
195
182
  }
196
- const submitter = e.nativeEvent
197
- .submitter;
183
+ const submitter = nativeEvent.submitter;
198
184
  // We deviate from the remix code here a bit because of our async submit.
199
185
  // In remix's `FormImpl`, they use `event.currentTarget` to get the form,
200
186
  // but we already have the form in `formRef.current` so we can just use that.
@@ -203,18 +189,17 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
203
189
  if (fetcher)
204
190
  fetcher.submit(submitter || e.currentTarget);
205
191
  else
206
- submit(submitter || e.currentTarget, { method, replace });
207
- clickedButtonRef.current = null;
192
+ submit(submitter || target, { method, replace });
208
193
  }
209
194
  };
210
- return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp, setFormElementInState]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
195
+ return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
211
196
  e.preventDefault();
212
- handleSubmit(e);
197
+ handleSubmit(e, e.currentTarget, e.nativeEvent);
213
198
  }, onReset: (event) => {
214
199
  onReset === null || onReset === void 0 ? void 0 : onReset(event);
215
200
  if (event.defaultPrevented)
216
201
  return;
217
202
  reset();
218
- resetControlledFields();
219
- }, 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));
203
+ resetControlledFields(formId);
204
+ }, children: _jsx(InternalFormContext.Provider, { value: contextValue, children: _jsxs(_Fragment, { 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) }, void 0));
220
205
  }
@@ -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) => (fieldName: string, value: unknown) => void;
67
+ export declare const useUpdateControlledField: (formId?: string | undefined) => (field: string, value: unknown) => void;
@@ -1,7 +1,7 @@
1
1
  import { FieldErrors, ValidationErrorResponseData } from "..";
2
2
  import { InternalFormContextValue } from "./formContext";
3
3
  import { Hydratable } from "./hydratable";
4
- import { InternalFormId } from "./state/storeFamily";
4
+ import { InternalFormId } from "./state/types";
5
5
  export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
6
6
  export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
7
7
  export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => Hydratable<FieldErrors | undefined>;
@@ -18,7 +18,7 @@ export declare const useInternalIsSubmitting: (formId: InternalFormId) => boolea
18
18
  export declare const useInternalIsValid: (formId: InternalFormId) => boolean;
19
19
  export declare const useInternalHasBeenSubmitted: (formId: InternalFormId) => boolean;
20
20
  export declare const useValidateField: (formId: InternalFormId) => (fieldName: string) => Promise<string | null>;
21
- export declare const useValidate: (formId: InternalFormId) => () => Promise<void>;
21
+ export declare const useValidate: (formId: InternalFormId) => () => Promise<import("..").ValidationResult<unknown>>;
22
22
  export declare const useRegisterReceiveFocus: (formId: InternalFormId) => (fieldName: string, handler: () => void) => () => void;
23
23
  export declare const useSyncedDefaultValues: (formId: InternalFormId) => {
24
24
  [fieldName: string]: any;
@@ -28,5 +28,6 @@ export declare const useTouchedFields: (formId: InternalFormId) => import("..").
28
28
  export declare const useFieldErrors: (formId: InternalFormId) => FieldErrors;
29
29
  export declare const useSetFieldErrors: (formId: InternalFormId) => (errors: FieldErrors) => void;
30
30
  export declare const useResetFormElement: (formId: InternalFormId) => () => void;
31
+ export declare const useSubmitForm: (formId: InternalFormId) => () => void;
31
32
  export declare const useFormActionProp: (formId: InternalFormId) => string | undefined;
32
33
  export declare const useFormSubactionProp: (formId: InternalFormId) => string | undefined;
@@ -113,5 +113,6 @@ export const useTouchedFields = (formId) => useFormStore(formId, (state) => stat
113
113
  export const useFieldErrors = (formId) => useFormStore(formId, (state) => state.fieldErrors);
114
114
  export const useSetFieldErrors = (formId) => useFormStore(formId, (state) => state.setFieldErrors);
115
115
  export const useResetFormElement = (formId) => useFormStore(formId, (state) => state.resetFormElement);
116
+ export const useSubmitForm = (formId) => useFormStore(formId, (state) => state.submit);
116
117
  export const useFormActionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.action; });
117
118
  export const useFormSubactionProp = (formId) => useFormStore(formId, (state) => { var _a; return (_a = state.formProps) === null || _a === void 0 ? void 0 : _a.subaction; });
@@ -1,24 +1,26 @@
1
+ import { InternalFormId } from "./types";
2
+ export declare type FieldState = {
3
+ refCount: number;
4
+ value: unknown;
5
+ defaultValue?: unknown;
6
+ hydrated: boolean;
7
+ valueUpdatePromise: Promise<void> | undefined;
8
+ resolveValueUpdate: (() => void) | undefined;
9
+ };
1
10
  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
+ forms: {
12
+ [formId: InternalFormId]: {
13
+ [fieldName: string]: FieldState | undefined;
14
+ };
11
15
  };
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;
16
+ register: (formId: InternalFormId, fieldName: string) => void;
17
+ unregister: (formId: InternalFormId, fieldName: string) => void;
18
+ getField: (formId: InternalFormId, fieldName: string) => FieldState | undefined;
19
+ setValue: (formId: InternalFormId, fieldName: string, value: unknown) => void;
20
+ hydrateWithDefault: (formId: InternalFormId, fieldName: string, defaultValue: unknown) => void;
21
+ awaitValueUpdate: (formId: InternalFormId, fieldName: string) => Promise<void>;
22
+ reset: (formId: InternalFormId) => void;
24
23
  };
24
+ export declare const useControlledFieldStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ControlledFieldState>, "setState"> & {
25
+ setState(nextStateOrUpdater: ControlledFieldState | Partial<ControlledFieldState> | ((state: import("immer/dist/internal").WritableDraft<ControlledFieldState>) => void), shouldReplace?: boolean | undefined): void;
26
+ }>;
@@ -1,14 +1,16 @@
1
1
  import create from "zustand";
2
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++;
3
+ export const useControlledFieldStore = create()(immer((set, get) => ({
4
+ forms: {},
5
+ register: (formId, field) => set((state) => {
6
+ if (!state.forms[formId]) {
7
+ state.forms[formId] = {};
8
+ }
9
+ if (state.forms[formId][field]) {
10
+ state.forms[formId][field].refCount++;
9
11
  }
10
12
  else {
11
- state.fields[field] = {
13
+ state.forms[formId][field] = {
12
14
  refCount: 1,
13
15
  value: undefined,
14
16
  hydrated: false,
@@ -17,16 +19,23 @@ export const controlledFieldStore = storeFamily(() => create()(immer((set, get,
17
19
  };
18
20
  }
19
21
  }),
20
- unregister: (field) => set((state) => {
21
- const fieldState = state.fields[field];
22
+ unregister: (formId, field) => set((state) => {
23
+ var _a;
24
+ const formState = (_a = state.forms) === null || _a === void 0 ? void 0 : _a[formId];
25
+ const fieldState = formState === null || formState === void 0 ? void 0 : formState[field];
22
26
  if (!fieldState)
23
27
  return;
24
28
  fieldState.refCount--;
25
29
  if (fieldState.refCount === 0)
26
- delete state.fields[field];
30
+ delete formState[field];
27
31
  }),
28
- setValue: (field, value) => set((state) => {
29
- const fieldState = state.fields[field];
32
+ getField: (formId, field) => {
33
+ var _a, _b;
34
+ return (_b = (_a = get().forms) === null || _a === void 0 ? void 0 : _a[formId]) === null || _b === void 0 ? void 0 : _b[field];
35
+ },
36
+ setValue: (formId, field, value) => set((state) => {
37
+ var _a, _b;
38
+ const fieldState = (_b = (_a = state.forms) === null || _a === void 0 ? void 0 : _a[formId]) === null || _b === void 0 ? void 0 : _b[field];
30
39
  if (!fieldState)
31
40
  return;
32
41
  fieldState.value = value;
@@ -35,23 +44,27 @@ export const controlledFieldStore = storeFamily(() => create()(immer((set, get,
35
44
  });
36
45
  fieldState.valueUpdatePromise = promise;
37
46
  }),
38
- hydrateWithDefault: (field, defaultValue) => set((state) => {
39
- const fieldState = state.fields[field];
47
+ hydrateWithDefault: (formId, field, defaultValue) => set((state) => {
48
+ var _a;
49
+ const fieldState = (_a = state.forms[formId]) === null || _a === void 0 ? void 0 : _a[field];
40
50
  if (!fieldState)
41
51
  return;
42
52
  fieldState.value = defaultValue;
43
53
  fieldState.defaultValue = defaultValue;
44
54
  fieldState.hydrated = true;
45
55
  }),
46
- awaitValueUpdate: async (field) => {
56
+ awaitValueUpdate: async (formId, field) => {
47
57
  var _a;
48
- await ((_a = get().fields[field]) === null || _a === void 0 ? void 0 : _a.valueUpdatePromise);
58
+ await ((_a = get().getField(formId, field)) === null || _a === void 0 ? void 0 : _a.valueUpdatePromise);
49
59
  },
50
- reset: () => set((state) => {
51
- Object.values(state.fields).forEach((field) => {
60
+ reset: (formId) => set((state) => {
61
+ const formState = state.forms[formId];
62
+ if (!formState)
63
+ return;
64
+ Object.values(formState).forEach((field) => {
52
65
  if (!field)
53
66
  return;
54
67
  field.value = field.defaultValue;
55
68
  });
56
69
  }),
57
- }))));
70
+ })));
@@ -1,6 +1,6 @@
1
1
  import { InternalFormContextValue } from "../formContext";
2
- import { InternalFormId } from "./storeFamily";
2
+ import { InternalFormId } from "./types";
3
3
  export declare const useControlledFieldValue: (context: InternalFormContextValue, field: string) => any;
4
4
  export declare const useControllableValue: (context: InternalFormContextValue, field: string) => readonly [any, (value: unknown) => void];
5
- export declare const useUpdateControllableValue: (formId: InternalFormId) => (fieldName: string, value: unknown) => void;
6
- export declare const useAwaitValue: (formId: InternalFormId) => (fieldName: string) => Promise<void>;
5
+ export declare const useUpdateControllableValue: (formId: InternalFormId) => (field: string, value: unknown) => void;
6
+ export declare const useAwaitValue: (formId: InternalFormId) => (field: string) => Promise<void>;
@@ -1,20 +1,19 @@
1
1
  import { useCallback, useEffect } from "react";
2
2
  import { useFieldDefaultValue } from "../hooks";
3
- import { controlledFieldStore } from "./controlledFieldStore";
4
- import { formStore } from "./createFormStore";
3
+ import { useControlledFieldStore } from "./controlledFieldStore";
4
+ import { useFormStore } from "./storeHooks";
5
5
  export const useControlledFieldValue = (context, field) => {
6
- const useValueStore = controlledFieldStore(context.formId);
7
- const value = useValueStore((state) => { var _a; return (_a = state.fields[field]) === null || _a === void 0 ? void 0 : _a.value; });
8
- const useFormStore = formStore(context.formId);
9
- const isFormHydrated = useFormStore((state) => state.isHydrated);
6
+ const value = useControlledFieldStore((state) => { var _a; return (_a = state.getField(context.formId, field)) === null || _a === void 0 ? void 0 : _a.value; });
7
+ const isFormHydrated = useFormStore(context.formId, (state) => state.isHydrated);
10
8
  const defaultValue = useFieldDefaultValue(field, context);
11
- const isFieldHydrated = useValueStore((state) => { var _a, _b; return (_b = (_a = state.fields[field]) === null || _a === void 0 ? void 0 : _a.hydrated) !== null && _b !== void 0 ? _b : false; });
12
- const hydrateWithDefault = useValueStore((state) => state.hydrateWithDefault);
9
+ const isFieldHydrated = useControlledFieldStore((state) => { var _a, _b; return (_b = (_a = state.getField(context.formId, field)) === null || _a === void 0 ? void 0 : _a.hydrated) !== null && _b !== void 0 ? _b : false; });
10
+ const hydrateWithDefault = useControlledFieldStore((state) => state.hydrateWithDefault);
13
11
  useEffect(() => {
14
12
  if (isFormHydrated && !isFieldHydrated) {
15
- hydrateWithDefault(field, defaultValue);
13
+ hydrateWithDefault(context.formId, field, defaultValue);
16
14
  }
17
15
  }, [
16
+ context.formId,
18
17
  defaultValue,
19
18
  field,
20
19
  hydrateWithDefault,
@@ -24,27 +23,26 @@ export const useControlledFieldValue = (context, field) => {
24
23
  return isFieldHydrated ? value : defaultValue;
25
24
  };
26
25
  export const useControllableValue = (context, field) => {
27
- const useValueStore = controlledFieldStore(context.formId);
28
- const resolveUpdate = useValueStore((state) => { var _a; return (_a = state.fields[field]) === null || _a === void 0 ? void 0 : _a.resolveValueUpdate; });
26
+ const resolveUpdate = useControlledFieldStore((state) => { var _a; return (_a = state.getField(context.formId, field)) === null || _a === void 0 ? void 0 : _a.resolveValueUpdate; });
29
27
  useEffect(() => {
30
28
  resolveUpdate === null || resolveUpdate === void 0 ? void 0 : resolveUpdate();
31
29
  }, [resolveUpdate]);
32
- const register = useValueStore((state) => state.register);
33
- const unregister = useValueStore((state) => state.unregister);
30
+ const register = useControlledFieldStore((state) => state.register);
31
+ const unregister = useControlledFieldStore((state) => state.unregister);
34
32
  useEffect(() => {
35
- register(field);
36
- return () => unregister(field);
33
+ register(context.formId, field);
34
+ return () => unregister(context.formId, field);
37
35
  }, [context.formId, field, register, unregister]);
38
- const setControlledFieldValue = useValueStore((state) => state.setValue);
39
- const setValue = useCallback((value) => setControlledFieldValue(field, value), [field, setControlledFieldValue]);
36
+ const setControlledFieldValue = useControlledFieldStore((state) => state.setValue);
37
+ const setValue = useCallback((value) => setControlledFieldValue(context.formId, field, value), [context.formId, field, setControlledFieldValue]);
40
38
  const value = useControlledFieldValue(context, field);
41
39
  return [value, setValue];
42
40
  };
43
41
  export const useUpdateControllableValue = (formId) => {
44
- const useValueStore = controlledFieldStore(formId);
45
- return useValueStore((state) => state.setValue);
42
+ const setValue = useControlledFieldStore((state) => state.setValue);
43
+ return useCallback((field, value) => setValue(formId, field, value), [formId, setValue]);
46
44
  };
47
45
  export const useAwaitValue = (formId) => {
48
- const useValueStore = controlledFieldStore(formId);
49
- return useValueStore((state) => state.awaitValueUpdate);
46
+ const awaitValue = useControlledFieldStore((state) => state.awaitValueUpdate);
47
+ return useCallback((field) => awaitValue(formId, field), [awaitValue, formId]);
50
48
  };
@@ -1,4 +1,6 @@
1
- import { FieldErrors, TouchedFields, Validator } from "../../validation/types";
1
+ import { WritableDraft } from "immer/dist/internal";
2
+ import { FieldErrors, TouchedFields, ValidationResult, Validator } from "../../validation/types";
3
+ import { InternalFormId } from "./types";
2
4
  export declare type SyncedFormProps = {
3
5
  formId?: string;
4
6
  action?: string;
@@ -9,6 +11,14 @@ export declare type SyncedFormProps = {
9
11
  registerReceiveFocus: (fieldName: string, handler: () => void) => () => void;
10
12
  validator: Validator<unknown>;
11
13
  };
14
+ export declare type FormStoreState = {
15
+ forms: {
16
+ [formId: InternalFormId]: FormState;
17
+ };
18
+ form: (formId: InternalFormId) => FormState;
19
+ registerForm: (formId: InternalFormId) => void;
20
+ cleanupForm: (formId: InternalFormId) => void;
21
+ };
12
22
  export declare type FormState = {
13
23
  isHydrated: boolean;
14
24
  isSubmitting: boolean;
@@ -29,12 +39,10 @@ export declare type FormState = {
29
39
  setHydrated: () => void;
30
40
  setFormElement: (formElement: HTMLFormElement | null) => void;
31
41
  validateField: (fieldName: string) => Promise<string | null>;
32
- validate: () => Promise<void>;
42
+ validate: () => Promise<ValidationResult<unknown>>;
33
43
  resetFormElement: () => void;
44
+ submit: () => void;
34
45
  };
35
- export declare const formStore: {
36
- (formId: import("./storeFamily").InternalFormId): import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<FormState>, "setState"> & {
37
- setState(nextStateOrUpdater: FormState | Partial<FormState> | ((state: import("immer/dist/internal").WritableDraft<FormState>) => void), shouldReplace?: boolean | undefined): void;
38
- }>;
39
- remove(formId: import("./storeFamily").InternalFormId): void;
40
- };
46
+ export declare const useRootFormStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<FormStoreState>, "setState"> & {
47
+ setState(nextStateOrUpdater: FormStoreState | Partial<FormStoreState> | ((state: WritableDraft<FormStoreState>) => void), shouldReplace?: boolean | undefined): void;
48
+ }>;
@@ -1,9 +1,37 @@
1
1
  import invariant from "tiny-invariant";
2
2
  import create from "zustand";
3
3
  import { immer } from "zustand/middleware/immer";
4
- import { controlledFieldStore } from "./controlledFieldStore";
5
- import { storeFamily } from "./storeFamily";
6
- export const formStore = storeFamily((formId) => create()(immer((set, get, api) => ({
4
+ import { useControlledFieldStore } from "./controlledFieldStore";
5
+ const noOp = () => { };
6
+ const defaultFormState = {
7
+ isHydrated: false,
8
+ isSubmitting: false,
9
+ hasBeenSubmitted: false,
10
+ touchedFields: {},
11
+ fieldErrors: {},
12
+ formElement: null,
13
+ isValid: () => true,
14
+ startSubmit: noOp,
15
+ endSubmit: noOp,
16
+ setTouched: noOp,
17
+ setFieldError: noOp,
18
+ setFieldErrors: noOp,
19
+ clearFieldError: noOp,
20
+ reset: () => noOp,
21
+ syncFormProps: noOp,
22
+ setHydrated: noOp,
23
+ setFormElement: noOp,
24
+ validateField: async () => null,
25
+ validate: async () => {
26
+ throw new Error("Validate called before form was initialized.");
27
+ },
28
+ submit: async () => {
29
+ throw new Error("Submit called before form was initialized.");
30
+ },
31
+ resetFormElement: noOp,
32
+ };
33
+ const createFormState = (formId, set, get) => ({
34
+ // It's not "hydrated" until the form props are synced
7
35
  isHydrated: false,
8
36
  isSubmitting: false,
9
37
  hasBeenSubmitted: false,
@@ -37,6 +65,7 @@ export const formStore = storeFamily((formId) => create()(immer((set, get, api)
37
65
  }),
38
66
  syncFormProps: (props) => set((state) => {
39
67
  state.formProps = props;
68
+ state.isHydrated = true;
40
69
  }),
41
70
  setHydrated: () => set((state) => {
42
71
  state.isHydrated = true;
@@ -58,7 +87,7 @@ export const formStore = storeFamily((formId) => create()(immer((set, get, api)
58
87
  invariant(formElement, "Cannot find reference to form. This is probably a bug in remix-validated-form.");
59
88
  const validator = (_a = get().formProps) === null || _a === void 0 ? void 0 : _a.validator;
60
89
  invariant(validator, "Cannot validator. This is probably a bug in remix-validated-form.");
61
- await ((_c = (_b = controlledFieldStore(formId).getState()).awaitValueUpdate) === null || _c === void 0 ? void 0 : _c.call(_b, field));
90
+ await ((_c = (_b = useControlledFieldStore.getState()).awaitValueUpdate) === null || _c === void 0 ? void 0 : _c.call(_b, formId, field));
62
91
  const { error } = await validator.validateField(new FormData(formElement), field);
63
92
  if (error) {
64
93
  get().setFieldError(field, error);
@@ -75,9 +104,34 @@ export const formStore = storeFamily((formId) => create()(immer((set, get, api)
75
104
  invariant(formElement, "Cannot find reference to form. This is probably a bug in remix-validated-form.");
76
105
  const validator = (_a = get().formProps) === null || _a === void 0 ? void 0 : _a.validator;
77
106
  invariant(validator, "Cannot validator. This is probably a bug in remix-validated-form.");
78
- const { error } = await validator.validate(new FormData(formElement));
79
- if (error)
80
- get().setFieldErrors(error.fieldErrors);
107
+ const result = await validator.validate(new FormData(formElement));
108
+ if (result.error)
109
+ get().setFieldErrors(result.error.fieldErrors);
110
+ return result;
111
+ },
112
+ submit: () => {
113
+ const formElement = get().formElement;
114
+ invariant(formElement, "Cannot find reference to form. This is probably a bug in remix-validated-form.");
115
+ formElement.submit();
81
116
  },
82
117
  resetFormElement: () => { var _a; return (_a = get().formElement) === null || _a === void 0 ? void 0 : _a.reset(); },
83
- }))));
118
+ });
119
+ export const useRootFormStore = create()(immer((set, get) => ({
120
+ forms: {},
121
+ form: (formId) => {
122
+ var _a;
123
+ return (_a = get().forms[formId]) !== null && _a !== void 0 ? _a : defaultFormState;
124
+ },
125
+ cleanupForm: (formId) => {
126
+ set((state) => {
127
+ delete state.forms[formId];
128
+ });
129
+ },
130
+ registerForm: (formId) => {
131
+ if (get().forms[formId])
132
+ return;
133
+ set((state) => {
134
+ state.forms[formId] = createFormState(formId, (setter) => set((state) => setter(state.forms[formId])), () => get().forms[formId]);
135
+ });
136
+ },
137
+ })));
@@ -1,5 +1,3 @@
1
- import { ControlledFieldState } from "./controlledFieldStore";
2
1
  import { FormState } from "./createFormStore";
3
- import { InternalFormId } from "./storeFamily";
2
+ import { InternalFormId } from "./types";
4
3
  export declare const useFormStore: <T>(formId: InternalFormId, selector: (state: FormState) => T) => T;
5
- export declare const useControlledFieldStore: <T>(formId: InternalFormId, selector: (state: ControlledFieldState) => T) => T;
@@ -1,10 +1,4 @@
1
- import { controlledFieldStore, } from "./controlledFieldStore";
2
- import { formStore } from "./createFormStore";
1
+ import { useRootFormStore } from "./createFormStore";
3
2
  export const useFormStore = (formId, selector) => {
4
- const useStore = formStore(formId);
5
- return useStore(selector);
6
- };
7
- export const useControlledFieldStore = (formId, selector) => {
8
- const useStore = controlledFieldStore(formId);
9
- return useStore(selector);
3
+ return useRootFormStore((state) => selector(state.form(formId)));
10
4
  };
@@ -0,0 +1 @@
1
+ export declare type InternalFormId = string | symbol;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { FieldErrors, TouchedFields } from "../validation/types";
1
+ import { FieldErrors, TouchedFields, ValidationResult } from "../validation/types";
2
2
  export declare type FormState = {
3
3
  fieldErrors: FieldErrors;
4
4
  isSubmitting: boolean;
@@ -33,7 +33,7 @@ export declare type FormHelpers = {
33
33
  /**
34
34
  * Validate the whole form and populate any errors.
35
35
  */
36
- validate: () => Promise<void>;
36
+ validate: () => Promise<ValidationResult<unknown>>;
37
37
  /**
38
38
  * Clears all errors on the form.
39
39
  */
@@ -45,6 +45,12 @@ export declare type FormHelpers = {
45
45
  * or clicking a button element with `type="reset"`.
46
46
  */
47
47
  reset: () => void;
48
+ /**
49
+ * Submits the form, running all validations first.
50
+ *
51
+ * _Note_: This is equivalent to clicking a button element with `type="submit"` or calling formElement.submit().
52
+ */
53
+ submit: () => void;
48
54
  };
49
55
  /**
50
56
  * Returns helpers that can be used to update the form state.