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.
- package/browser/ValidatedForm.d.ts +11 -0
- package/browser/ValidatedForm.js +69 -0
- package/browser/hooks.d.ts +8 -0
- package/browser/hooks.js +17 -0
- package/{src/index.ts → browser/index.d.ts} +0 -0
- package/browser/index.js +5 -0
- package/browser/internal/formContext.d.ts +13 -0
- package/browser/internal/formContext.js +7 -0
- package/browser/internal/util.d.ts +3 -0
- package/browser/internal/util.js +19 -0
- package/browser/server.d.ts +2 -0
- package/browser/server.js +2 -0
- package/browser/validation/types.d.ts +15 -0
- package/browser/validation/types.js +1 -0
- package/browser/validation/validation.test.d.ts +1 -0
- package/browser/validation/validation.test.js +56 -0
- package/browser/validation/withYup.d.ts +3 -0
- package/browser/validation/withYup.js +34 -0
- package/build/ValidatedForm.d.ts +11 -0
- package/build/ValidatedForm.js +76 -0
- package/build/hooks.d.ts +8 -0
- package/build/hooks.js +23 -0
- package/build/index.d.ts +5 -0
- package/build/index.js +17 -0
- package/build/internal/formContext.d.ts +13 -0
- package/build/internal/formContext.js +10 -0
- package/build/internal/util.d.ts +3 -0
- package/build/internal/util.js +24 -0
- package/build/server.d.ts +2 -0
- package/build/server.js +6 -0
- package/build/validation/types.d.ts +15 -0
- package/build/validation/types.js +2 -0
- package/build/validation/validation.test.d.ts +1 -0
- package/build/validation/validation.test.js +77 -0
- package/build/validation/withYup.d.ts +3 -0
- package/build/validation/withYup.js +38 -0
- package/package.json +5 -1
- package/.eslintrc.js +0 -46
- package/.github/workflows/test.yml +0 -35
- package/.husky/pre-commit +0 -4
- package/src/ValidatedForm.tsx +0 -130
- package/src/hooks.ts +0 -27
- package/src/internal/formContext.ts +0 -18
- package/src/internal/util.ts +0 -23
- package/src/server.ts +0 -5
- package/src/validation/types.ts +0 -12
- package/src/validation/validation.test.ts +0 -76
- package/src/validation/withYup.ts +0 -37
- package/test-app/README.md +0 -53
- package/test-app/app/components/Input.tsx +0 -24
- package/test-app/app/components/SubmitButton.tsx +0 -18
- package/test-app/app/entry.client.tsx +0 -4
- package/test-app/app/entry.server.tsx +0 -21
- package/test-app/app/root.tsx +0 -246
- package/test-app/app/routes/default-values.tsx +0 -34
- package/test-app/app/routes/index.tsx +0 -100
- package/test-app/app/routes/noscript.tsx +0 -10
- package/test-app/app/routes/submission.alt.tsx +0 -6
- package/test-app/app/routes/submission.fetcher.tsx +0 -6
- package/test-app/app/routes/submission.tsx +0 -47
- package/test-app/app/routes/validation.tsx +0 -40
- package/test-app/app/styles/dark.css +0 -7
- package/test-app/app/styles/demos/about.css +0 -26
- package/test-app/app/styles/demos/remix.css +0 -120
- package/test-app/app/styles/global.css +0 -98
- package/test-app/cypress/fixtures/example.json +0 -5
- package/test-app/cypress/integration/default-values.ts +0 -15
- package/test-app/cypress/integration/sanity.ts +0 -19
- package/test-app/cypress/integration/submission.ts +0 -26
- package/test-app/cypress/integration/validation.ts +0 -70
- package/test-app/cypress/plugins/config.ts +0 -38
- package/test-app/cypress/plugins/index.ts +0 -9
- package/test-app/cypress/support/commands/index.ts +0 -13
- package/test-app/cypress/support/commands/types.d.ts +0 -11
- package/test-app/cypress/support/index.ts +0 -20
- package/test-app/cypress/tsconfig.json +0 -11
- package/test-app/cypress.json +0 -3
- package/test-app/package-lock.json +0 -11675
- package/test-app/package.json +0 -40
- package/test-app/public/favicon.ico +0 -0
- package/test-app/remix.config.js +0 -10
- package/test-app/remix.env.d.ts +0 -2
- package/test-app/tsconfig.json +0 -18
- 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;
|
package/browser/hooks.js
ADDED
@@ -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
|
package/browser/index.js
ADDED
@@ -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,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,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,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;
|
package/build/hooks.d.ts
ADDED
@@ -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;
|
package/build/index.d.ts
ADDED
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,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;
|
package/build/server.js
ADDED
@@ -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 @@
|
|
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
|
+
});
|