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.
- 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
|
+
});
|