use react-hook-form

This commit is contained in:
EGOIST 2022-05-12 13:47:10 +08:00
parent 744a47f416
commit baa478a2bf
9 changed files with 122 additions and 181 deletions

View File

@ -31,7 +31,6 @@
"dayjs": "^1.11.1",
"dotenv": "^16.0.0",
"form-data": "^4.0.0",
"formik": "^2.2.9",
"mailgun.js": "^5.2.2",
"markdown-it": "^13.0.0",
"nanoid": "^3.3.3",
@ -40,6 +39,7 @@
"react": "^18.1.0",
"react-avatar-editor": "^12.0.0",
"react-dom": "^18.1.0",
"react-hook-form": "^7.31.1",
"react-hot-toast": "^2.2.0",
"react-query": "^3.39.0",
"ua-parser-js": "^1.0.2",

View File

@ -35,7 +35,6 @@ specifiers:
eslint: ^8.15.0
eslint-config-next: ^12.1.6
form-data: ^4.0.0
formik: ^2.2.9
mailgun.js: ^5.2.2
markdown-it: ^13.0.0
nanoid: ^3.3.3
@ -46,6 +45,7 @@ specifiers:
react: ^18.1.0
react-avatar-editor: ^12.0.0
react-dom: ^18.1.0
react-hook-form: ^7.31.1
react-hot-toast: ^2.2.0
react-query: ^3.39.0
tailwindcss: ^3.0.24
@ -77,7 +77,6 @@ dependencies:
dayjs: 1.11.2
dotenv: 16.0.0
form-data: 4.0.0
formik: 2.2.9_react@18.1.0
mailgun.js: 5.2.2
markdown-it: 13.0.1
nanoid: 3.3.4
@ -86,6 +85,7 @@ dependencies:
react: 18.1.0
react-avatar-editor: 12.0.0_ef5jwxihqo6n7gxfmzogljlgcm
react-dom: 18.1.0_react@18.1.0
react-hook-form: 7.31.1_react@18.1.0
react-hot-toast: 2.2.0_ef5jwxihqo6n7gxfmzogljlgcm
react-query: 3.39.0_ef5jwxihqo6n7gxfmzogljlgcm
ua-parser-js: 1.0.2
@ -2211,11 +2211,6 @@ packages:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true
/deepmerge/2.2.1:
resolution: {integrity: sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==}
engines: {node: '>=0.10.0'}
dev: false
/define-properties/1.1.4:
resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==}
engines: {node: '>= 0.4'}
@ -2726,21 +2721,6 @@ packages:
mime-types: 2.1.35
dev: false
/formik/2.2.9_react@18.1.0:
resolution: {integrity: sha512-LQLcISMmf1r5at4/gyJigGn0gOwFbeEAlji+N9InZF6LIMXnFNkO42sCI8Jt84YZggpD4cPWObAZaxpEFtSzNA==}
peerDependencies:
react: '>=16.8.0'
dependencies:
deepmerge: 2.2.1
hoist-non-react-statics: 3.3.2
lodash: 4.17.21
lodash-es: 4.17.21
react: 18.1.0
react-fast-compare: 2.0.4
tiny-warning: 1.0.3
tslib: 1.14.1
dev: false
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
@ -2888,12 +2868,6 @@ packages:
dependencies:
function-bind: 1.1.1
/hoist-non-react-statics/3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
dependencies:
react-is: 16.13.1
dev: false
/ignore/5.2.0:
resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==}
engines: {node: '>= 4'}
@ -3169,10 +3143,6 @@ packages:
path-exists: 3.0.0
dev: true
/lodash-es/4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: false
/lodash.debounce/4.0.8:
resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=}
dev: false
@ -3181,10 +3151,6 @@ packages:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
/loose-envify/1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@ -3651,8 +3617,13 @@ packages:
scheduler: 0.22.0
dev: false
/react-fast-compare/2.0.4:
resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==}
/react-hook-form/7.31.1_react@18.1.0:
resolution: {integrity: sha512-QjtjZ8r8KtEBWWpcXLyQordCraTFxILtyQpaz5KLLxN2YzcC+FZ9LLtOnNGuOnzZo9gCoB+viK3ZHV9Mb2htmQ==}
engines: {node: '>=12.22.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18
dependencies:
react: 18.1.0
dev: false
/react-hot-toast/2.2.0_ef5jwxihqo6n7gxfmzogljlgcm:
@ -3964,10 +3935,6 @@ packages:
resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}
dev: true
/tiny-warning/1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/to-fast-properties/2.0.0:
resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=}
engines: {node: '>=4'}

View File

@ -1,9 +1,8 @@
import { Button } from "~/components/ui/Button"
import { useStore } from "~/lib/store"
import { Dialog } from "@headlessui/react"
import { useEffect, useState } from "react"
import { trpc } from "~/lib/trpc"
import { useFormik } from "formik"
import { useForm } from "react-hook-form"
export const LoginModal: React.FC = () => {
const [loginModalOpened, setLoginModalOpened] = useStore((store) => [
@ -12,16 +11,17 @@ export const LoginModal: React.FC = () => {
])
const requestLoginLink = trpc.useMutation("auth.requestLoginLink")
const form = useFormik({
initialValues: {
const form = useForm({
defaultValues: {
email: "",
},
async onSubmit(values) {
await requestLoginLink.mutateAsync({
email: values.email,
url: location.href,
})
},
})
const handleSubmit = form.handleSubmit((values) => {
requestLoginLink.mutate({
email: values.email,
url: location.href,
})
})
return (
@ -45,7 +45,7 @@ export const LoginModal: React.FC = () => {
inbox and spam folder in case you can{`'`}t find it.
</div>
)}
<form className="space-y-5" onSubmit={form.handleSubmit}>
<form className="space-y-5" onSubmit={handleSubmit}>
<div>
<label
className="block mb-1 font-medium text-zinc-600"
@ -55,11 +55,9 @@ export const LoginModal: React.FC = () => {
</label>
<input
id="email"
name="email"
type="email"
className="input is-block"
value={form.values.email}
onChange={form.handleChange}
{...form.register("email")}
/>
</div>
<div>

View File

@ -1,11 +1,11 @@
import React, { useEffect } from "react"
import { useFormik } from "formik"
import { useStore } from "~/lib/store"
import { Button } from "../ui/Button"
import { Modal } from "../ui/Modal"
import toast from "react-hot-toast"
import { trpc } from "~/lib/trpc"
import { Input } from "../ui/Input"
import { useForm } from "react-hook-form"
export const SubscribeModal: React.FC<{
siteId: string
@ -23,25 +23,26 @@ export const SubscribeModal: React.FC<{
const unsubscribe = trpc.useMutation("site.unsubscribe")
const trpcContext = trpc.useContext()
const subscribeForm = useFormik({
initialValues: {
const subscribeForm = useForm({
defaultValues: {
newUserEmail: "",
email: subscription?.email ?? true,
telegram: subscription?.telegram ?? false,
},
onSubmit(values) {
subscribe.mutate({
siteId,
email: values.email,
telegram: values.telegram,
newUser: isLoggedIn
? undefined
: {
email: values.newUserEmail,
url: location.href,
},
})
},
})
const handleSubscribe = subscribeForm.handleSubmit((values) => {
subscribe.mutate({
siteId,
email: values.email,
telegram: values.telegram,
newUser: isLoggedIn
? undefined
: {
email: values.newUserEmail,
url: location.href,
},
})
})
useEffect(() => {
@ -70,40 +71,28 @@ export const SubscribeModal: React.FC<{
<p>Please check your inbox (and spam folder).</p>
</div>
) : (
<form className="p-5" onSubmit={subscribeForm.handleSubmit}>
<form className="p-5" onSubmit={handleSubscribe}>
{!isLoggedIn && (
<div className="mb-5">
<Input
label="Email"
name="newUserEmail"
type="email"
id="email"
isBlock
value={subscribeForm.values.newUserEmail}
onChange={subscribeForm.handleChange}
required
{...subscribeForm.register("newUserEmail", {})}
/>
</div>
)}
<div>
<label className="select-none flex items-center space-x-1">
<input
type="checkbox"
checked={subscribeForm.values.email}
onChange={(e) =>
subscribeForm.setFieldValue("email", e.target.checked)
}
/>
<input type="checkbox" {...subscribeForm.register("email")} />
<span>Receive updates via Email</span>
</label>
</div>
<div className="">
<label className="select-none flex items-center space-x-1">
<input
type="checkbox"
checked={subscribeForm.values.telegram}
onChange={(e) =>
subscribeForm.setFieldValue("telegram", e.target.checked)
}
/>
<input type="checkbox" {...subscribeForm.register("telegram")} />
<span>Receive updates via Telegram</span>
</label>
</div>

View File

@ -1,14 +1,20 @@
import clsx from "clsx"
import { DetailedHTMLProps, InputHTMLAttributes } from "react"
import { DetailedHTMLProps, forwardRef, InputHTMLAttributes } from "react"
export const Input: React.FC<
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> & {
label?: string
addon?: string
isBlock?: boolean
error?: string
}
> = ({ label, addon, className, isBlock, error, ...inputProps }) => {
type Props = DetailedHTMLProps<
InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
> & {
label?: string
addon?: string
isBlock?: boolean
error?: string
}
export const Input = forwardRef<HTMLInputElement, Props>(function Input(
{ label, addon, className, isBlock, error, ...inputProps },
ref
) {
const hasAddon = !!addon
return (
<div>
@ -20,6 +26,7 @@ export const Input: React.FC<
<div className="flex items-center">
<input
{...inputProps}
ref={ref}
className={clsx(
"input",
hasAddon && `has-addon`,
@ -36,4 +43,4 @@ export const Input: React.FC<
{error && <div className="text-sm mt-1 text-red-500">{error}</div>}
</div>
)
}
})

View File

@ -1,5 +1,5 @@
import { useFormik } from "formik"
import { useEffect } from "react"
import { useForm } from "react-hook-form"
import toast from "react-hot-toast"
import { AvatarForm } from "~/components/dashboard/AvatarForm"
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
@ -12,16 +12,17 @@ export default function AccountProfilePage() {
const viewer = trpc.useQuery(["auth.viewer"])
const updateProfile = trpc.useMutation("user.updateProfile")
const form = useFormik({
initialValues: {
const form = useForm({
defaultValues: {
name: "",
username: "",
bio: "",
email: "",
},
onSubmit(values) {
updateProfile.mutate(values)
},
})
const handleSubmit = form.handleSubmit((values) => {
updateProfile.mutate(values)
})
useEffect(() => {
@ -33,12 +34,10 @@ export default function AccountProfilePage() {
useEffect(() => {
if (viewer.data) {
form.setValues({
name: viewer.data.name,
username: viewer.data.username,
bio: viewer.data.bio || "",
email: viewer.data.email,
})
form.setValue("name", viewer.data.name)
form.setValue("username", viewer.data.username)
form.setValue("bio", viewer.data.bio || "")
form.setValue("email", viewer.data.email || "")
}
}, [viewer.data, form])
@ -51,38 +50,32 @@ export default function AccountProfilePage() {
<AvatarForm filename={viewer.data.avatar} name={viewer.data.name} />
</div>
)}
<form onSubmit={form.handleSubmit}>
<form onSubmit={handleSubmit}>
<div className="mt-5">
<Input
label="Display Name"
id="name"
name="name"
required
type="text"
value={form.values.name}
onChange={form.handleChange}
{...form.register("name")}
/>
</div>
<div className="mt-5">
<Input
label="Username"
id="username"
name="username"
required
type="text"
value={form.values.username}
onChange={form.handleChange}
{...form.register("username")}
/>
</div>
<div className="mt-5">
<Input
label="Email"
id="email"
name="email"
required
type="email"
value={form.values.email}
onChange={form.handleChange}
{...form.register("email")}
/>
</div>
<div className="mt-10">

View File

@ -1,6 +1,6 @@
import { useFormik } from "formik"
import { useRouter } from "next/router"
import { useEffect } from "react"
import { useForm } from "react-hook-form"
import toast from "react-hot-toast"
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
@ -18,16 +18,17 @@ export default function SettingsDomainsPage() {
const ctx = trpc.useContext()
const updateSite = trpc.useMutation("site.updateSite")
const form = useFormik({
initialValues: {
const form = useForm({
defaultValues: {
subdomain: "",
},
onSubmit(values) {
updateSite.mutate({
site: siteResult.data!.id,
subdomain: values.subdomain,
})
},
})
const handleSubmit = form.handleSubmit((values) => {
updateSite.mutate({
site: siteResult.data!.id,
subdomain: values.subdomain,
})
})
useEffect(() => {
@ -45,25 +46,21 @@ export default function SettingsDomainsPage() {
useEffect(() => {
if (siteResult.data) {
form.setValues({
subdomain: siteResult.data.subdomain,
})
form.setValue("subdomain", siteResult.data.subdomain)
}
}, [form, siteResult.data])
return (
<DashboardLayout>
<SettingsLayout title="Site Settings" type="site">
<form onSubmit={form.handleSubmit}>
<form onSubmit={handleSubmit}>
<div className="">
<Input
name="subdomain"
id="subdomain"
label="Subdomain"
addon={`.${OUR_DOMAIN}`}
value={form.values.subdomain}
className="w-28"
onChange={form.handleChange}
{...form.register("subdomain")}
error={updateSite.error?.message}
/>
</div>

View File

@ -7,7 +7,7 @@ import { SettingsLayout } from "~/components/dashboard/SettingsLayout"
import { useRouter } from "next/router"
import { DashboardLayout } from "~/components/dashboard/DashboardLayout"
import { trpc } from "~/lib/trpc"
import { useFormik } from "formik"
import { useForm } from "react-hook-form"
export default function SiteSettingsGeneralPage() {
const router = useRouter()
@ -20,28 +20,27 @@ export default function SiteSettingsGeneralPage() {
const site = siteResult.data
const updateSite = trpc.useMutation("site.updateSite")
const form = useFormik({
initialValues: {
const form = useForm({
defaultValues: {
name: "",
description: "",
},
onSubmit(values) {
updateSite.mutate({
site: subdomain,
name: values.name,
description: values.description,
})
},
})
const handleSubmit = form.handleSubmit((values) => {
updateSite.mutate({
site: subdomain,
name: values.name,
description: values.description,
})
})
useEffect(() => {
if (site) {
form.setValues({
name: site.name,
description: site.description || "",
})
form.setValue("name", site.name)
form.setValue("description", site.description || "")
}
}, [form, site])
}, [site, form])
useEffect(() => {
if (updateSite.isSuccess && updateSite.data) {
@ -63,16 +62,9 @@ export default function SiteSettingsGeneralPage() {
/>
</div>
)}
<form onSubmit={form.handleSubmit}>
<form onSubmit={handleSubmit}>
<div className="mt-5">
<Input
required
label="Name"
id="name"
name="name"
value={form.values.name}
onChange={form.handleChange}
/>
<Input required label="Name" id="name" {...form.register("name")} />
</div>
<div className="mt-5">
<label htmlFor="description" className="label">
@ -81,10 +73,8 @@ export default function SiteSettingsGeneralPage() {
<textarea
id="description"
className="input is-block"
name="description"
rows={6}
value={form.values.description}
onChange={form.handleChange}
{...form.register("description")}
/>
</div>
<div className="mt-5">

View File

@ -1,7 +1,7 @@
import { useFormik } from "formik"
import Link from "next/link"
import { useRouter } from "next/router"
import { useEffect } from "react"
import { useForm } from "react-hook-form"
import toast from "react-hot-toast"
import { Button } from "~/components/ui/Button"
import { Input } from "~/components/ui/Input"
@ -13,14 +13,15 @@ export default function NewSitePage() {
const viewer = trpc.useQuery(["auth.viewer"])
const createSite = trpc.useMutation("site.create")
const form = useFormik({
initialValues: {
const form = useForm({
defaultValues: {
name: "",
subdomain: "",
},
onSubmit(values) {
createSite.mutate(values)
},
})
const handleSubmit = form.handleSubmit((values) => {
createSite.mutate(values)
})
useEffect(() => {
@ -60,28 +61,27 @@ export default function NewSitePage() {
</header>
<div className="max-w-sm mx-auto mt-20">
<h2 className="text-3xl mb-10 text-center">Create a new site</h2>
<form className="space-y-5" onSubmit={form.handleSubmit}>
<form className="space-y-5" onSubmit={handleSubmit}>
<div>
<Input
name="name"
id="name"
label="Site Name"
required
isBlock
value={form.values.name}
onChange={form.handleChange}
required
maxLength={30}
{...form.register("name", {})}
/>
</div>
<div>
<Input
name="subdomain"
id="subdomain"
label="Subdomain"
required
isBlock
required
addon={`.${OUR_DOMAIN}`}
value={form.values.subdomain}
onChange={form.handleChange}
minLength={2}
maxLength={20}
{...form.register("subdomain", {})}
/>
</div>
<div>