replace formik with react-hook-form

This commit is contained in:
秦秋旭 2023-03-02 17:24:03 +08:00
parent f6abf2bc38
commit c14a2bc082
6 changed files with 137 additions and 164 deletions

View File

@ -14,15 +14,16 @@
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",
"@fontsource/roboto": "^4.5.8",
"@hookform/resolvers": "^2.9.11",
"@mui/icons-material": "^5.11.9",
"@mui/material": "^5.11.9",
"@next/font": "13.1.6",
"axios": "^1.3.3",
"formik": "^2.2.9",
"http-status": "^1.6.2",
"next": "13.1.6",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.43.2",
"react-toastify": "^9.1.1",
"swr": "^2.0.3",
"yup": "^1.0.0"

View File

@ -4,6 +4,7 @@ specifiers:
'@emotion/react': ^11.10.6
'@emotion/styled': ^11.10.6
'@fontsource/roboto': ^4.5.8
'@hookform/resolvers': ^2.9.11
'@mui/icons-material': ^5.11.9
'@mui/material': ^5.11.9
'@next/font': 13.1.6
@ -14,7 +15,6 @@ specifiers:
eslint: ^8.34.0
eslint-config-next: ^13.1.6
eslint-config-prettier: ^8.6.0
formik: ^2.2.9
http-status: ^1.6.2
husky: ^8.0.0
lint-staged: ^13.1.2
@ -22,6 +22,7 @@ specifiers:
prettier: 2.8.4
react: 18.2.0
react-dom: 18.2.0
react-hook-form: ^7.43.2
react-toastify: ^9.1.1
swr: ^2.0.3
typescript: ^4.9.5
@ -31,15 +32,16 @@ dependencies:
'@emotion/react': 11.10.6_pmekkgnqduwlme35zpnqhenc34
'@emotion/styled': 11.10.6_oouaibmszuch5k64ms7uxp2aia
'@fontsource/roboto': 4.5.8
'@hookform/resolvers': 2.9.11_react-hook-form@7.43.2
'@mui/icons-material': 5.11.9_ofpk46txu7v2f5mzrtv4xsczka
'@mui/material': 5.11.9_xqeqsl5kvjjtyxwyi3jhw3yuli
'@next/font': 13.1.6
axios: 1.3.3
formik: 2.2.9_react@18.2.0
http-status: 1.6.2
next: 13.1.6_biqbaboplfbrettd7655fr4n2y
react: 18.2.0
react-dom: 18.2.0_react@18.2.0
react-hook-form: 7.43.2_react@18.2.0
react-toastify: 9.1.1_biqbaboplfbrettd7655fr4n2y
swr: 2.0.3_react@18.2.0
yup: 1.0.0
@ -243,6 +245,14 @@ packages:
resolution: {integrity: sha512-CnD7zLItIzt86q4Sj3kZUiLcBk1dSk81qcqgMGaZe7SQ1P8hFNxhMl5AZthK1zrDM5m74VVhaOpuMGIL4gagaA==}
dev: false
/@hookform/resolvers/2.9.11_react-hook-form@7.43.2:
resolution: {integrity: sha512-bA3aZ79UgcHj7tFV7RlgThzwSSHZgvfbt2wprldRkYBcMopdMvHyO17Wwp/twcJasNFischFfS7oz8Katz8DdQ==}
peerDependencies:
react-hook-form: ^7.0.0
dependencies:
react-hook-form: 7.43.2_react@18.2.0
dev: false
/@humanwhocodes/config-array/0.11.8:
resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
engines: {node: '>=10.10.0'}
@ -1104,11 +1114,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-lazy-prop/2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
@ -1668,21 +1673,6 @@ packages:
mime-types: 2.1.35
dev: false
/formik/2.2.9_react@18.2.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.2.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: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true
@ -2233,18 +2223,10 @@ packages:
p-locate: 5.0.0
dev: true
/lodash-es/4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
dev: false
/lodash.merge/4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
dev: true
/lodash/4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: false
/log-update/4.0.0:
resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
engines: {node: '>=10'}
@ -2629,8 +2611,13 @@ packages:
scheduler: 0.23.0
dev: false
/react-fast-compare/2.0.4:
resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==}
/react-hook-form/7.43.2_react@18.2.0:
resolution: {integrity: sha512-NvD3Oe2Y9hhqo2R4I4iJigDzSLpdMnzUpNMxlnzTbdiT7NT3BW0GxWCzEtwPudZMUPbZhNcSy1EcGAygyhDORg==}
engines: {node: '>=12.22.0'}
peerDependencies:
react: ^16.8.0 || ^17 || ^18
dependencies:
react: 18.2.0
dev: false
/react-is/16.13.1:
@ -3012,10 +2999,6 @@ packages:
globrex: 0.1.2
dev: true
/tiny-warning/1.0.3:
resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==}
dev: false
/to-fast-properties/2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
@ -3043,6 +3026,7 @@ packages:
/tslib/1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
dev: true
/tslib/2.5.0:
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}

View File

@ -1,6 +1,5 @@
import * as React from 'react'
import { useUser } from '@/utils/useUser'
import { useFormik } from 'formik'
import { useRouter } from 'next/router'
import Button from '@mui/material/Button'
import TextField from '@mui/material/TextField'
@ -16,6 +15,8 @@ import * as yup from '@/utils/validation'
import * as api from '@/api'
import { useCountdown, COUNTDOWN_SECONDS } from '@/utils/useCountdown'
import { toast } from 'react-toastify'
import { useForm, SubmitHandler } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
export function DeleteUser() {
const { user } = useUser()
@ -24,33 +25,33 @@ export function DeleteUser() {
const [confirm, setConfrim] = React.useState(false)
const { countdown, setCountdown } = useCountdown()
const formik = useFormik<DeleteUser>({
initialValues: {
const hookForm = useForm<DeleteUser>({
mode: 'onTouched',
defaultValues: {
email: user!.email,
password: '',
verifyCode: '',
token: '',
},
validationSchema: yup.object({
verifyCode: yup.verifyCodeSchema,
password: yup.string().required('请输入密码'),
}),
onSubmit: async (values) => {
await api.user.deleteUser(values)
localStorage.removeItem('accessToken')
toast.success('用户删除成功')
router.push('/login')
},
resolver: yupResolver(
yup.object({
verifyCode: yup.verifyCodeSchema,
password: yup.string().required('请输入密码'),
}),
),
})
const onSubmit: SubmitHandler<DeleteUser> = async (values) => {
await api.user.deleteUser(values)
localStorage.removeItem('accessToken')
toast.success('用户删除成功')
router.push('/login')
}
async function sendVerifyCode() {
setCountdown(COUNTDOWN_SECONDS)
try {
const res = await api.email.sendEmailVerifyCode({
email: formik.values.email,
email: hookForm.getValues('email'),
scene: EmailVerifyCodeScene.deleteUser,
})
formik.setFieldValue('token', res.data.token)
hookForm.setValue('token', res.data.token)
} catch (err) {
setCountdown(0)
}
@ -73,7 +74,7 @@ export function DeleteUser() {
<Grid container spacing={[2, 1]}>
<Grid xs={12}>
<TextField
{...formik.getFieldProps('email')}
{...hookForm.register('email')}
disabled
helperText=" "
fullWidth
@ -82,15 +83,14 @@ export function DeleteUser() {
</Grid>
<Grid xs={8}>
<TextField
{...formik.getFieldProps('verifyCode')}
{...hookForm.register('verifyCode')}
required
error={formik.touched.verifyCode && !!formik.errors.verifyCode}
error={!!hookForm.formState.errors.verifyCode}
helperText={
(formik.touched.verifyCode && formik.errors.verifyCode) || ' '
hookForm.formState.errors.verifyCode?.message || ' '
}
fullWidth
label="验证码"
autoComplete="verifyCode"
/>
</Grid>
<Grid xs={4}>
@ -107,12 +107,10 @@ export function DeleteUser() {
</Grid>
<Grid xs={12}>
<TextField
{...formik.getFieldProps('password')}
{...hookForm.register('password')}
required
error={formik.touched.password && !!formik.errors.password}
helperText={
(formik.touched.password && formik.errors.password) || ' '
}
error={!!hookForm.formState.errors.password}
helperText={hookForm.formState.errors.password?.message || ' '}
fullWidth
label="密码"
type="password"
@ -139,7 +137,7 @@ export function DeleteUser() {
</DialogContent>
<DialogActions>
<Button onClick={() => setConfrim(false)}></Button>
<Button color="error" onClick={formik.submitForm}>
<Button color="error" onClick={hookForm.handleSubmit(onSubmit)}>
</Button>
</DialogActions>

View File

@ -6,7 +6,6 @@ import Grid from '@mui/material/Unstable_Grid2'
import KeyIcon from '@mui/icons-material/Key'
import Typography from '@mui/material/Typography'
import Container from '@mui/material/Container'
import { useFormik } from 'formik'
import { useRouter } from 'next/router'
import * as yup from '@/utils/validation'
import * as api from '@/api'
@ -14,43 +13,42 @@ import { ForgetPasswordInputDto } from '@/api/user.interface'
import { EmailVerifyCodeScene } from '@/api/email.interface'
import { useCountdown, COUNTDOWN_SECONDS } from '@/utils/useCountdown'
import { toast } from 'react-toastify'
import { useForm, SubmitHandler } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
export default function ForgetPassword() {
const router = useRouter()
const { countdown, setCountdown } = useCountdown()
const formik = useFormik<ForgetPasswordInputDto>({
initialValues: {
email: '',
password: '',
verifyCode: '',
token: '',
userId: '',
},
validationSchema: yup.object({
email: yup.emailSchema,
password: yup.passwordSchema,
verifyCode: yup.verifyCodeSchema,
}),
onSubmit: async (values) => {
const res = await api.user.forgetPassword(values)
localStorage.setItem('accessToken', res.data)
toast.success('密码重置成功!', {
onClose: () => router.push('/login'),
})
},
const hookForm = useForm<ForgetPasswordInputDto>({
mode: 'onTouched',
resolver: yupResolver(
yup.object({
email: yup.emailSchema,
password: yup.passwordSchema,
verifyCode: yup.verifyCodeSchema,
}),
),
})
const onSubmit: SubmitHandler<ForgetPasswordInputDto> = async (values) => {
const res = await api.user.forgetPassword(values)
localStorage.setItem('accessToken', res.data)
toast.success('密码重置成功!', {
onClose: () => router.push('/login'),
})
}
async function sendVerifyCode() {
await formik.validateField('email')
const isValid = await hookForm.trigger('email')
if (!isValid) return
setCountdown(COUNTDOWN_SECONDS)
try {
const res = await api.email.sendEmailVerifyCode({
email: formik.values.email,
email: hookForm.getValues('email'),
scene: EmailVerifyCodeScene.forgetPassword,
})
formik.setFieldValue('token', res.data.token)
formik.setFieldValue('userId', res.data.userId)
hookForm.setValue('token', res.data.token)
hookForm.setValue('userId', res.data.userId || '')
} catch (err) {
setCountdown(0)
}
@ -77,14 +75,14 @@ export default function ForgetPassword() {
container
component="form"
spacing={[2, 1]}
onSubmit={formik.handleSubmit}
onSubmit={hookForm.handleSubmit(onSubmit)}
>
<Grid xs={12}>
<TextField
{...formik.getFieldProps('email')}
{...hookForm.register('email')}
required
error={formik.touched.email && !!formik.errors.email}
helperText={(formik.touched.email && formik.errors.email) || ' '}
error={!!hookForm.formState.errors.email}
helperText={hookForm.formState.errors.email?.message || ' '}
fullWidth
label="邮箱"
autoComplete="email"
@ -94,15 +92,12 @@ export default function ForgetPassword() {
</Grid>
<Grid xs={8}>
<TextField
{...formik.getFieldProps('verifyCode')}
{...hookForm.register('verifyCode')}
required
error={formik.touched.verifyCode && !!formik.errors.verifyCode}
helperText={
(formik.touched.verifyCode && formik.errors.verifyCode) || ' '
}
error={!!hookForm.formState.errors.verifyCode}
helperText={hookForm.formState.errors.verifyCode?.message || ' '}
fullWidth
label="验证码"
autoComplete="verifyCode"
/>
</Grid>
<Grid xs={4} mt={1}>
@ -118,12 +113,10 @@ export default function ForgetPassword() {
</Grid>
<Grid xs={12}>
<TextField
{...formik.getFieldProps('password')}
{...hookForm.register('password')}
required
error={formik.touched.password && !!formik.errors.password}
helperText={
(formik.touched.password && formik.errors.password) || ' '
}
error={!!hookForm.formState.errors.password}
helperText={hookForm.formState.errors.password?.message || ' '}
fullWidth
label="新密码"
type="password"

View File

@ -7,29 +7,34 @@ import Box from '@mui/material/Box'
import LoginIcon from '@mui/icons-material/Login'
import Typography from '@mui/material/Typography'
import Container from '@mui/material/Container'
import { useFormik } from 'formik'
import { useRouter } from 'next/router'
import * as yup from '@/utils/validation'
import * as api from '@/api'
import { LoginInputDto } from '@/api/user.interface'
import { useForm, SubmitHandler } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
export default function Login() {
const router = useRouter()
const formik = useFormik({
initialValues: {
const hookForm = useForm<LoginInputDto>({
mode: 'onTouched',
defaultValues: {
email: '',
password: '',
},
validationSchema: yup.object({
email: yup.emailSchema,
password: yup.string().required('请输入密码'),
}),
onSubmit: async (values) => {
const res = await api.user.login(values)
localStorage.setItem('accessToken', res.data)
router.push('/')
},
resolver: yupResolver(
yup.object({
email: yup.emailSchema,
password: yup.string().required('请输入密码'),
}),
),
})
const onSubmit: SubmitHandler<LoginInputDto> = async (values) => {
const res = await api.user.login(values)
localStorage.setItem('accessToken', res.data)
router.push('/')
}
return (
<Container
@ -48,12 +53,12 @@ export default function Login() {
<Typography component="h1" variant="h5" mt={1} mb={3}>
</Typography>
<Box component="form" onSubmit={formik.handleSubmit}>
<Box component="form" onSubmit={hookForm.handleSubmit(onSubmit)}>
<TextField
{...formik.getFieldProps('email')}
{...hookForm.register('email')}
required
error={formik.touched.email && !!formik.errors.email}
helperText={(formik.touched.email && formik.errors.email) || ' '}
error={!!hookForm.formState.errors.email}
helperText={hookForm.formState.errors.email?.message || ' '}
fullWidth
label="邮箱"
autoComplete="email"
@ -61,12 +66,10 @@ export default function Login() {
sx={{ mt: 1 }}
/>
<TextField
{...formik.getFieldProps('password')}
{...hookForm.register('password')}
required
error={formik.touched.password && !!formik.errors.password}
helperText={
(formik.touched.password && formik.errors.password) || ' '
}
error={!!hookForm.formState.errors.password}
helperText={hookForm.formState.errors.password?.message || ' '}
fullWidth
label="密码"
type="password"

View File

@ -6,46 +6,45 @@ import Grid from '@mui/material/Unstable_Grid2'
import AppRegistrationIcon from '@mui/icons-material/AppRegistration'
import Typography from '@mui/material/Typography'
import Container from '@mui/material/Container'
import { useFormik } from 'formik'
import { useRouter } from 'next/router'
import * as yup from '@/utils/validation'
import * as api from '@/api'
import { RegisterInputDto } from '@/api/user.interface'
import { EmailVerifyCodeScene } from '@/api/email.interface'
import { useCountdown, COUNTDOWN_SECONDS } from '@/utils/useCountdown'
import { useForm, SubmitHandler } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
export default function Register() {
const router = useRouter()
const { countdown, setCountdown } = useCountdown()
const formik = useFormik<RegisterInputDto>({
initialValues: {
email: '',
password: '',
verifyCode: '',
token: '',
},
validationSchema: yup.object({
email: yup.emailSchema,
password: yup.passwordSchema,
verifyCode: yup.verifyCodeSchema,
}),
onSubmit: async (values) => {
const res = await api.user.register(values)
localStorage.setItem('accessToken', res.data)
router.push('/')
},
const hookForm = useForm<RegisterInputDto>({
mode: 'onTouched',
resolver: yupResolver(
yup.object({
email: yup.emailSchema,
password: yup.passwordSchema,
verifyCode: yup.verifyCodeSchema,
}),
),
})
const onSubmit: SubmitHandler<RegisterInputDto> = async (values) => {
const res = await api.user.register(values)
localStorage.setItem('accessToken', res.data)
router.push('/')
}
async function sendVerifyCode() {
await formik.validateField('email')
const isValid = await hookForm.trigger('email')
if (!isValid) return
setCountdown(COUNTDOWN_SECONDS)
try {
const res = await api.email.sendEmailVerifyCode({
email: formik.values.email,
email: hookForm.getValues('email'),
scene: EmailVerifyCodeScene.register,
})
formik.setFieldValue('token', res.data.token)
hookForm.setValue('token', res.data.token)
} catch (err) {
setCountdown(0)
}
@ -72,14 +71,14 @@ export default function Register() {
container
component="form"
spacing={[2, 1]}
onSubmit={formik.handleSubmit}
onSubmit={hookForm.handleSubmit(onSubmit)}
>
<Grid xs={12}>
<TextField
{...formik.getFieldProps('email')}
{...hookForm.register('email')}
required
error={formik.touched.email && !!formik.errors.email}
helperText={(formik.touched.email && formik.errors.email) || ' '}
error={!!hookForm.formState.errors.email}
helperText={hookForm.formState.errors.email?.message || ' '}
fullWidth
label="邮箱"
autoComplete="email"
@ -89,15 +88,12 @@ export default function Register() {
</Grid>
<Grid xs={8}>
<TextField
{...formik.getFieldProps('verifyCode')}
{...hookForm.register('verifyCode')}
required
error={formik.touched.verifyCode && !!formik.errors.verifyCode}
helperText={
(formik.touched.verifyCode && formik.errors.verifyCode) || ' '
}
error={!!hookForm.formState.errors.verifyCode}
helperText={hookForm.formState.errors.verifyCode?.message || ' '}
fullWidth
label="验证码"
autoComplete="verifyCode"
/>
</Grid>
<Grid xs={4} mt={1}>
@ -113,14 +109,12 @@ export default function Register() {
</Grid>
<Grid xs={12}>
<TextField
{...formik.getFieldProps('password')}
{...hookForm.register('password')}
required
error={formik.touched.password && !!formik.errors.password}
helperText={
(formik.touched.password && formik.errors.password) || ' '
}
error={!!hookForm.formState.errors.password}
helperText={hookForm.formState.errors.password?.message || ' '}
fullWidth
label="密码"
label="密码"
type="password"
autoComplete="current-password"
/>