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/react": "^11.10.6",
"@emotion/styled": "^11.10.6", "@emotion/styled": "^11.10.6",
"@fontsource/roboto": "^4.5.8", "@fontsource/roboto": "^4.5.8",
"@hookform/resolvers": "^2.9.11",
"@mui/icons-material": "^5.11.9", "@mui/icons-material": "^5.11.9",
"@mui/material": "^5.11.9", "@mui/material": "^5.11.9",
"@next/font": "13.1.6", "@next/font": "13.1.6",
"axios": "^1.3.3", "axios": "^1.3.3",
"formik": "^2.2.9",
"http-status": "^1.6.2", "http-status": "^1.6.2",
"next": "13.1.6", "next": "13.1.6",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.43.2",
"react-toastify": "^9.1.1", "react-toastify": "^9.1.1",
"swr": "^2.0.3", "swr": "^2.0.3",
"yup": "^1.0.0" "yup": "^1.0.0"

View File

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

View File

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

View File

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

View File

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

View File

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