remix-validated-form 4.1.0-beta.1 → 4.1.0-beta.2

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.
@@ -10,7 +10,7 @@ export declare type FormProps<DataType> = {
10
10
  * A submit callback that gets called when the form is submitted
11
11
  * after all validations have been run.
12
12
  */
13
- onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => Promise<void>;
13
+ onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void | Promise<void>;
14
14
  /**
15
15
  * Allows you to provide a `fetcher` from remix's `useFetcher` hook.
16
16
  * The form will use the fetcher for loading states, action data, etc
@@ -75,6 +75,22 @@ const FormResetter = ({ resetAfterSubmit, formRef, }) => {
75
75
  });
76
76
  return null;
77
77
  };
78
+ function formEventProxy(event) {
79
+ let defaultPrevented = false;
80
+ return new Proxy(event, {
81
+ get: (target, prop) => {
82
+ if (prop === "preventDefault") {
83
+ return () => {
84
+ defaultPrevented = true;
85
+ };
86
+ }
87
+ if (prop === "defaultPrevented") {
88
+ return defaultPrevented;
89
+ }
90
+ return target[prop];
91
+ },
92
+ });
93
+ }
78
94
  /**
79
95
  * The primary form component of `remix-validated-form`.
80
96
  */
@@ -170,28 +186,36 @@ export function ValidatedForm({ validator, onSubmit, children, fetcher, action,
170
186
  window.removeEventListener("click", handleClick);
171
187
  };
172
188
  }, []);
173
- return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: async (e) => {
174
- e.preventDefault();
175
- startSubmit({ formAtom });
176
- const result = await validator.validate(getDataFromForm(e.currentTarget));
177
- if (result.error) {
178
- endSubmit({ formAtom });
179
- setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
180
- if (!disableFocusOnError) {
181
- focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
182
- }
189
+ const handleSubmit = async (e) => {
190
+ startSubmit({ formAtom });
191
+ const result = await validator.validate(getDataFromForm(e.currentTarget));
192
+ if (result.error) {
193
+ endSubmit({ formAtom });
194
+ setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
195
+ if (!disableFocusOnError) {
196
+ focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
183
197
  }
184
- else {
185
- onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, e);
186
- if (fetcher)
187
- fetcher.submit(clickedButtonRef.current || e.currentTarget);
188
- else
189
- submit(clickedButtonRef.current || e.currentTarget, {
190
- method,
191
- replace,
192
- });
193
- clickedButtonRef.current = null;
198
+ }
199
+ else {
200
+ const eventProxy = formEventProxy(e);
201
+ await (onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, eventProxy));
202
+ if (eventProxy.defaultPrevented) {
203
+ endSubmit({ formAtom });
204
+ return;
194
205
  }
206
+ if (fetcher)
207
+ fetcher.submit(clickedButtonRef.current || e.currentTarget);
208
+ else
209
+ submit(clickedButtonRef.current || e.currentTarget, {
210
+ method,
211
+ replace,
212
+ });
213
+ clickedButtonRef.current = null;
214
+ }
215
+ };
216
+ return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
217
+ e.preventDefault();
218
+ handleSubmit(e);
195
219
  }, onReset: (event) => {
196
220
  onReset === null || onReset === void 0 ? void 0 : onReset(event);
197
221
  if (event.defaultPrevented)
package/browser/hooks.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useMemo } from "react";
2
2
  import { createGetInputProps, } from "./internal/getInputProps";
3
- import { useUnknownFormContextSelectAtom, useInternalFormContext, useFieldTouched, useFieldError, useFieldDefaultValue, useContextSelectAtom, useClearError, useSetTouched, } from "./internal/hooks";
3
+ import { useInternalFormContext, useFieldTouched, useFieldError, useFieldDefaultValue, useContextSelectAtom, useClearError, useSetTouched, } from "./internal/hooks";
4
4
  import { hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, registerReceiveFocusAtom, validateFieldAtom, } from "./internal/state";
5
5
  /**
6
6
  * Returns whether or not the parent form is currently being submitted.
@@ -9,13 +9,19 @@ import { hasBeenSubmittedAtom, isSubmittingAtom, isValidAtom, registerReceiveFoc
9
9
  *
10
10
  * @param formId
11
11
  */
12
- export const useIsSubmitting = (formId) => useUnknownFormContextSelectAtom(formId, isSubmittingAtom, "useIsSubmitting");
12
+ export const useIsSubmitting = (formId) => {
13
+ const formContext = useInternalFormContext(formId, "useIsSubmitting");
14
+ return useContextSelectAtom(formContext.formId, isSubmittingAtom);
15
+ };
13
16
  /**
14
17
  * Returns whether or not the current form is valid.
15
18
  *
16
19
  * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
17
20
  */
18
- export const useIsValid = (formId) => useUnknownFormContextSelectAtom(formId, isValidAtom, "useIsValid");
21
+ export const useIsValid = (formId) => {
22
+ const formContext = useInternalFormContext(formId, "useIsValid");
23
+ return useContextSelectAtom(formContext.formId, isValidAtom);
24
+ };
19
25
  /**
20
26
  * Provides the data and helpers necessary to set up a field.
21
27
  */
@@ -7,7 +7,6 @@ declare type FormSelectorAtomCreator<T> = (formState: FormAtom) => Atom<T>;
7
7
  declare const USE_HYDRATED_STATE: unique symbol;
8
8
  export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
9
9
  export declare const useContextSelectAtom: <T>(formId: string | symbol, selectorAtomCreator: FormSelectorAtomCreator<T>) => T extends Promise<infer V> ? V : T;
10
- export declare const useUnknownFormContextSelectAtom: <T>(formId: string | symbol | undefined, selectorAtomCreator: FormSelectorAtomCreator<T>, hookName: string) => T extends Promise<infer V> ? V : T;
11
10
  export declare const useHydratableSelector: <T, U>({ formId }: InternalFormContextValue, atomCreator: FormSelectorAtomCreator<T>, dataToUse: U | typeof USE_HYDRATED_STATE, selector?: (data: U) => T) => T | (T extends Promise<infer V> ? V : T);
12
11
  export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
13
12
  export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => import("..").FieldErrors | typeof USE_HYDRATED_STATE | undefined;
@@ -20,10 +20,6 @@ export const useContextSelectAtom = (formId, selectorAtomCreator) => {
20
20
  const selectorAtom = useMemo(() => selectorAtomCreator(formAtom), [formAtom, selectorAtomCreator]);
21
21
  return useAtomValue(selectorAtom, ATOM_SCOPE);
22
22
  };
23
- export const useUnknownFormContextSelectAtom = (formId, selectorAtomCreator, hookName) => {
24
- const formContext = useInternalFormContext(formId, hookName);
25
- return useContextSelectAtom(formContext.formId, selectorAtomCreator);
26
- };
27
23
  export const useHydratableSelector = ({ formId }, atomCreator, dataToUse, selector = identity) => {
28
24
  const dataFromState = useContextSelectAtom(formId, atomCreator);
29
25
  return dataToUse === USE_HYDRATED_STATE ? dataFromState : selector(dataToUse);
@@ -10,7 +10,7 @@ export declare type FormProps<DataType> = {
10
10
  * A submit callback that gets called when the form is submitted
11
11
  * after all validations have been run.
12
12
  */
13
- onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => Promise<void>;
13
+ onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void | Promise<void>;
14
14
  /**
15
15
  * Allows you to provide a `fetcher` from remix's `useFetcher` hook.
16
16
  * The form will use the fetcher for loading states, action data, etc
@@ -100,6 +100,22 @@ const FormResetter = ({ resetAfterSubmit, formRef, }) => {
100
100
  });
101
101
  return null;
102
102
  };
103
+ function formEventProxy(event) {
104
+ let defaultPrevented = false;
105
+ return new Proxy(event, {
106
+ get: (target, prop) => {
107
+ if (prop === "preventDefault") {
108
+ return () => {
109
+ defaultPrevented = true;
110
+ };
111
+ }
112
+ if (prop === "defaultPrevented") {
113
+ return defaultPrevented;
114
+ }
115
+ return target[prop];
116
+ },
117
+ });
118
+ }
103
119
  /**
104
120
  * The primary form component of `remix-validated-form`.
105
121
  */
@@ -195,28 +211,36 @@ function ValidatedForm({ validator, onSubmit, children, fetcher, action, default
195
211
  window.removeEventListener("click", handleClick);
196
212
  };
197
213
  }, []);
198
- return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: async (e) => {
199
- e.preventDefault();
200
- startSubmit({ formAtom });
201
- const result = await validator.validate(getDataFromForm(e.currentTarget));
202
- if (result.error) {
203
- endSubmit({ formAtom });
204
- setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
205
- if (!disableFocusOnError) {
206
- focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
207
- }
214
+ const handleSubmit = async (e) => {
215
+ startSubmit({ formAtom });
216
+ const result = await validator.validate(getDataFromForm(e.currentTarget));
217
+ if (result.error) {
218
+ endSubmit({ formAtom });
219
+ setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
220
+ if (!disableFocusOnError) {
221
+ focusFirstInvalidInput(result.error.fieldErrors, customFocusHandlers(), formRef.current);
208
222
  }
209
- else {
210
- onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, e);
211
- if (fetcher)
212
- fetcher.submit(clickedButtonRef.current || e.currentTarget);
213
- else
214
- submit(clickedButtonRef.current || e.currentTarget, {
215
- method,
216
- replace,
217
- });
218
- clickedButtonRef.current = null;
223
+ }
224
+ else {
225
+ const eventProxy = formEventProxy(e);
226
+ await (onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, eventProxy));
227
+ if (eventProxy.defaultPrevented) {
228
+ endSubmit({ formAtom });
229
+ return;
219
230
  }
231
+ if (fetcher)
232
+ fetcher.submit(clickedButtonRef.current || e.currentTarget);
233
+ else
234
+ submit(clickedButtonRef.current || e.currentTarget, {
235
+ method,
236
+ replace,
237
+ });
238
+ clickedButtonRef.current = null;
239
+ }
240
+ };
241
+ return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, id: id, action: action, method: method, replace: replace, onSubmit: (e) => {
242
+ e.preventDefault();
243
+ handleSubmit(e);
220
244
  }, onReset: (event) => {
221
245
  onReset === null || onReset === void 0 ? void 0 : onReset(event);
222
246
  if (event.defaultPrevented)
package/build/hooks.js CHANGED
@@ -12,14 +12,20 @@ const state_1 = require("./internal/state");
12
12
  *
13
13
  * @param formId
14
14
  */
15
- const useIsSubmitting = (formId) => (0, hooks_1.useUnknownFormContextSelectAtom)(formId, state_1.isSubmittingAtom, "useIsSubmitting");
15
+ const useIsSubmitting = (formId) => {
16
+ const formContext = (0, hooks_1.useInternalFormContext)(formId, "useIsSubmitting");
17
+ return (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.isSubmittingAtom);
18
+ };
16
19
  exports.useIsSubmitting = useIsSubmitting;
17
20
  /**
18
21
  * Returns whether or not the current form is valid.
19
22
  *
20
23
  * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
21
24
  */
22
- const useIsValid = (formId) => (0, hooks_1.useUnknownFormContextSelectAtom)(formId, state_1.isValidAtom, "useIsValid");
25
+ const useIsValid = (formId) => {
26
+ const formContext = (0, hooks_1.useInternalFormContext)(formId, "useIsValid");
27
+ return (0, hooks_1.useContextSelectAtom)(formContext.formId, state_1.isValidAtom);
28
+ };
23
29
  exports.useIsValid = useIsValid;
24
30
  /**
25
31
  * Provides the data and helpers necessary to set up a field.
@@ -7,7 +7,6 @@ declare type FormSelectorAtomCreator<T> = (formState: FormAtom) => Atom<T>;
7
7
  declare const USE_HYDRATED_STATE: unique symbol;
8
8
  export declare const useInternalFormContext: (formId?: string | symbol | undefined, hookName?: string | undefined) => InternalFormContextValue;
9
9
  export declare const useContextSelectAtom: <T>(formId: string | symbol, selectorAtomCreator: FormSelectorAtomCreator<T>) => T extends Promise<infer V> ? V : T;
10
- export declare const useUnknownFormContextSelectAtom: <T>(formId: string | symbol | undefined, selectorAtomCreator: FormSelectorAtomCreator<T>, hookName: string) => T extends Promise<infer V> ? V : T;
11
10
  export declare const useHydratableSelector: <T, U>({ formId }: InternalFormContextValue, atomCreator: FormSelectorAtomCreator<T>, dataToUse: U | typeof USE_HYDRATED_STATE, selector?: (data: U) => T) => T | (T extends Promise<infer V> ? V : T);
12
11
  export declare function useErrorResponseForForm({ fetcher, subaction, formId, }: InternalFormContextValue): ValidationErrorResponseData | null;
13
12
  export declare const useFieldErrorsForForm: (context: InternalFormContextValue) => import("..").FieldErrors | typeof USE_HYDRATED_STATE | undefined;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.useSetTouched = exports.useClearError = exports.useFormUpdateAtom = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useHydratableSelector = exports.useUnknownFormContextSelectAtom = exports.useContextSelectAtom = exports.useInternalFormContext = void 0;
6
+ exports.useSetTouched = exports.useClearError = exports.useFormUpdateAtom = exports.useFieldDefaultValue = exports.useFieldError = exports.useFieldTouched = exports.useHasActiveFormSubmit = exports.useDefaultValuesForForm = exports.useDefaultValuesFromLoader = exports.useFieldErrorsForForm = exports.useErrorResponseForForm = exports.useHydratableSelector = exports.useContextSelectAtom = exports.useInternalFormContext = void 0;
7
7
  const react_1 = require("@remix-run/react");
8
8
  const utils_1 = require("jotai/utils");
9
9
  const get_1 = __importDefault(require("lodash/get"));
@@ -28,11 +28,6 @@ const useContextSelectAtom = (formId, selectorAtomCreator) => {
28
28
  return (0, utils_1.useAtomValue)(selectorAtom, state_1.ATOM_SCOPE);
29
29
  };
30
30
  exports.useContextSelectAtom = useContextSelectAtom;
31
- const useUnknownFormContextSelectAtom = (formId, selectorAtomCreator, hookName) => {
32
- const formContext = (0, exports.useInternalFormContext)(formId, hookName);
33
- return (0, exports.useContextSelectAtom)(formContext.formId, selectorAtomCreator);
34
- };
35
- exports.useUnknownFormContextSelectAtom = useUnknownFormContextSelectAtom;
36
31
  const useHydratableSelector = ({ formId }, atomCreator, dataToUse, selector = identity_1.default) => {
37
32
  const dataFromState = (0, exports.useContextSelectAtom)(formId, atomCreator);
38
33
  return dataToUse === USE_HYDRATED_STATE ? dataFromState : selector(dataToUse);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remix-validated-form",
3
- "version": "4.1.0-beta.1",
3
+ "version": "4.1.0-beta.2",
4
4
  "description": "Form component and utils for easy form validation in remix",
5
5
  "browser": "./browser/index.js",
6
6
  "main": "./build/index.js",
@@ -2,6 +2,7 @@ import { Form as RemixForm, useFetcher, useSubmit } from "@remix-run/react";
2
2
  import uniq from "lodash/uniq";
3
3
  import React, {
4
4
  ComponentProps,
5
+ FormEvent,
5
6
  RefObject,
6
7
  useCallback,
7
8
  useEffect,
@@ -53,7 +54,7 @@ export type FormProps<DataType> = {
53
54
  onSubmit?: (
54
55
  data: DataType,
55
56
  event: React.FormEvent<HTMLFormElement>
56
- ) => Promise<void>;
57
+ ) => void | Promise<void>;
57
58
  /**
58
59
  * Allows you to provide a `fetcher` from remix's `useFetcher` hook.
59
60
  * The form will use the fetcher for loading states, action data, etc
@@ -171,6 +172,25 @@ const FormResetter = ({
171
172
  return null;
172
173
  };
173
174
 
175
+ function formEventProxy<T extends object>(event: T): T {
176
+ let defaultPrevented = false;
177
+ return new Proxy(event, {
178
+ get: (target, prop) => {
179
+ if (prop === "preventDefault") {
180
+ return () => {
181
+ defaultPrevented = true;
182
+ };
183
+ }
184
+
185
+ if (prop === "defaultPrevented") {
186
+ return defaultPrevented;
187
+ }
188
+
189
+ return target[prop as keyof T];
190
+ },
191
+ }) as T;
192
+ }
193
+
174
194
  /**
175
195
  * The primary form component of `remix-validated-form`.
176
196
  */
@@ -305,6 +325,37 @@ export function ValidatedForm<DataType>({
305
325
  };
306
326
  }, []);
307
327
 
328
+ const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
329
+ startSubmit({ formAtom });
330
+ const result = await validator.validate(getDataFromForm(e.currentTarget));
331
+ if (result.error) {
332
+ endSubmit({ formAtom });
333
+ setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
334
+ if (!disableFocusOnError) {
335
+ focusFirstInvalidInput(
336
+ result.error.fieldErrors,
337
+ customFocusHandlers(),
338
+ formRef.current!
339
+ );
340
+ }
341
+ } else {
342
+ const eventProxy = formEventProxy(e);
343
+ await onSubmit?.(result.data, eventProxy);
344
+ if (eventProxy.defaultPrevented) {
345
+ endSubmit({ formAtom });
346
+ return;
347
+ }
348
+
349
+ if (fetcher) fetcher.submit(clickedButtonRef.current || e.currentTarget);
350
+ else
351
+ submit(clickedButtonRef.current || e.currentTarget, {
352
+ method,
353
+ replace,
354
+ });
355
+ clickedButtonRef.current = null;
356
+ }
357
+ };
358
+
308
359
  return (
309
360
  <Form
310
361
  ref={mergeRefs([formRef, formRefProp])}
@@ -313,33 +364,9 @@ export function ValidatedForm<DataType>({
313
364
  action={action}
314
365
  method={method}
315
366
  replace={replace}
316
- onSubmit={async (e) => {
367
+ onSubmit={(e) => {
317
368
  e.preventDefault();
318
- startSubmit({ formAtom });
319
- const result = await validator.validate(
320
- getDataFromForm(e.currentTarget)
321
- );
322
- if (result.error) {
323
- endSubmit({ formAtom });
324
- setFieldErrors({ fieldErrors: result.error.fieldErrors, formAtom });
325
- if (!disableFocusOnError) {
326
- focusFirstInvalidInput(
327
- result.error.fieldErrors,
328
- customFocusHandlers(),
329
- formRef.current!
330
- );
331
- }
332
- } else {
333
- onSubmit?.(result.data, e);
334
- if (fetcher)
335
- fetcher.submit(clickedButtonRef.current || e.currentTarget);
336
- else
337
- submit(clickedButtonRef.current || e.currentTarget, {
338
- method,
339
- replace,
340
- });
341
- clickedButtonRef.current = null;
342
- }
369
+ handleSubmit(e);
343
370
  }}
344
371
  onReset={(event) => {
345
372
  onReset?.(event);
package/src/hooks.ts CHANGED
@@ -5,7 +5,6 @@ import {
5
5
  ValidationBehaviorOptions,
6
6
  } from "./internal/getInputProps";
7
7
  import {
8
- useUnknownFormContextSelectAtom,
9
8
  useInternalFormContext,
10
9
  useFieldTouched,
11
10
  useFieldError,
@@ -29,16 +28,20 @@ import {
29
28
  *
30
29
  * @param formId
31
30
  */
32
- export const useIsSubmitting = (formId?: string) =>
33
- useUnknownFormContextSelectAtom(formId, isSubmittingAtom, "useIsSubmitting");
31
+ export const useIsSubmitting = (formId?: string) => {
32
+ const formContext = useInternalFormContext(formId, "useIsSubmitting");
33
+ return useContextSelectAtom(formContext.formId, isSubmittingAtom);
34
+ };
34
35
 
35
36
  /**
36
37
  * Returns whether or not the current form is valid.
37
38
  *
38
39
  * @param formId the id of the form. Only necessary if being used outside a ValidatedForm.
39
40
  */
40
- export const useIsValid = (formId?: string) =>
41
- useUnknownFormContextSelectAtom(formId, isValidAtom, "useIsValid");
41
+ export const useIsValid = (formId?: string) => {
42
+ const formContext = useInternalFormContext(formId, "useIsValid");
43
+ return useContextSelectAtom(formContext.formId, isValidAtom);
44
+ };
42
45
 
43
46
  export type FieldProps = {
44
47
  /**
@@ -48,15 +48,6 @@ export const useContextSelectAtom = <T>(
48
48
  return useAtomValue(selectorAtom, ATOM_SCOPE);
49
49
  };
50
50
 
51
- export const useUnknownFormContextSelectAtom = <T>(
52
- formId: string | symbol | undefined,
53
- selectorAtomCreator: FormSelectorAtomCreator<T>,
54
- hookName: string
55
- ) => {
56
- const formContext = useInternalFormContext(formId, hookName);
57
- return useContextSelectAtom(formContext.formId, selectorAtomCreator);
58
- };
59
-
60
51
  export const useHydratableSelector = <T, U>(
61
52
  { formId }: InternalFormContextValue,
62
53
  atomCreator: FormSelectorAtomCreator<T>,