Skip to content

Commit 81529bf

Browse files
committed
refactor: use new field component
1 parent 54e2a29 commit 81529bf

File tree

9 files changed

+432
-277
lines changed

9 files changed

+432
-277
lines changed

apps/docs/src/components/ui/field.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ const FieldError = (props: FieldErrorProps) => {
216216
}
217217

218218
if (errors.length == 1) {
219-
return <>errors[0]?.message</>
219+
return <>{errors[0]?.message}</>
220220
}
221221

222222
return (

apps/web/package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"@aws-sdk/client-s3": "3.919.0",
3232
"@aws-sdk/s3-request-presigner": "3.919.0",
3333
"@fastify/deepmerge": "3.1.0",
34-
"@hookform/resolvers": "5.2.2",
3534
"@icons-pack/react-simple-icons": "13.8.0",
3635
"@number-flow/react": "0.5.10",
3736
"@octokit/rest": "22.0.0",
@@ -47,6 +46,7 @@
4746
"@repo/ui": "workspace:*",
4847
"@shikijs/rehype": "3.14.0",
4948
"@shikijs/transformers": "3.14.0",
49+
"@tanstack/react-form": "1.23.8",
5050
"@tanstack/react-query": "5.90.5",
5151
"@tanstack/react-query-devtools": "5.90.2",
5252
"@tanstack/react-table": "8.21.3",
@@ -73,7 +73,6 @@
7373
"posthog-node": "5.10.4",
7474
"react": "19.2.0",
7575
"react-dom": "19.2.0",
76-
"react-hook-form": "7.65.0",
7776
"react-intersection-observer": "10.0.0",
7877
"react-medium-image-zoom": "5.4.0",
7978
"remark": "15.0.1",
@@ -96,7 +95,7 @@
9695
"@content-collections/cli": "0.1.7",
9796
"@content-collections/core": "0.11.1",
9897
"@content-collections/mdx": "0.2.2",
99-
"@content-collections/next": "0.2.8",
98+
"@content-collections/next": "0.2.9",
10099
"@nelsonlaidev/eslint-config": "2.3.1",
101100
"@nelsonlaidev/typescript-config": "1.0.0",
102101
"@next/bundle-analyzer": "16.0.1",

apps/web/src/components/account/profile.tsx

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use client'
22

3-
import { zodResolver } from '@hookform/resolvers/zod'
43
import {
54
AlertDialog,
65
AlertDialogCancel,
@@ -14,11 +13,11 @@ import {
1413
import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/avatar'
1514
import { Button } from '@repo/ui/components/button'
1615
import { Card } from '@repo/ui/components/card'
17-
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@repo/ui/components/form'
16+
import { Field, FieldError } from '@repo/ui/components/field'
1817
import { Input } from '@repo/ui/components/input'
18+
import { useForm } from '@tanstack/react-form'
1919
import { useTranslations } from 'next-intl'
2020
import { useRef, useState } from 'react'
21-
import { useForm } from 'react-hook-form'
2221
import { toast } from 'sonner'
2322
import * as z from 'zod'
2423

@@ -102,23 +101,28 @@ const EditName = (props: EditNameProps) => {
102101
name: z.string().min(1, t('error.name-cannot-be-empty')).max(50, t('error.name-too-long'))
103102
})
104103

105-
const form = useForm<z.infer<typeof editNameFormSchema>>({
106-
resolver: zodResolver(editNameFormSchema),
104+
const form = useForm({
107105
defaultValues: {
108106
name
107+
},
108+
validators: {
109+
onSubmit: editNameFormSchema
110+
},
111+
onSubmit: ({ value }) => {
112+
if (isUpdating) return
113+
updateUser({ name: value.name })
109114
}
110115
})
111116

112117
const { mutate: updateUser, isPending: isUpdating } = useUpdateUser(() => {
113-
form.reset({ name: form.getValues('name') })
114118
setOpen(false)
115119
toast.success(t('success.name-updated'))
116120
refetchSession()
117121
})
118122

119-
const onSubmit = (values: z.infer<typeof editNameFormSchema>) => {
120-
if (isUpdating) return
121-
updateUser({ name: values.name })
123+
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
124+
event.preventDefault()
125+
form.handleSubmit()
122126
}
123127

124128
return (
@@ -127,31 +131,38 @@ const EditName = (props: EditNameProps) => {
127131
<Button variant='outline'>{t('account.edit-name')}</Button>
128132
</AlertDialogTrigger>
129133
<AlertDialogContent>
130-
<Form {...form}>
131-
<form className='space-y-6' onSubmit={form.handleSubmit(onSubmit)}>
132-
<AlertDialogHeader>
133-
<AlertDialogTitle>{t('account.edit-name')}</AlertDialogTitle>
134-
<AlertDialogDescription>{t('account.edit-name-description')}</AlertDialogDescription>
135-
</AlertDialogHeader>
136-
<FormField
137-
control={form.control}
138-
name='name'
139-
render={({ field }) => (
140-
<FormItem>
141-
<FormLabel>{t('account.display-name')}</FormLabel>
142-
<FormControl>
143-
<Input {...field} placeholder={t('account.display-name')} />
144-
</FormControl>
145-
<FormMessage />
146-
</FormItem>
147-
)}
148-
/>
149-
<AlertDialogFooter>
150-
<AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
151-
<Button type='submit'>{t('common.save')}</Button>
152-
</AlertDialogFooter>
153-
</form>
154-
</Form>
134+
<form className='space-y-6' onSubmit={handleSubmit}>
135+
<AlertDialogHeader>
136+
<AlertDialogTitle>{t('account.edit-name')}</AlertDialogTitle>
137+
<AlertDialogDescription>{t('account.edit-name-description')}</AlertDialogDescription>
138+
</AlertDialogHeader>
139+
<form.Field name='name'>
140+
{(field) => {
141+
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
142+
143+
return (
144+
<Field data-invalid={isInvalid}>
145+
<Input
146+
id={field.name}
147+
name={field.name}
148+
value={field.state.value}
149+
onBlur={field.handleBlur}
150+
onChange={(e) => {
151+
field.handleChange(e.target.value)
152+
}}
153+
aria-invalid={isInvalid}
154+
placeholder={t('account.display-name')}
155+
/>
156+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
157+
</Field>
158+
)
159+
}}
160+
</form.Field>
161+
<AlertDialogFooter>
162+
<AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
163+
<Button type='submit'>{t('common.save')}</Button>
164+
</AlertDialogFooter>
165+
</form>
155166
</AlertDialogContent>
156167
</AlertDialog>
157168
)

apps/web/src/components/guestbook/message-box.tsx

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22

33
import type { User } from '@/lib/auth-client'
44

5-
import { zodResolver } from '@hookform/resolvers/zod'
65
import { Avatar, AvatarFallback, AvatarImage } from '@repo/ui/components/avatar'
76
import { Button } from '@repo/ui/components/button'
8-
import { Form, FormControl, FormField, FormItem, FormMessage } from '@repo/ui/components/form'
7+
import { Field, FieldError, FieldGroup } from '@repo/ui/components/field'
98
import { Textarea } from '@repo/ui/components/textarea'
9+
import { useForm } from '@tanstack/react-form'
1010
import { useTranslations } from 'next-intl'
11-
import { useForm } from 'react-hook-form'
1211
import { toast } from 'sonner'
1312
import * as z from 'zod'
1413

@@ -30,10 +29,16 @@ const MessageBox = (props: MessageBoxProps) => {
3029
message: z.string().min(1, t('error.message-cannot-be-empty'))
3130
})
3231

33-
const form = useForm<z.infer<typeof guestbookFormSchema>>({
34-
resolver: zodResolver(guestbookFormSchema),
32+
const form = useForm({
3533
defaultValues: {
3634
message: ''
35+
},
36+
validators: {
37+
onSubmit: guestbookFormSchema
38+
},
39+
onSubmit: ({ value }) => {
40+
if (isCreating) return
41+
createMessage({ message: value.message })
3742
}
3843
})
3944

@@ -42,9 +47,9 @@ const MessageBox = (props: MessageBoxProps) => {
4247
toast.success(t('success.message-created'))
4348
})
4449

45-
const onSubmit = (values: z.infer<typeof guestbookFormSchema>) => {
46-
if (isCreating) return
47-
createMessage({ message: values.message })
50+
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
51+
event.preventDefault()
52+
form.handleSubmit()
4853
}
4954

5055
const defaultImage = getDefaultImage(user.id)
@@ -55,35 +60,41 @@ const MessageBox = (props: MessageBoxProps) => {
5560
<AvatarImage src={user.image ?? defaultImage} alt={user.name} />
5661
<AvatarFallback>{getAbbreviation(user.name)}</AvatarFallback>
5762
</Avatar>
58-
<Form {...form}>
59-
<form onSubmit={form.handleSubmit(onSubmit)} className='w-full'>
60-
<FormField
61-
control={form.control}
62-
name='message'
63-
render={({ field }) => (
64-
<FormItem>
65-
<FormControl>
66-
<Textarea placeholder={t('guestbook.placeholder')} data-testid='guestbook-textarea' {...field} />
67-
</FormControl>
68-
<FormMessage />
69-
</FormItem>
70-
)}
71-
/>
72-
<div className='mt-4 flex justify-end gap-2'>
73-
<Button variant='outline' onClick={signOut}>
74-
{t('common.sign-out')}
75-
</Button>
76-
<Button
77-
type='submit'
78-
disabled={isCreating}
79-
aria-disabled={isCreating}
80-
data-testid='guestbook-submit-button'
81-
>
82-
{t('guestbook.submit')}
83-
</Button>
84-
</div>
85-
</form>
86-
</Form>
63+
<form onSubmit={handleSubmit} className='w-full'>
64+
<FieldGroup>
65+
<form.Field name='message'>
66+
{(field) => {
67+
const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid
68+
69+
return (
70+
<Field data-invalid={isInvalid}>
71+
<Textarea
72+
id={field.name}
73+
name={field.name}
74+
value={field.state.value}
75+
onBlur={field.handleBlur}
76+
onChange={(e) => {
77+
field.handleChange(e.target.value)
78+
}}
79+
aria-invalid={isInvalid}
80+
placeholder={t('guestbook.placeholder')}
81+
data-testid='guestbook-textarea'
82+
/>
83+
{isInvalid && <FieldError errors={field.state.meta.errors} />}
84+
</Field>
85+
)
86+
}}
87+
</form.Field>
88+
</FieldGroup>
89+
<div className='mt-4 flex justify-end gap-2'>
90+
<Button variant='outline' onClick={signOut}>
91+
{t('common.sign-out')}
92+
</Button>
93+
<Button type='submit' disabled={isCreating} aria-disabled={isCreating} data-testid='guestbook-submit-button'>
94+
{t('guestbook.submit')}
95+
</Button>
96+
</div>
97+
</form>
8798
</div>
8899
)
89100
}

packages/ui/eslint.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export default defineConfig({
99
},
1010
sonarjs: {
1111
'sonarjs/table-header': 'off'
12+
},
13+
react: {
14+
// They are OK in UI components
15+
'jsx-a11y/prefer-tag-over-role': 'off'
1216
}
1317
}
1418
})

packages/ui/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
"merge-refs": "2.0.0",
3535
"next-themes": "0.4.6",
3636
"radix-ui": "1.4.3",
37-
"react-hook-form": "7.65.0",
3837
"sonner": "2.0.7",
3938
"tailwind-merge": "3.3.1",
4039
"vaul": "1.1.2"

0 commit comments

Comments
 (0)