remix-hook-form
Advanced tools
Comparing version 1.0.8 to 1.1.0
import React from "react"; | ||
import { SubmitFunction } from "@remix-run/react"; | ||
import { SubmitErrorHandler, SubmitHandler } from "react-hook-form"; | ||
import type { FieldValues, UseFormProps, UseFormReturn } from "react-hook-form/dist/types"; | ||
import type { FieldValues, Path, RegisterOptions, UseFormProps, UseFormReturn } from "react-hook-form/dist/types"; | ||
export type SubmitFunctionOptions = Parameters<SubmitFunction>[1]; | ||
@@ -16,2 +16,16 @@ interface UseRemixFormOptions<T extends FieldValues> extends UseFormProps<T> { | ||
handleSubmit: (e?: React.BaseSyntheticEvent<object, any, any> | undefined) => Promise<void>; | ||
register: (name: Path<T>, options: RegisterOptions<T>) => { | ||
defaultValue: any; | ||
onChange: import("react-hook-form").ChangeHandler; | ||
onBlur: import("react-hook-form").ChangeHandler; | ||
ref: import("react-hook-form").RefCallBack; | ||
name: Path<T>; | ||
min?: string | number | undefined; | ||
max?: string | number | undefined; | ||
maxLength?: number | undefined; | ||
minLength?: number | undefined; | ||
pattern?: string | undefined; | ||
required?: boolean | undefined; | ||
disabled?: boolean | undefined; | ||
}; | ||
formState: { | ||
@@ -41,3 +55,2 @@ dirtyFields: Partial<Readonly<import("react-hook-form").DeepMap<import("react-hook-form").DeepPartial<T>, boolean>>>; | ||
control: import("react-hook-form").Control<T, any>; | ||
register: import("react-hook-form").UseFormRegister<T>; | ||
setFocus: import("react-hook-form").UseFormSetFocus<T>; | ||
@@ -44,0 +57,0 @@ }; |
@@ -1,74 +0,81 @@ | ||
import V from "react"; | ||
import { useSubmit as $, useActionData as j } from "@remix-run/react"; | ||
import { useForm as k, FormProvider as R, useFormContext as A } from "react-hook-form"; | ||
const u = (t) => { | ||
const e = {}; | ||
for (const [o, a] of t.entries()) { | ||
const i = o.split("."); | ||
let r = e; | ||
for (let s = 0; s < i.length - 1; s++) { | ||
const c = i[s]; | ||
r[c] || (r[c] = /^\d+$/.test(i[s + 1]) ? [] : {}), r = r[c]; | ||
import k from "react"; | ||
import { useSubmit as R, useActionData as A } from "@remix-run/react"; | ||
import { useForm as _, FormProvider as C, useFormContext as I } from "react-hook-form"; | ||
const f = (t) => { | ||
const o = {}; | ||
for (const [e, a] of t.entries()) { | ||
const n = e.split("."); | ||
let r = o; | ||
for (let i = 0; i < n.length - 1; i++) { | ||
const u = n[i]; | ||
r[u] || (r[u] = /^\d+$/.test(n[i + 1]) ? [] : {}), r = r[u]; | ||
} | ||
const n = i[i.length - 1], m = /\[\d*\]$|\[\]$/.test(n); | ||
if (m) { | ||
const s = n.replace(/\[\d*\]$|\[\]$/, ""); | ||
r[s] || (r[s] = []), r[s].push(a); | ||
const c = n[n.length - 1], s = /\[\d*\]$|\[\]$/.test(c); | ||
if (s) { | ||
const i = c.replace(/\[\d*\]$|\[\]$/, ""); | ||
r[i] || (r[i] = []), r[i].push(a); | ||
} | ||
m || (/^\d+$/.test(n) ? r.push(a) : r[n] = a); | ||
s || (/^\d+$/.test(c) ? r.push(a) : r[c] = a); | ||
} | ||
return e; | ||
}, C = (t) => { | ||
const e = new URL(t.url).searchParams; | ||
return u(e); | ||
}, I = (t) => t.method === "GET" || t.method === "get", _ = async (t, e) => { | ||
const o = I(t) ? C(t) : await J(t); | ||
return await N(o, e); | ||
}, N = async (t, e) => { | ||
const { errors: o, values: a } = await e(t, {}, { shouldUseNativeValidation: !1, fields: {} }); | ||
return Object.keys(o).length > 0 ? { errors: o, data: void 0 } : { errors: void 0, data: a }; | ||
}, G = (t, e = "formData") => { | ||
const o = new FormData(), a = JSON.stringify(t); | ||
return o.append(e, a), o; | ||
}, J = async (t, e = "formData") => { | ||
const o = await t.formData(), a = o.get(e); | ||
return o; | ||
}, N = (t) => { | ||
const o = new URL(t.url).searchParams; | ||
return f(o); | ||
}, G = (t) => t.method === "GET" || t.method === "get", B = async (t, o) => { | ||
const e = G(t) ? N(t) : await L(t); | ||
return { ...await J(e, o), receivedValues: e }; | ||
}, J = async (t, o) => { | ||
const { errors: e, values: a } = await o(t, {}, { shouldUseNativeValidation: !1, fields: {} }); | ||
return Object.keys(e).length > 0 ? { errors: e, data: void 0 } : { errors: void 0, data: a }; | ||
}, K = (t, o = "formData") => { | ||
const e = new FormData(), a = JSON.stringify(t); | ||
return e.append(o, a), e; | ||
}, L = async (t, o = "formData") => { | ||
const e = await t.formData(), a = e.get(o); | ||
if (!a) | ||
return u(o); | ||
return f(e); | ||
if (typeof a != "string") | ||
throw new Error("Data is not a string"); | ||
return JSON.parse(a); | ||
}, l = (t, e) => { | ||
if (!e) | ||
}, v = (t, o) => { | ||
if (!o) | ||
return t; | ||
for (const [o, a] of Object.entries(e)) | ||
typeof a == "object" && !Array.isArray(a) ? (t[o] || (t[o] = {}), l(t[o], a)) : a && (t[o] = a); | ||
for (const [e, a] of Object.entries(o)) | ||
typeof a == "object" && !Array.isArray(a) ? (t[e] || (t[e] = {}), v(t[e], a)) : a && (t[e] = a); | ||
return t; | ||
}, T = ({ submitHandlers: t, submitConfig: e, submitData: o, ...a }) => { | ||
var i, r; | ||
const n = $(), m = j(), s = k(a), c = (x) => { | ||
n(G({ ...x, ...o }), { | ||
}, M = ({ submitHandlers: t, submitConfig: o, submitData: e, ...a }) => { | ||
var n, r; | ||
const c = R(), s = A(), i = _(a), u = (l) => { | ||
c(K({ ...l, ...e }), { | ||
method: "post", | ||
...e | ||
...o | ||
}); | ||
}, d = () => { | ||
}, f = s.formState, { dirtyFields: h, isDirty: v, isSubmitSuccessful: y, isSubmitted: p, isSubmitting: g, isValid: D, isValidating: F, touchedFields: S, submitCount: b, errors: P, isLoading: w } = f, O = l(P, m); | ||
}, h = () => { | ||
}, y = i.formState, { dirtyFields: g, isDirty: p, isSubmitSuccessful: D, isSubmitted: F, isSubmitting: S, isValid: b, isValidating: P, touchedFields: V, submitCount: w, errors: O, isLoading: x } = y, $ = v(O, s != null && s.errors ? s.errors : s); | ||
return { | ||
...s, | ||
handleSubmit: s.handleSubmit((i = t == null ? void 0 : t.onValid) !== null && i !== void 0 ? i : c, (r = t == null ? void 0 : t.onInvalid) !== null && r !== void 0 ? r : d), | ||
...i, | ||
handleSubmit: i.handleSubmit((n = t == null ? void 0 : t.onValid) !== null && n !== void 0 ? n : u, (r = t == null ? void 0 : t.onInvalid) !== null && r !== void 0 ? r : h), | ||
register: (l, j) => { | ||
var d, m; | ||
return { | ||
...i.register(l, j), | ||
defaultValue: (m = (d = s == null ? void 0 : s.defaultValues) === null || d === void 0 ? void 0 : d[l]) !== null && m !== void 0 ? m : "" | ||
}; | ||
}, | ||
formState: { | ||
dirtyFields: h, | ||
isDirty: v, | ||
isSubmitSuccessful: y, | ||
isSubmitted: p, | ||
isSubmitting: g, | ||
isValid: D, | ||
isValidating: F, | ||
touchedFields: S, | ||
submitCount: b, | ||
isLoading: w, | ||
errors: O | ||
dirtyFields: g, | ||
isDirty: p, | ||
isSubmitSuccessful: D, | ||
isSubmitted: F, | ||
isSubmitting: S, | ||
isValid: b, | ||
isValidating: P, | ||
touchedFields: V, | ||
submitCount: w, | ||
isLoading: x, | ||
errors: $ | ||
} | ||
}; | ||
}, z = ({ children: t, ...e }) => V.createElement(R, { ...e }, t), B = () => { | ||
const t = A(); | ||
}, Q = ({ children: t, ...o }) => k.createElement(C, { ...o }, t), W = () => { | ||
const t = I(); | ||
return { | ||
@@ -80,10 +87,10 @@ ...t, | ||
export { | ||
z as RemixFormProvider, | ||
G as createFormData, | ||
C as getFormDataFromSearchParams, | ||
_ as getValidatedFormData, | ||
J as parseFormData, | ||
T as useRemixForm, | ||
B as useRemixFormContext, | ||
N as validateFormData | ||
Q as RemixFormProvider, | ||
K as createFormData, | ||
N as getFormDataFromSearchParams, | ||
B as getValidatedFormData, | ||
L as parseFormData, | ||
M as useRemixForm, | ||
W as useRemixFormContext, | ||
J as validateFormData | ||
}; |
@@ -22,5 +22,7 @@ import { FieldValues, Resolver, FieldErrors, FieldErrorsImpl, DeepRequired } from "react-hook-form"; | ||
export declare const getValidatedFormData: <T extends FieldValues>(request: Request, resolver: Resolver) => Promise<{ | ||
receivedValues: Record<any, any>; | ||
errors: FieldErrors<T>; | ||
data: undefined; | ||
} | { | ||
receivedValues: Record<any, any>; | ||
errors: undefined; | ||
@@ -27,0 +29,0 @@ data: T; |
{ | ||
"name": "remix-hook-form", | ||
"version": "1.0.8", | ||
"version": "1.1.0", | ||
"description": "Utility wrapper around react-hook-form for use with Remix.run", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -16,2 +16,3 @@ # remix-hook-form | ||
Oh, and did we mention that this is fully Progressively enhanced? That's right, you can use this with or without javascript! | ||
@@ -28,3 +29,3 @@ ## Installation | ||
## Usage | ||
## Basic usage | ||
@@ -91,2 +92,64 @@ Here is an example usage of remix-hook-form: | ||
## Usage with NO js | ||
Here is an example usage of remix-hook-form: | ||
```jsx | ||
import { useRemixForm, getValidatedFormData } from "remix-hook-form"; | ||
import { Form } from "@remix-run/react"; | ||
import { zodResolver } from "@hookform/resolvers/zod"; | ||
import * as zod from "zod"; | ||
import { ActionArgs, json } from "@remix-run/server-runtime"; | ||
const schema = zod.object({ | ||
name: zod.string().nonempty(), | ||
email: zod.string().email().nonempty(), | ||
}); | ||
type FormData = zod.infer<typeof schema>; | ||
const resolver = zodResolver(schema); | ||
export const action = async ({ request }: ActionArgs) => { | ||
const { errors, data, receivedValues: defaultValues } = | ||
await getValidatedFormData<FormData>(request, resolver); | ||
if (errors) { | ||
return json({ errors, defaultValues }); | ||
} | ||
// Do something with the data | ||
return json(data); | ||
}; | ||
export default function MyForm() { | ||
const { | ||
handleSubmit, | ||
formState: { errors }, | ||
register, | ||
} = useRemixForm({ | ||
mode: "onSubmit", | ||
defaultValues: { | ||
name: "", | ||
email: "", | ||
}, | ||
resolver, | ||
}); | ||
return ( | ||
<Form onSubmit={handleSubmit}> | ||
<label> | ||
Name: | ||
<input type="text" {...register("name")} /> | ||
{errors.name && <p>{errors.name.message}</p>} | ||
</label> | ||
<label> | ||
Email: | ||
<input type="email" {...register("email")} /> | ||
{errors.email && <p>{errors.email.message}</p>} | ||
</label> | ||
<button type="submit">Submit</button> | ||
</Form> | ||
); | ||
} | ||
``` | ||
## Utilities | ||
@@ -102,4 +165,7 @@ | ||
getValidatedFormData is a utility function that can be used to validate form data in your action. It takes two arguments: the request object and the resolver function. It returns an object with two properties: `errors` and `data`. If there are no errors, `errors` will be `undefined`. If there are errors, `errors` will be an object with the same shape as the `errors` object returned by `useRemixForm`. If there are no errors, `data` will be an object with the same shape as the `data` object returned by `useRemixForm`. | ||
getValidatedFormData is a utility function that can be used to validate form data in your action. It takes two arguments: the request object and the resolver function. It returns an object with three properties: `errors`, `receivedValues` and `data`. If there are no errors, `errors` will be `undefined`. If there are errors, `errors` will be an object with the same shape as the `errors` object returned by `useRemixForm`. If there are no errors, `data` will be an object with the same shape as the `data` object returned by `useRemixForm`. | ||
The `receivedValues` property allows you to set the default values of your form to the values that were received from the request object. This is useful if you want to display the form again with the values that were submitted by the user when there is no JS present | ||
### Example with errors only | ||
```jsx | ||
@@ -117,3 +183,18 @@ /** all the same code from above */ | ||
}; | ||
``` | ||
### Example with errors and receivedValues | ||
```jsx | ||
/** all the same code from above */ | ||
export const action = async ({ request }: ActionArgs) => { | ||
// Takes the request from the frontend, parses and validates it and returns the data | ||
const { errors, data, receivedValues } = | ||
await getValidatedFormData<FormData>(request, resolver); | ||
if (errors) { | ||
return json({ errors, receivedValues }); | ||
} | ||
// Do something with the data | ||
}; | ||
``` | ||
@@ -213,3 +294,2 @@ | ||
- The success case is provided by default where when the form is validated by the provided resolver, and it has no errors, it will automatically submit the form to the current route using a POST request. The data will be sent as `formData` to the action function. | ||
@@ -221,2 +301,3 @@ - The data that is sent is automatically wrapped into a formData object and passed to the server ready to be used. Easiest way to consume it is by using the `parseFormData` or `getValidatedFormData` function from the `remix-hook-form` package. | ||
The `register` function returned also has super powers that allows it to set the default value of the input returned from the server. | ||
@@ -223,0 +304,0 @@ This is achieved by using `useActionData` from `@remix-run/react` to get the data returned by the action function. If the data returned by the action function is an object with the same shape as the `errors` object returned by `useRemixForm`, it will automatically populate the `formState.errors` object with the errors returned by the server. To ensure this is done properly, it is recommended that you use `getValidatedFormData` and then return the errors object from the action function as `json(errors)`. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
35096
254
411