forked from forge-42/remix-hook-form
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d0b9e0f
commit bf75c5e
Showing
8 changed files
with
626 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,3 +102,5 @@ dist | |
|
||
# TernJS port file | ||
.tern-port | ||
|
||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,134 @@ | ||
# remix-hook-form | ||
Open source wrapper for react-hook-form aimed at Remix.run | ||
|
||
remix-hook-form is a lightweight wrapper around [remix-hook-form](https://react-hook-form.com/) that makes it easier to use in your [Remix](https://remix.run) applications. It provides a set of hooks and utilities that simplify the process of working with forms and form data, while leveraging the power and flexibility of remix-hook-form. | ||
|
||
## Installation | ||
|
||
You can install the latest version of remix-hook-form using [npm](https://www.npmjs.com/): | ||
|
||
`npm install remix-hook-form` | ||
|
||
Or, if you prefer [yarn](https://yarnpkg.com/): | ||
|
||
`yarn add remix-hook-form` | ||
|
||
## Usage | ||
|
||
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 = ({ request }: ActionArgs) => { | ||
const { errors, data } = | ||
getValidatedFormData < FormData > (request, resolver); | ||
if (errors) { | ||
return json(errors); | ||
} | ||
// 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 | ||
|
||
### getValidatedFormData | ||
|
||
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`. | ||
|
||
### validateFormData | ||
|
||
validateFormData 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`. | ||
|
||
The difference between `validateFormData` and `getValidatedFormData` is that `validateFormData` only validates the data while the `getValidatedFormData` function also extracts the data automatically from the request object assuming you were using the default setup | ||
|
||
### createFormData | ||
|
||
createFormData is a utility function that can be used to create a FormData object from the data returned by the handleSubmit function from `react-hook-form`. It takes two arguments, first one is the `data` from the `handleSubmit` function and the second one is the key that the data will be stored in the FormData object. (default is `formData`). It returns a FormData object. | ||
|
||
### parseFormData | ||
|
||
parseFormData is a utility function that can be used to parse the data submitted to the action by the handleSubmit function from `react-hook-form`. It takes two arguments, first one is the `request` submitted from the frontend and the second one is the key that the data will be stored in the FormData object. (default is `formData`). It returns an object that contains unvalidated `data` submitted from the frontend. | ||
|
||
## Hooks | ||
|
||
### useRemixForm | ||
|
||
useRemixForm is a hook that can be used to create a form in your Remix application. It takes all the same properties as `react-hook-form`'s `useForm` hook, with the addition of 3 properties: | ||
`submitHandlers` - an object containing two properties, `onValid` which can be passed into the function to override the default behavior of the handleSubmit success case provided by the hook, and `onInvalid` which can be passed into the function to override the default behavior of the handleSubmit error case provided by the hook. | ||
`submitConfig` - allows you to pass additional configuration to the `useSubmit` function from remix such as `{ replace: true }` to replace the current history entry instead of pushing a new one., | ||
`submitData` - allows you to pass additional data to the backend when the form is submitted | ||
|
||
The hook acts almost identically to the `react-hook-form` hook, with the exception of the `handleSubmit` function, and the `formState.errors`. | ||
|
||
The `handleSubmit` function uses two thing under the hood to allow you easier usage in Remix, those two things are: | ||
|
||
- 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. | ||
- 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 `formState.errors` object is automatically populated with the errors returned by the server. If the server returns 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. | ||
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 assure this is done properly it is recommended that you use `getValidatedFormData` and then return the errors object from the action function as `json(errors)`. | ||
|
||
### useRemixFormContext | ||
|
||
Exactly the same as `useFormContext` from `react-hook-form` but it also returns the changed `formState.errors` and `handleSubmit` object. | ||
|
||
## RemixFormProvider | ||
|
||
Identical to the `FormProvider` from `react-hook-form`, but it also returns the changed `formState.errors` and `handleSubmit` object. | ||
|
||
## License | ||
|
||
MIT | ||
|
||
## Bugs | ||
|
||
If you find a bug, please file an issue on [our issue tracker on GitHub](https://github.com/Code-Forge-Net/remix-hook-form/issues) | ||
|
||
## Contributing | ||
|
||
We welcome contributions from the community! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"name": "remix-hook-form", | ||
"version": "1.0.0", | ||
"description": "Utility wrapper around react-hook-form", | ||
"main": "build/index.js", | ||
"scripts": { | ||
"prepare": "tsc", | ||
"prepublish": "npm run prepare", | ||
"test": "test" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/Code-Forge-Net/remix-hook-form.git" | ||
}, | ||
"keywords": [ | ||
"React", | ||
"Remix", | ||
"Remix.run", | ||
"react-hook-form", | ||
"hooks", | ||
"forms" | ||
], | ||
"author": "Alem Tuzlak", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/Code-Forge-Net/remix-hook-form/issues" | ||
}, | ||
"homepage": "https://github.com/Code-Forge-Net/remix-hook-form#readme", | ||
"peerDependencies": { | ||
"@remix-run/react": "^1.15.0", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"react-hook-form": "^7.43.9" | ||
}, | ||
"devDependencies": { | ||
"@hookform/resolvers": "^3.0.1", | ||
"@types/react": "^18.0.34", | ||
"typescript": "^5.0.4" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import React from "react"; | ||
import { SubmitFunction, useActionData, useSubmit } from "@remix-run/react"; | ||
import { | ||
SubmitErrorHandler, | ||
SubmitHandler, | ||
useFormContext, | ||
} from "react-hook-form"; | ||
import { useForm, FormProvider } from "react-hook-form"; | ||
import type { | ||
FieldValues, | ||
UseFormHandleSubmit, | ||
UseFormProps, | ||
UseFormReturn, | ||
} from "react-hook-form/dist/types"; | ||
import { createFormData, mergeErrors } from "../utilities"; | ||
|
||
export type SubmitFunctionOptions = Parameters<SubmitFunction>[1]; | ||
interface UseRemixFormOptions<T extends FieldValues> extends UseFormProps<T> { | ||
submitHandlers?: { | ||
onValid?: SubmitHandler<T>; | ||
onInvalid?: SubmitErrorHandler<T>; | ||
}; | ||
submitConfig?: SubmitFunctionOptions; | ||
submitData?: FieldValues; | ||
} | ||
|
||
export const useRemixForm = <T extends FieldValues>({ | ||
submitHandlers, | ||
submitConfig, | ||
submitData, | ||
...formProps | ||
}: UseRemixFormOptions<T>) => { | ||
const submit = useSubmit(); | ||
const data = useActionData(); | ||
const methods = useForm<T>(formProps); | ||
|
||
// Submits the data to the server when form is valid | ||
const onSubmit = (data: T) => { | ||
submit(createFormData({ ...data, ...submitData }), { | ||
method: "post", | ||
...submitConfig, | ||
}); | ||
}; | ||
|
||
const onInvalid = () => {}; | ||
|
||
const formState = methods.formState; | ||
|
||
const { | ||
dirtyFields, | ||
isDirty, | ||
isSubmitSuccessful, | ||
isSubmitted, | ||
isSubmitting, | ||
isValid, | ||
isValidating, | ||
touchedFields, | ||
submitCount, | ||
errors, | ||
isLoading, | ||
} = formState; | ||
|
||
const formErrors = mergeErrors<T>(errors, data); | ||
|
||
return { | ||
...methods, | ||
handleSubmit: methods.handleSubmit( | ||
submitHandlers?.onValid ?? onSubmit, | ||
submitHandlers?.onInvalid ?? onInvalid | ||
), | ||
formState: { | ||
dirtyFields, | ||
isDirty, | ||
isSubmitSuccessful, | ||
isSubmitted, | ||
isSubmitting, | ||
isValid, | ||
isValidating, | ||
touchedFields, | ||
submitCount, | ||
isLoading, | ||
errors: formErrors, | ||
}, | ||
}; | ||
}; | ||
interface RemixFormProviderProps<T extends FieldValues> | ||
extends Omit<UseFormReturn<T>, "handleSubmit"> { | ||
children: React.ReactNode; | ||
handleSubmit: any; | ||
} | ||
export const RemixFormProvider = <T extends FieldValues>({ | ||
children, | ||
...props | ||
}: RemixFormProviderProps<T>) => { | ||
return <FormProvider {...props}>{children}</FormProvider>; | ||
}; | ||
|
||
export const useRemixFormContext = <T extends FieldValues>() => { | ||
const methods = useFormContext<T>(); | ||
return { | ||
...methods, | ||
handleSubmit: methods.handleSubmit as any as ReturnType< | ||
UseFormHandleSubmit<T> | ||
>, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from "./utilities"; | ||
export * from "./hook"; | ||
export * from "./utilities"; |
Oops, something went wrong.