Skip to content

Commit 268d7be

Browse files
committed
refactor: create the @louffee/hook-form internal module
1 parent d2710a8 commit 268d7be

File tree

6 files changed

+253
-0
lines changed

6 files changed

+253
-0
lines changed

packages/hook-form/package.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"name": "@louffee/hook-form",
3+
"version": "0.0.0",
4+
"type": "module",
5+
"exports": {
6+
"./field-controller": "./src/field-controller.tsx",
7+
"./form-controller": "./src/form-controller.tsx",
8+
"./use-form": "./src/use-form.ts"
9+
},
10+
"homepage": "https://github.com/louffee/louffee.co/tree/main/packages/hook-form",
11+
"peerDependencies": {
12+
"@hookform/resolvers": "^3",
13+
"react": "^18",
14+
"react-hook-form": "^7",
15+
"zod": "^3.22.4"
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type { JSX, ReactElement } from 'react'
2+
3+
import {
4+
Controller as HookForm$Controller,
5+
type FieldValues,
6+
type ControllerFieldState as HookForm$ControllerFieldState,
7+
type ControllerProps as HookForm$ControllerProps,
8+
type ControllerRenderProps as HookForm$ControllerRenderProps,
9+
type FieldPath as HookForm$FieldPath,
10+
type UseFormStateReturn as HookForm$UseFormStateReturn,
11+
} from 'react-hook-form'
12+
13+
export interface FieldRenderFnProps<
14+
TFieldValues extends FieldValues = FieldValues,
15+
TName extends HookForm$FieldPath<TFieldValues> = HookForm$FieldPath<TFieldValues>,
16+
> {
17+
/**
18+
* The object which contains the field props. This object contains the
19+
* properties, *e.g.*, which are injected into the input element to control
20+
* the field, *e.g.*, the field `value`, `onChange`, `onBlur`, and `ref`.
21+
*
22+
* @see https://react-hook-form.com/docs/usecontroller
23+
*/
24+
field: HookForm$ControllerRenderProps<TFieldValues, TName>
25+
/**
26+
* The object which contains the field state. This object contains the
27+
* properties such, *e.g.*, the field is touched, dirty, or invalid.
28+
*
29+
* @see {@link HookForm$ControllerFieldState}
30+
*/
31+
fieldState: HookForm$ControllerFieldState
32+
/**
33+
* The object which contains the form state. This object contains the
34+
* properties, *e.g.*, the form is submitting, submitting, or dirty.
35+
*
36+
* @see {@link HookForm$UseFormStateReturn}
37+
*/
38+
formState: HookForm$UseFormStateReturn<TFieldValues>
39+
}
40+
41+
/**
42+
* The type which defines the parameters and return type of the render function
43+
* passed down to the `FieldController` component.
44+
*/
45+
export type FieldRenderFn<
46+
TFieldValues extends FieldValues = FieldValues,
47+
TName extends HookForm$FieldPath<TFieldValues> = HookForm$FieldPath<TFieldValues>,
48+
> = (props: FieldRenderFnProps<TFieldValues, TName>) => ReactElement
49+
50+
/**
51+
* @internal The type which defines the props for the `FieldController`
52+
* component. This type is used to pick the props from the
53+
* `FieldController` component and pass them down to the `Controller`.
54+
*/
55+
type PickedFieldControllerProps = Omit<HookForm$ControllerProps, 'render'>
56+
57+
/**
58+
* The interface which defines the props for the `FieldController` component.
59+
*/
60+
export interface FieldControllerProps extends PickedFieldControllerProps {
61+
/**
62+
* The `children` prop is defined as a render function which will be passed
63+
* down to the `FieldController` component. This function will be called with
64+
* the `FieldController` props.
65+
*
66+
* @see {@link FieldRenderFn}
67+
*/
68+
children: FieldRenderFn
69+
}
70+
71+
/**
72+
* The `FieldController` is a component which wraps the `Controller` component
73+
* from the `react-hook-form` library, connecting the field to the form state.
74+
*
75+
* @example
76+
* ```tsx
77+
* <FieldController name="email" control={control} rules={{ required: true }}>
78+
* {({ field, fieldState, formState }) => (
79+
* <input type="email" {...field} />
80+
* })}
81+
* </FieldController>
82+
* ```
83+
*
84+
* @props {@link FieldControllerProps}
85+
*/
86+
function FieldController({ children, ...props }: FieldControllerProps): JSX.Element {
87+
return <HookForm$Controller {...props} render={children} />
88+
}
89+
90+
export default FieldController
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import type { JSX, ReactNode } from 'react'
2+
3+
import { FormProvider, type FieldValues } from 'react-hook-form'
4+
5+
import useForm, { type UseFormProps } from './use-form'
6+
7+
/**
8+
* The interface which defines the props for the `FormController` component.
9+
*
10+
* @see https://react-hook-form.com
11+
*/
12+
export interface FormControllerProps<
13+
TFieldValues extends FieldValues = FieldValues,
14+
TContext = // biome-ignore lint/suspicious/noExplicitAny:
15+
any,
16+
> extends UseFormProps<TFieldValues, TContext> {
17+
/**
18+
* The children passed down to the `FormController` component and to which the
19+
* form context will be available.
20+
*
21+
* @see {@link ReactNode}
22+
*/
23+
children: ReactNode | ReactNode[]
24+
}
25+
26+
/**
27+
* The `FormController` component is a client-side controller which initialises
28+
* the `useForm()` hook and provides the form context to its children.
29+
*
30+
* This component also handles the form default behaviours and settings via the
31+
* [Hook Form](https://react-hook-form.com).
32+
*
33+
* @props {@link FormControllerProps}
34+
*
35+
* @see https://react-hook-form.com
36+
*/
37+
function FormController<
38+
TFieldValues extends FieldValues = FieldValues,
39+
TContext = // biome-ignore lint/suspicious/noExplicitAny:
40+
any,
41+
>({ children, ...props }: FormControllerProps<TFieldValues, TContext>): JSX.Element {
42+
const control = useForm<TFieldValues>(props)
43+
44+
return <FormProvider {...control}>{children}</FormProvider>
45+
}
46+
47+
export default FormController
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { useFormState as useFormState$HookForm, type FieldValues, type FormState } from 'react-hook-form'
2+
3+
/**
4+
* The type which defines the return value of the `useFormState()` hook as a
5+
* tuple containing the form state and the form handlers.
6+
*/
7+
export type UseFormStateReturnType<TFieldValues extends FieldValues = FieldValues> = FormState<TFieldValues>
8+
9+
/**
10+
* The `useFormState()` is a custom React hook which returns the form state and
11+
* the form handlers from [React Hook Form](https://react-hook-form.com).
12+
*
13+
* Note that to use this hook, the host component must be wrapped in a
14+
* `FormController` component.
15+
*
16+
* @see {@link UseFormStateReturnType}
17+
*/
18+
function useFormState<TFieldValues extends FieldValues = FieldValues>(): UseFormStateReturnType<TFieldValues> {
19+
const formState = useFormState$HookForm<TFieldValues>()
20+
21+
return formState
22+
}
23+
24+
export default useFormState

packages/hook-form/src/use-form.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { zodResolver } from '@hookform/resolvers/zod'
2+
import {
3+
useForm as useForm$ReactHookForm,
4+
type FieldValues,
5+
type UseFormProps as HookForm$UseFormProps,
6+
type UseFormReturn as HookForm$UseFormReturn,
7+
} from 'react-hook-form'
8+
import type { Schema } from 'zod'
9+
10+
/**
11+
* The interface which defines and maps the props of the `useForm()` hook. It
12+
* utilises the `UseFormProps` interface from the `react-hook-form` package.
13+
*
14+
* @see https://react-hook-form.com
15+
*/
16+
export interface UseFormProps<
17+
TFieldValues extends FieldValues = FieldValues,
18+
TContext = // biome-ignore lint/suspicious/noExplicitAny:
19+
any,
20+
> extends HookForm$UseFormProps<TFieldValues, TContext> {
21+
/**
22+
* The schema which defines the shape of the form values with Zod.
23+
*
24+
* @see https://nehalist.io/react-hook-form-with-nextjs-server-actions
25+
*/
26+
schema?: Schema<TFieldValues>
27+
}
28+
29+
/**
30+
* The interface for the return value of the `useForm()` hook. It is the same as
31+
* the return value of the `useForm()` hook from `react-hook-form`.
32+
*
33+
* The `TFieldValues` generic is the shape of the form values.
34+
*
35+
* @see https://react-hook-form.com
36+
*/
37+
export interface UseFormReturnType<
38+
TFieldValues extends FieldValues = FieldValues,
39+
TContext = // biome-ignore lint/suspicious/noExplicitAny:
40+
any,
41+
> extends HookForm$UseFormReturn<TFieldValues, TContext> {}
42+
43+
/**
44+
* The `useForm()` is a custom React hook which defines the behaviour of a form
45+
* shaped by the given Zod {@link UseFormProps.schema | schema}.
46+
*
47+
* @example
48+
* ```ts
49+
* const schema = z.object({
50+
* email: z.string().email(),
51+
* });
52+
*
53+
* const { register, handleSubmit, formState: { errors } } = useForm({
54+
* schema,
55+
* });
56+
* ```
57+
*/
58+
function useForm<
59+
TFieldValues extends FieldValues = FieldValues,
60+
TContext = // biome-ignore lint/suspicious/noExplicitAny:
61+
any,
62+
>({ schema, mode = 'all', ...props }: UseFormProps<TFieldValues, TContext>): UseFormReturnType<TFieldValues, TContext> {
63+
const hook = useForm$ReactHookForm({
64+
resolver: schema ? zodResolver(schema) : undefined,
65+
mode,
66+
...props,
67+
})
68+
69+
return hook
70+
}
71+
72+
export default useForm

packages/hook-form/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "@louffee/tsconfig/tsconfig.react-library.json"
3+
}

0 commit comments

Comments
 (0)