remix-validated-form 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. package/browser/ValidatedForm.d.ts +11 -0
  2. package/browser/ValidatedForm.js +69 -0
  3. package/browser/hooks.d.ts +8 -0
  4. package/browser/hooks.js +17 -0
  5. package/{src/index.ts → browser/index.d.ts} +0 -0
  6. package/browser/index.js +5 -0
  7. package/browser/internal/formContext.d.ts +13 -0
  8. package/browser/internal/formContext.js +7 -0
  9. package/browser/internal/util.d.ts +3 -0
  10. package/browser/internal/util.js +19 -0
  11. package/browser/server.d.ts +2 -0
  12. package/browser/server.js +2 -0
  13. package/browser/validation/types.d.ts +15 -0
  14. package/browser/validation/types.js +1 -0
  15. package/browser/validation/validation.test.d.ts +1 -0
  16. package/browser/validation/validation.test.js +56 -0
  17. package/browser/validation/withYup.d.ts +3 -0
  18. package/browser/validation/withYup.js +34 -0
  19. package/build/ValidatedForm.d.ts +11 -0
  20. package/build/ValidatedForm.js +76 -0
  21. package/build/hooks.d.ts +8 -0
  22. package/build/hooks.js +23 -0
  23. package/build/index.d.ts +5 -0
  24. package/build/index.js +17 -0
  25. package/build/internal/formContext.d.ts +13 -0
  26. package/build/internal/formContext.js +10 -0
  27. package/build/internal/util.d.ts +3 -0
  28. package/build/internal/util.js +24 -0
  29. package/build/server.d.ts +2 -0
  30. package/build/server.js +6 -0
  31. package/build/validation/types.d.ts +15 -0
  32. package/build/validation/types.js +2 -0
  33. package/build/validation/validation.test.d.ts +1 -0
  34. package/build/validation/validation.test.js +77 -0
  35. package/build/validation/withYup.d.ts +3 -0
  36. package/build/validation/withYup.js +38 -0
  37. package/package.json +5 -1
  38. package/.eslintrc.js +0 -46
  39. package/.github/workflows/test.yml +0 -35
  40. package/.husky/pre-commit +0 -4
  41. package/src/ValidatedForm.tsx +0 -130
  42. package/src/hooks.ts +0 -27
  43. package/src/internal/formContext.ts +0 -18
  44. package/src/internal/util.ts +0 -23
  45. package/src/server.ts +0 -5
  46. package/src/validation/types.ts +0 -12
  47. package/src/validation/validation.test.ts +0 -76
  48. package/src/validation/withYup.ts +0 -37
  49. package/test-app/README.md +0 -53
  50. package/test-app/app/components/Input.tsx +0 -24
  51. package/test-app/app/components/SubmitButton.tsx +0 -18
  52. package/test-app/app/entry.client.tsx +0 -4
  53. package/test-app/app/entry.server.tsx +0 -21
  54. package/test-app/app/root.tsx +0 -246
  55. package/test-app/app/routes/default-values.tsx +0 -34
  56. package/test-app/app/routes/index.tsx +0 -100
  57. package/test-app/app/routes/noscript.tsx +0 -10
  58. package/test-app/app/routes/submission.alt.tsx +0 -6
  59. package/test-app/app/routes/submission.fetcher.tsx +0 -6
  60. package/test-app/app/routes/submission.tsx +0 -47
  61. package/test-app/app/routes/validation.tsx +0 -40
  62. package/test-app/app/styles/dark.css +0 -7
  63. package/test-app/app/styles/demos/about.css +0 -26
  64. package/test-app/app/styles/demos/remix.css +0 -120
  65. package/test-app/app/styles/global.css +0 -98
  66. package/test-app/cypress/fixtures/example.json +0 -5
  67. package/test-app/cypress/integration/default-values.ts +0 -15
  68. package/test-app/cypress/integration/sanity.ts +0 -19
  69. package/test-app/cypress/integration/submission.ts +0 -26
  70. package/test-app/cypress/integration/validation.ts +0 -70
  71. package/test-app/cypress/plugins/config.ts +0 -38
  72. package/test-app/cypress/plugins/index.ts +0 -9
  73. package/test-app/cypress/support/commands/index.ts +0 -13
  74. package/test-app/cypress/support/commands/types.d.ts +0 -11
  75. package/test-app/cypress/support/index.ts +0 -20
  76. package/test-app/cypress/tsconfig.json +0 -11
  77. package/test-app/cypress.json +0 -3
  78. package/test-app/package-lock.json +0 -11675
  79. package/test-app/package.json +0 -40
  80. package/test-app/public/favicon.ico +0 -0
  81. package/test-app/remix.config.js +0 -10
  82. package/test-app/remix.env.d.ts +0 -2
  83. package/test-app/tsconfig.json +0 -18
  84. package/tsconfig.json +0 -15
@@ -0,0 +1,11 @@
1
+ import { Form as RemixForm, useFetcher } from "@remix-run/react";
2
+ import React, { ComponentProps } from "react";
3
+ import { Validator } from "./validation/types";
4
+ export declare type FormProps<DataType> = {
5
+ validator: Validator<DataType>;
6
+ onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void;
7
+ fetcher?: ReturnType<typeof useFetcher>;
8
+ defaultValues?: Partial<DataType>;
9
+ formRef?: React.RefObject<HTMLFormElement>;
10
+ } & Omit<ComponentProps<typeof RemixForm>, "onSubmit">;
11
+ export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, ...rest }: FormProps<DataType>): JSX.Element;
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Form as RemixForm, useActionData, useFormAction, useTransition, } from "@remix-run/react";
3
+ import { useEffect, useMemo, useRef, useState, } from "react";
4
+ import invariant from "tiny-invariant";
5
+ import { FormContext } from "./internal/formContext";
6
+ import { omit, mergeRefs } from "./internal/util";
7
+ function useFieldErrors(fetcher) {
8
+ const actionData = useActionData();
9
+ const dataToUse = fetcher ? fetcher.data : actionData;
10
+ const fieldErrorsFromAction = dataToUse === null || dataToUse === void 0 ? void 0 : dataToUse.fieldErrors;
11
+ const [fieldErrors, setFieldErrors] = useState(fieldErrorsFromAction !== null && fieldErrorsFromAction !== void 0 ? fieldErrorsFromAction : {});
12
+ useEffect(() => {
13
+ if (fieldErrorsFromAction)
14
+ setFieldErrors(fieldErrorsFromAction);
15
+ }, [fieldErrorsFromAction]);
16
+ return [fieldErrors, setFieldErrors];
17
+ }
18
+ const useIsSubmitting = (action, fetcher) => {
19
+ const actionForCurrentPage = useFormAction();
20
+ const pendingFormSubmit = useTransition().submission;
21
+ return fetcher
22
+ ? fetcher.state === "submitting"
23
+ : pendingFormSubmit &&
24
+ pendingFormSubmit.action.endsWith(action !== null && action !== void 0 ? action : actionForCurrentPage);
25
+ };
26
+ const getDataFromForm = (el) => Object.fromEntries(new FormData(el));
27
+ export function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, ...rest }) {
28
+ var _a;
29
+ const [fieldErrors, setFieldErrors] = useFieldErrors(fetcher);
30
+ const isSubmitting = useIsSubmitting(action, fetcher);
31
+ const formRef = useRef(null);
32
+ const contextValue = useMemo(() => ({
33
+ fieldErrors,
34
+ action,
35
+ defaultValues,
36
+ isSubmitting: isSubmitting !== null && isSubmitting !== void 0 ? isSubmitting : false,
37
+ clearError: (fieldName) => {
38
+ setFieldErrors((prev) => omit(prev, fieldName));
39
+ },
40
+ validateField: (fieldName) => {
41
+ invariant(formRef.current, "Cannot find reference to form");
42
+ const { error } = validator.validateField(getDataFromForm(formRef.current), fieldName);
43
+ if (error) {
44
+ setFieldErrors((prev) => ({
45
+ ...prev,
46
+ [fieldName]: error,
47
+ }));
48
+ }
49
+ },
50
+ }), [
51
+ fieldErrors,
52
+ action,
53
+ defaultValues,
54
+ isSubmitting,
55
+ setFieldErrors,
56
+ validator,
57
+ ]);
58
+ const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : RemixForm;
59
+ return (_jsx(Form, { ref: mergeRefs([formRef, formRefProp]), ...rest, action: action, onSubmit: (event) => {
60
+ const result = validator.validate(getDataFromForm(event.currentTarget));
61
+ if (result.error) {
62
+ event.preventDefault();
63
+ setFieldErrors(result.error);
64
+ }
65
+ else {
66
+ onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, event);
67
+ }
68
+ }, children: _jsx(FormContext.Provider, { value: contextValue, children: children }, void 0) }, void 0));
69
+ }
@@ -0,0 +1,8 @@
1
+ export declare const useField: (name: string) => {
2
+ error: string;
3
+ clearError: () => void;
4
+ validate: () => void;
5
+ defaultValue: any;
6
+ };
7
+ export declare const useFormContext: () => import("./internal/formContext").FormContextValue;
8
+ export declare const useIsSubmitting: () => boolean;
@@ -0,0 +1,17 @@
1
+ import { useContext, useMemo } from "react";
2
+ import { FormContext } from "./internal/formContext";
3
+ export const useField = (name) => {
4
+ const { fieldErrors, clearError, validateField, defaultValues } = useContext(FormContext);
5
+ const field = useMemo(() => ({
6
+ error: fieldErrors[name],
7
+ clearError: () => {
8
+ clearError(name);
9
+ },
10
+ validate: () => validateField(name),
11
+ defaultValue: defaultValues === null || defaultValues === void 0 ? void 0 : defaultValues[name],
12
+ }), [clearError, defaultValues, fieldErrors, name, validateField]);
13
+ return field;
14
+ };
15
+ // test commit
16
+ export const useFormContext = () => useContext(FormContext);
17
+ export const useIsSubmitting = () => useFormContext().isSubmitting;
File without changes
@@ -0,0 +1,5 @@
1
+ export * from "./hooks";
2
+ export * from "./server";
3
+ export * from "./ValidatedForm";
4
+ export * from "./validation/types";
5
+ export * from "./validation/withYup";
@@ -0,0 +1,13 @@
1
+ /// <reference types="react" />
2
+ import { FieldErrors } from "../validation/types";
3
+ export declare type FormContextValue = {
4
+ fieldErrors: FieldErrors;
5
+ clearError: (...names: string[]) => void;
6
+ validateField: (fieldName: string) => void;
7
+ action?: string;
8
+ isSubmitting: boolean;
9
+ defaultValues?: {
10
+ [fieldName: string]: any;
11
+ };
12
+ };
13
+ export declare const FormContext: import("react").Context<FormContextValue>;
@@ -0,0 +1,7 @@
1
+ import { createContext } from "react";
2
+ export const FormContext = createContext({
3
+ fieldErrors: {},
4
+ clearError: () => { },
5
+ validateField: () => { },
6
+ isSubmitting: false,
7
+ });
@@ -0,0 +1,3 @@
1
+ import type React from "react";
2
+ export declare const omit: (obj: any, ...keys: string[]) => any;
3
+ export declare const mergeRefs: <T = any>(refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined)[]) => (instance: T | null) => void;
@@ -0,0 +1,19 @@
1
+ export const omit = (obj, ...keys) => {
2
+ const result = { ...obj };
3
+ for (const key of keys) {
4
+ delete result[key];
5
+ }
6
+ return result;
7
+ };
8
+ export const mergeRefs = (refs) => {
9
+ return (value) => {
10
+ refs.filter(Boolean).forEach((ref) => {
11
+ if (typeof ref === "function") {
12
+ ref(value);
13
+ }
14
+ else if (ref != null) {
15
+ ref.current = value;
16
+ }
17
+ });
18
+ };
19
+ };
@@ -0,0 +1,2 @@
1
+ import { FieldErrors } from "./validation/types";
2
+ export declare const fieldErrors: (errors: FieldErrors) => Response;
@@ -0,0 +1,2 @@
1
+ import { json } from "@remix-run/server-runtime";
2
+ export const fieldErrors = (errors) => json({ fieldErrors: errors }, { status: 422 });
@@ -0,0 +1,15 @@
1
+ export declare type FieldErrors = Record<string, string>;
2
+ export declare type ValidationResult<DataType> = {
3
+ data: DataType;
4
+ error: undefined;
5
+ } | {
6
+ error: FieldErrors;
7
+ data: undefined;
8
+ };
9
+ export declare type ValidateFieldResult = {
10
+ error?: string;
11
+ };
12
+ export declare type Validator<DataType> = {
13
+ validate: (formData: unknown) => ValidationResult<DataType>;
14
+ validateField: (formData: unknown, field: string) => ValidateFieldResult;
15
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import * as yup from "yup";
2
+ import { withYup } from "..";
3
+ const validationTestCases = [
4
+ {
5
+ name: "yup",
6
+ validator: withYup(yup.object({
7
+ firstName: yup.string().required(),
8
+ lastName: yup.string().required(),
9
+ age: yup.number(),
10
+ })),
11
+ },
12
+ ];
13
+ // Not going to enforce exact error strings here
14
+ const anyString = expect.any(String);
15
+ describe("Validation", () => {
16
+ describe.each(validationTestCases)("Adapter for $name", ({ validator }) => {
17
+ describe("validate", () => {
18
+ it("should return the data when valid", () => {
19
+ const obj = {
20
+ firstName: "John",
21
+ lastName: "Doe",
22
+ age: 30,
23
+ };
24
+ expect(validator.validate(obj)).toEqual({
25
+ data: obj,
26
+ error: undefined,
27
+ });
28
+ });
29
+ it("should return field errors when invalid", () => {
30
+ const obj = { age: "hi!" };
31
+ expect(validator.validate(obj)).toEqual({
32
+ data: undefined,
33
+ error: {
34
+ firstName: anyString,
35
+ lastName: anyString,
36
+ age: anyString,
37
+ },
38
+ });
39
+ });
40
+ });
41
+ describe("validateField", () => {
42
+ it("should not return an error if field is valid", () => {
43
+ const obj = { firstName: "John", lastName: 123 };
44
+ expect(validator.validateField(obj, "firstName")).toEqual({
45
+ error: undefined,
46
+ });
47
+ });
48
+ it("should return an error if field is invalid", () => {
49
+ const obj = { firstName: "John", lastName: {} };
50
+ expect(validator.validateField(obj, "lastName")).toEqual({
51
+ error: anyString,
52
+ });
53
+ });
54
+ });
55
+ });
56
+ });
@@ -0,0 +1,3 @@
1
+ import type { AnyObjectSchema, InferType } from "yup";
2
+ import { Validator } from "./types";
3
+ export declare const withYup: <Schema extends AnyObjectSchema>(validationSchema: Schema) => Validator<InferType<Schema>>;
@@ -0,0 +1,34 @@
1
+ const validationErrorToFieldErrors = (error) => {
2
+ const fieldErrors = {};
3
+ error.inner.forEach((innerError) => {
4
+ if (!innerError.path)
5
+ return;
6
+ fieldErrors[innerError.path] = innerError.message;
7
+ });
8
+ return fieldErrors;
9
+ };
10
+ export const withYup = (validationSchema) => ({
11
+ validate: (data) => {
12
+ try {
13
+ const validated = validationSchema.validateSync(data, {
14
+ abortEarly: false,
15
+ });
16
+ return { data: validated, error: undefined };
17
+ }
18
+ catch (err) {
19
+ return {
20
+ error: validationErrorToFieldErrors(err),
21
+ data: undefined,
22
+ };
23
+ }
24
+ },
25
+ validateField: (data, field) => {
26
+ try {
27
+ validationSchema.validateSyncAt(field, data);
28
+ return {};
29
+ }
30
+ catch (err) {
31
+ return { error: err.message };
32
+ }
33
+ },
34
+ });
@@ -0,0 +1,11 @@
1
+ import { Form as RemixForm, useFetcher } from "@remix-run/react";
2
+ import React, { ComponentProps } from "react";
3
+ import { Validator } from "./validation/types";
4
+ export declare type FormProps<DataType> = {
5
+ validator: Validator<DataType>;
6
+ onSubmit?: (data: DataType, event: React.FormEvent<HTMLFormElement>) => void;
7
+ fetcher?: ReturnType<typeof useFetcher>;
8
+ defaultValues?: Partial<DataType>;
9
+ formRef?: React.RefObject<HTMLFormElement>;
10
+ } & Omit<ComponentProps<typeof RemixForm>, "onSubmit">;
11
+ export declare function ValidatedForm<DataType>({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, ...rest }: FormProps<DataType>): JSX.Element;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ValidatedForm = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("@remix-run/react");
9
+ const react_2 = require("react");
10
+ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
11
+ const formContext_1 = require("./internal/formContext");
12
+ const util_1 = require("./internal/util");
13
+ function useFieldErrors(fetcher) {
14
+ const actionData = (0, react_1.useActionData)();
15
+ const dataToUse = fetcher ? fetcher.data : actionData;
16
+ const fieldErrorsFromAction = dataToUse === null || dataToUse === void 0 ? void 0 : dataToUse.fieldErrors;
17
+ const [fieldErrors, setFieldErrors] = (0, react_2.useState)(fieldErrorsFromAction !== null && fieldErrorsFromAction !== void 0 ? fieldErrorsFromAction : {});
18
+ (0, react_2.useEffect)(() => {
19
+ if (fieldErrorsFromAction)
20
+ setFieldErrors(fieldErrorsFromAction);
21
+ }, [fieldErrorsFromAction]);
22
+ return [fieldErrors, setFieldErrors];
23
+ }
24
+ const useIsSubmitting = (action, fetcher) => {
25
+ const actionForCurrentPage = (0, react_1.useFormAction)();
26
+ const pendingFormSubmit = (0, react_1.useTransition)().submission;
27
+ return fetcher
28
+ ? fetcher.state === "submitting"
29
+ : pendingFormSubmit &&
30
+ pendingFormSubmit.action.endsWith(action !== null && action !== void 0 ? action : actionForCurrentPage);
31
+ };
32
+ const getDataFromForm = (el) => Object.fromEntries(new FormData(el));
33
+ function ValidatedForm({ validator, onSubmit, children, fetcher, action, defaultValues, formRef: formRefProp, ...rest }) {
34
+ var _a;
35
+ const [fieldErrors, setFieldErrors] = useFieldErrors(fetcher);
36
+ const isSubmitting = useIsSubmitting(action, fetcher);
37
+ const formRef = (0, react_2.useRef)(null);
38
+ const contextValue = (0, react_2.useMemo)(() => ({
39
+ fieldErrors,
40
+ action,
41
+ defaultValues,
42
+ isSubmitting: isSubmitting !== null && isSubmitting !== void 0 ? isSubmitting : false,
43
+ clearError: (fieldName) => {
44
+ setFieldErrors((prev) => (0, util_1.omit)(prev, fieldName));
45
+ },
46
+ validateField: (fieldName) => {
47
+ (0, tiny_invariant_1.default)(formRef.current, "Cannot find reference to form");
48
+ const { error } = validator.validateField(getDataFromForm(formRef.current), fieldName);
49
+ if (error) {
50
+ setFieldErrors((prev) => ({
51
+ ...prev,
52
+ [fieldName]: error,
53
+ }));
54
+ }
55
+ },
56
+ }), [
57
+ fieldErrors,
58
+ action,
59
+ defaultValues,
60
+ isSubmitting,
61
+ setFieldErrors,
62
+ validator,
63
+ ]);
64
+ const Form = (_a = fetcher === null || fetcher === void 0 ? void 0 : fetcher.Form) !== null && _a !== void 0 ? _a : react_1.Form;
65
+ return ((0, jsx_runtime_1.jsx)(Form, { ref: (0, util_1.mergeRefs)([formRef, formRefProp]), ...rest, action: action, onSubmit: (event) => {
66
+ const result = validator.validate(getDataFromForm(event.currentTarget));
67
+ if (result.error) {
68
+ event.preventDefault();
69
+ setFieldErrors(result.error);
70
+ }
71
+ else {
72
+ onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(result.data, event);
73
+ }
74
+ }, children: (0, jsx_runtime_1.jsx)(formContext_1.FormContext.Provider, { value: contextValue, children: children }, void 0) }, void 0));
75
+ }
76
+ exports.ValidatedForm = ValidatedForm;
@@ -0,0 +1,8 @@
1
+ export declare const useField: (name: string) => {
2
+ error: string;
3
+ clearError: () => void;
4
+ validate: () => void;
5
+ defaultValue: any;
6
+ };
7
+ export declare const useFormContext: () => import("./internal/formContext").FormContextValue;
8
+ export declare const useIsSubmitting: () => boolean;
package/build/hooks.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useIsSubmitting = exports.useFormContext = exports.useField = void 0;
4
+ const react_1 = require("react");
5
+ const formContext_1 = require("./internal/formContext");
6
+ const useField = (name) => {
7
+ const { fieldErrors, clearError, validateField, defaultValues } = (0, react_1.useContext)(formContext_1.FormContext);
8
+ const field = (0, react_1.useMemo)(() => ({
9
+ error: fieldErrors[name],
10
+ clearError: () => {
11
+ clearError(name);
12
+ },
13
+ validate: () => validateField(name),
14
+ defaultValue: defaultValues === null || defaultValues === void 0 ? void 0 : defaultValues[name],
15
+ }), [clearError, defaultValues, fieldErrors, name, validateField]);
16
+ return field;
17
+ };
18
+ exports.useField = useField;
19
+ // test commit
20
+ const useFormContext = () => (0, react_1.useContext)(formContext_1.FormContext);
21
+ exports.useFormContext = useFormContext;
22
+ const useIsSubmitting = () => (0, exports.useFormContext)().isSubmitting;
23
+ exports.useIsSubmitting = useIsSubmitting;
@@ -0,0 +1,5 @@
1
+ export * from "./hooks";
2
+ export * from "./server";
3
+ export * from "./ValidatedForm";
4
+ export * from "./validation/types";
5
+ export * from "./validation/withYup";
package/build/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./hooks"), exports);
14
+ __exportStar(require("./server"), exports);
15
+ __exportStar(require("./ValidatedForm"), exports);
16
+ __exportStar(require("./validation/types"), exports);
17
+ __exportStar(require("./validation/withYup"), exports);
@@ -0,0 +1,13 @@
1
+ /// <reference types="react" />
2
+ import { FieldErrors } from "../validation/types";
3
+ export declare type FormContextValue = {
4
+ fieldErrors: FieldErrors;
5
+ clearError: (...names: string[]) => void;
6
+ validateField: (fieldName: string) => void;
7
+ action?: string;
8
+ isSubmitting: boolean;
9
+ defaultValues?: {
10
+ [fieldName: string]: any;
11
+ };
12
+ };
13
+ export declare const FormContext: import("react").Context<FormContextValue>;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FormContext = void 0;
4
+ const react_1 = require("react");
5
+ exports.FormContext = (0, react_1.createContext)({
6
+ fieldErrors: {},
7
+ clearError: () => { },
8
+ validateField: () => { },
9
+ isSubmitting: false,
10
+ });
@@ -0,0 +1,3 @@
1
+ import type React from "react";
2
+ export declare const omit: (obj: any, ...keys: string[]) => any;
3
+ export declare const mergeRefs: <T = any>(refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined)[]) => (instance: T | null) => void;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeRefs = exports.omit = void 0;
4
+ const omit = (obj, ...keys) => {
5
+ const result = { ...obj };
6
+ for (const key of keys) {
7
+ delete result[key];
8
+ }
9
+ return result;
10
+ };
11
+ exports.omit = omit;
12
+ const mergeRefs = (refs) => {
13
+ return (value) => {
14
+ refs.filter(Boolean).forEach((ref) => {
15
+ if (typeof ref === "function") {
16
+ ref(value);
17
+ }
18
+ else if (ref != null) {
19
+ ref.current = value;
20
+ }
21
+ });
22
+ };
23
+ };
24
+ exports.mergeRefs = mergeRefs;
@@ -0,0 +1,2 @@
1
+ import { FieldErrors } from "./validation/types";
2
+ export declare const fieldErrors: (errors: FieldErrors) => Response;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fieldErrors = void 0;
4
+ const server_runtime_1 = require("@remix-run/server-runtime");
5
+ const fieldErrors = (errors) => (0, server_runtime_1.json)({ fieldErrors: errors }, { status: 422 });
6
+ exports.fieldErrors = fieldErrors;
@@ -0,0 +1,15 @@
1
+ export declare type FieldErrors = Record<string, string>;
2
+ export declare type ValidationResult<DataType> = {
3
+ data: DataType;
4
+ error: undefined;
5
+ } | {
6
+ error: FieldErrors;
7
+ data: undefined;
8
+ };
9
+ export declare type ValidateFieldResult = {
10
+ error?: string;
11
+ };
12
+ export declare type Validator<DataType> = {
13
+ validate: (formData: unknown) => ValidationResult<DataType>;
14
+ validateField: (formData: unknown, field: string) => ValidateFieldResult;
15
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ const yup = __importStar(require("yup"));
23
+ const __1 = require("..");
24
+ const validationTestCases = [
25
+ {
26
+ name: "yup",
27
+ validator: (0, __1.withYup)(yup.object({
28
+ firstName: yup.string().required(),
29
+ lastName: yup.string().required(),
30
+ age: yup.number(),
31
+ })),
32
+ },
33
+ ];
34
+ // Not going to enforce exact error strings here
35
+ const anyString = expect.any(String);
36
+ describe("Validation", () => {
37
+ describe.each(validationTestCases)("Adapter for $name", ({ validator }) => {
38
+ describe("validate", () => {
39
+ it("should return the data when valid", () => {
40
+ const obj = {
41
+ firstName: "John",
42
+ lastName: "Doe",
43
+ age: 30,
44
+ };
45
+ expect(validator.validate(obj)).toEqual({
46
+ data: obj,
47
+ error: undefined,
48
+ });
49
+ });
50
+ it("should return field errors when invalid", () => {
51
+ const obj = { age: "hi!" };
52
+ expect(validator.validate(obj)).toEqual({
53
+ data: undefined,
54
+ error: {
55
+ firstName: anyString,
56
+ lastName: anyString,
57
+ age: anyString,
58
+ },
59
+ });
60
+ });
61
+ });
62
+ describe("validateField", () => {
63
+ it("should not return an error if field is valid", () => {
64
+ const obj = { firstName: "John", lastName: 123 };
65
+ expect(validator.validateField(obj, "firstName")).toEqual({
66
+ error: undefined,
67
+ });
68
+ });
69
+ it("should return an error if field is invalid", () => {
70
+ const obj = { firstName: "John", lastName: {} };
71
+ expect(validator.validateField(obj, "lastName")).toEqual({
72
+ error: anyString,
73
+ });
74
+ });
75
+ });
76
+ });
77
+ });
@@ -0,0 +1,3 @@
1
+ import type { AnyObjectSchema, InferType } from "yup";
2
+ import { Validator } from "./types";
3
+ export declare const withYup: <Schema extends AnyObjectSchema>(validationSchema: Schema) => Validator<InferType<Schema>>;