remix-validated-form 0.0.2 → 0.0.3

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 (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>>;