Skip to content

Commit 4d3ce8d

Browse files
authored
improve react-hook-form reference (#82)
1 parent 55dc501 commit 4d3ce8d

File tree

7 files changed

+203
-119
lines changed

7 files changed

+203
-119
lines changed

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,28 @@
2020
},
2121
"dependencies": {
2222
"@heroicons/react": "^1.0.6",
23-
"@hookform/resolvers": "^2.8.10",
23+
"@hookform/resolvers": "^3.1.1",
2424
"@tailwindcss/typography": "^0.5.7",
2525
"@tanstack/react-query": "^4.2.3",
26-
"@trpc/client": "^10.5.0",
27-
"@trpc/next": "^10.5.0",
28-
"@trpc/react-query": "^10.5.0",
29-
"@trpc/server": "^10.5.0",
26+
"@trpc/client": "^10.34.0",
27+
"@trpc/next": "^10.34.0",
28+
"@trpc/react-query": "^10.34.0",
29+
"@trpc/server": "^10.34.0",
3030
"autoprefixer": "^10.4.11",
3131
"clsx": "^1.1.1",
3232
"next": "12.3.1",
3333
"next-auth": "^4.10.3",
3434
"prism-react-renderer": "^1.3.3",
3535
"react": "^18.2.0",
3636
"react-dom": "^18.2.0",
37-
"react-hook-form": "^7.31.1",
37+
"react-hook-form": "^7.45.2",
3838
"start-server-and-test": "^1.12.0",
3939
"superjson": "^1.9.1",
4040
"tailwindcss": "^3.1.8",
4141
"zod": "^3.16.0"
4242
},
4343
"devDependencies": {
44-
"@tailwindcss/forms": "^0.5.3",
44+
"@tailwindcss/forms": "^0.5.4",
4545
"@types/jest": "^29.2.4",
4646
"@types/node": "^17.0.33",
4747
"@types/react": "^18.0.9",

pnpm-lock.yaml

+43-43
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/feature/next-auth/index.tsx

+2-10
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ export default function NextAuth() {
1313
}
1414

1515
function ServerSideSessionCheck() {
16-
const query = trpc.authRouter.getSession.useQuery(undefined, {
17-
suspense: true,
18-
});
19-
20-
const session = query.data;
16+
const [session] = trpc.authRouter.getSession.useSuspenseQuery();
2117

2218
return (
2319
<div className="my-1">
@@ -63,11 +59,7 @@ function MiddlewareQuery() {
6359
}
6460

6561
function SignInButton() {
66-
const query = trpc.authRouter.getSession.useQuery(undefined, {
67-
suspense: true,
68-
});
69-
70-
const session = query.data;
62+
const [session] = trpc.authRouter.getSession.useSuspenseQuery();
7163

7264
return (
7365
<div className="flex items-center">

src/feature/react-hook-form/Form.tsx

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { zodResolver } from '@hookform/resolvers/zod';
2+
import { useId } from 'react';
3+
import {
4+
FieldValues,
5+
FormProvider,
6+
SubmitHandler,
7+
useForm,
8+
useFormContext,
9+
UseFormProps,
10+
UseFormReturn,
11+
} from 'react-hook-form';
12+
import { z } from 'zod';
13+
14+
type UseZodForm<TInput extends FieldValues> = UseFormReturn<TInput> & {
15+
/**
16+
* A unique ID for this form.
17+
*/
18+
id: string;
19+
};
20+
export function useZodForm<TSchema extends z.ZodType>(
21+
props: Omit<UseFormProps<TSchema['_input']>, 'resolver'> & {
22+
schema: TSchema;
23+
},
24+
) {
25+
const form = useForm<TSchema['_input']>({
26+
...props,
27+
resolver: zodResolver(props.schema, undefined, {
28+
// This makes it so we can use `.transform()`s on the schema without same transform getting applied again when it reaches the server
29+
raw: true,
30+
}),
31+
}) as UseZodForm<TSchema['_input']>;
32+
33+
form.id = useId();
34+
35+
return form;
36+
}
37+
38+
type AnyZodForm = UseZodForm<any>;
39+
40+
export function Form<TInput extends FieldValues>(
41+
props: Omit<React.ComponentProps<'form'>, 'onSubmit' | 'id'> & {
42+
handleSubmit: SubmitHandler<TInput>;
43+
form: UseZodForm<TInput>;
44+
},
45+
) {
46+
const { handleSubmit, form, ...passThrough }: typeof props = props;
47+
return (
48+
<FormProvider {...form}>
49+
<form
50+
{...passThrough}
51+
id={form.id}
52+
onSubmit={(event) => {
53+
event.stopPropagation();
54+
event.preventDefault();
55+
56+
form.handleSubmit(async (values) => {
57+
try {
58+
await handleSubmit(values);
59+
} catch (cause) {
60+
form.setError('root.server', {
61+
message: (cause as Error)?.message ?? 'Unknown error',
62+
type: 'server',
63+
});
64+
}
65+
})(event);
66+
}}
67+
></form>
68+
</FormProvider>
69+
);
70+
}
71+
72+
export function SubmitButton(
73+
props: Omit<React.ComponentProps<'button'>, 'type' | 'form'> & {
74+
/**
75+
* Optionally specify a form to submit instead of the closest form context.
76+
*/
77+
form?: AnyZodForm;
78+
},
79+
) {
80+
const context = useFormContext();
81+
82+
const form = props.form ?? context;
83+
if (!form) {
84+
throw new Error(
85+
'SubmitButton must be used within a Form or have a form prop',
86+
);
87+
}
88+
const { formState } = form;
89+
90+
return (
91+
<button
92+
{...props}
93+
form={props.form?.id}
94+
type="submit"
95+
disabled={formState.isSubmitting}
96+
>
97+
{formState.isSubmitting ? 'Loading' : props.children}
98+
</button>
99+
);
100+
}

0 commit comments

Comments
 (0)