React登录表单验证失效:未输入账号密码仍可登录问题求助
React登录表单验证失效及登录逻辑修复方案
问题描述
我开发了一个React登录认证界面,当前运行不符合预期:点击“Connexion(登录)”按钮时,即便未在输入框中填写任何账号密码信息,仍能成功完成登录。已添加yup schema验证,但验证功能未生效。使用fake store API生成假token并存储在localStorage中,以下是LoginForm组件及App组件代码:
原LoginForm组件代码
import React, { useState } from "react"; import { Box, Button, Checkbox, FormControlLabel, TextField, Typography, } from "@mui/material"; import axios from "axios"; import { LoginFormProps } from "../types/loginFormProps"; import { Controller, useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import ErrorMessage from "./ErrorMessage"; const schema = yup.object({ username: yup .string() .required("Ce champ est obligatoire") .max(40, "Moins de 40 stp"), password: yup .string() .required("Ce champ est obligatoire") .email("Email invalide"), }); const LoginForm: React.FC<LoginFormProps> = ({ token, setToken }) => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const loginHandler = () => { axios({ url: "https://fakestoreapi.com/auth/login", method: "POST", headers: { "Content-Type": "application/json", }, data: { username: "mor_2314", password: "83r5^_", }, }) .then((res) => { console.log(res.data.token); setToken(res.data.token); localStorage.setItem("userToken", res.data.token); }) .catch((error) => { console.log(error.response); setError(error.response.data); }); }; const { control, formState: { errors }, } = useForm({ defaultValues: { username: "", password: "", }, resolver: yupResolver(schema), }); console.log(schema); return ( <form onSubmit={loginHandler}> <Box sx={{ display: "flex", flexDirection: "column", mt: "15rem", alignItems: "start", pl: "20rem", pr: "20rem", borderRadius: "11px", background: "white", width: "35%", padding: "2rem", margin: "20rem", }} > <Box> <Typography variant="h2" sx={{ fontSize: "2rem", mb: "1rem", color: "#666d92", }} > Accédez à{" "} </Typography> <Typography variant="h1" sx={{ fontSize: "2.5rem", color: "#0909ff", }} > Tuto-React </Typography> </Box> {/* FORM */} <Box sx={{ width: "100%", mt: "2rem" }}> <Controller name="username" control={control} render={({ field }) => ( <TextField {...field} value={username} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setUsername(e.target.value) } name="username" label="Email" type="text" fullWidth sx={{ mb: "1rem" }} error={!!errors.username} /> )} /> {errors.username && ( <ErrorMessage message={errors.username?.message} /> )} <Controller name="password" control={control} render={({ field }) => ( <TextField {...field} value={password} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setPassword(e.target.value) } name="password" label="Mot de passe" type="password" fullWidth sx={{ mb: "1rem" }} error={!!errors.password} /> )} /> {errors.password && ( <ErrorMessage message={errors.password?.message} /> )} <Box sx={{ ml: "1rem" }}> <FormControlLabel control={<Checkbox />} label="Se souvenir de moi" /> </Box> <Button onClick={loginHandler} fullWidth sx={{ mt: "1rem" }}> Connexion </Button> </Box> </Box> </form> ); }; export default LoginForm;
原App组件代码
import { ThemeProvider } from "@mui/material/styles"; import CssBaseline from "@mui/material/CssBaseline"; import { theme } from "@docaposte-agility/da-design-system"; import Listing from "./components/Listing"; import { Container } from "@mui/material"; import LoginForm from "./components/LoginForm"; import { useState } from "react"; import NavBar from "./components/NavBar"; function App() { const [token, setToken] = useState(localStorage.getItem("userToken") ?? null); return ( <> <ThemeProvider theme={theme}> <CssBaseline /> {token ? <NavBar setToken={setToken} /> : null} <Container maxWidth="md"> {token ? ( <Listing /> ) : ( <LoginForm token={token} setToken={setToken} password={""} email={""} /> )} </Container> </ThemeProvider> </> ); } export default App;
问题根源及修复步骤
1. 移除手动维护的表单状态
同时使用useState维护username/password和react-hook-form的Controller会导致状态冲突,验证逻辑无法感知输入变化。删除自定义的useState,完全依赖react-hook-form管理表单状态。
2. 正确触发表单验证与提交
- 登录按钮不要用
onClick,改为设置type="submit",让表单的onSubmit绑定react-hook-form的handleSubmit方法,确保先执行验证,验证通过后再调用登录逻辑。 - 从
useForm中解构出handleSubmit方法。
3. 修正登录请求参数
当前登录请求使用硬编码的固定账号密码,替换为表单中用户实际输入的值,通过handleSubmit回调的参数获取表单数据。
4. 修复密码字段的yup验证规则
密码字段错误使用了.email()验证规则,替换为符合密码要求的规则(比如最小长度限制)。
5. 移除TextField上的手动value和onChange
Controller的field属性已经包含了value和onChange,手动覆盖会导致react-hook-form无法追踪输入变化,验证失效。
修复后的LoginForm组件代码
import React, { useState } from "react"; import { Box, Button, Checkbox, FormControlLabel, TextField, Typography, } from "@mui/material"; import axios from "axios"; import { LoginFormProps } from "../types/loginFormProps"; import { Controller, useForm } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; import * as yup from "yup"; import ErrorMessage from "./ErrorMessage"; // 修正密码验证规则 const schema = yup.object({ username: yup .string() .required("Ce champ est obligatoire") .max(40, "Moins de 40 stp"), password: yup .string() .required("Ce champ est obligatoire") .min(6, "Le mot de passe doit contenir au moins 6 caractères"), }); const LoginForm: React.FC<LoginFormProps> = ({ token, setToken }) => { // 删除手动维护的username/password状态 const [error, setError] = useState(""); const { control, formState: { errors }, handleSubmit, // 解构handleSubmit方法 } = useForm({ defaultValues: { username: "", password: "", }, resolver: yupResolver(schema), }); // 接收表单数据作为参数 const loginHandler = (data: { username: string; password: string }) => { axios({ url: "https://fakestoreapi.com/auth/login", method: "POST", headers: { "Content-Type": "application/json", }, // 使用用户实际输入的表单数据 data: { username: data.username, password: data.password, }, }) .then((res) => { console.log(res.data.token); setToken(res.data.token); localStorage.setItem("userToken", res.data.token); }) .catch((error) => { console.log(error.response); setError(error.response.data); }); }; return ( // 绑定handleSubmit,触发验证后执行登录逻辑 <form onSubmit={handleSubmit(loginHandler)}> <Box sx={{ display: "flex", flexDirection: "column", mt: "15rem", alignItems: "start", pl: "20rem", pr: "20rem", borderRadius: "11px", background: "white", width: "35%", padding: "2rem", margin: "20rem", }} > <Box> <Typography variant="h2" sx={{ fontSize: "2rem", mb: "1rem", color: "#666d92", }} > Accédez à{" "} </Typography> <Typography variant="h1" sx={{ fontSize: "2.5rem", color: "#0909ff", }} > Tuto-React </Typography> </Box> {/* FORM */} <Box sx={{ width: "100%", mt: "2rem" }}> <Controller name="username" control={control} render={({ field }) => ( <TextField {...field} name="username" label="Email" type="text" fullWidth sx={{ mb: "1rem" }} error={!!errors.username} /> )} /> {errors.username && ( <ErrorMessage message={errors.username?.message} /> )} <Controller name="password" control={control} render={({ field }) => ( <TextField {...field} name="password" label="Mot de passe" type="password" fullWidth sx={{ mb: "1rem" }} error={!!errors.password} /> )} /> {errors.password && ( <ErrorMessage message={errors.password?.message} /> )} <Box sx={{ ml: "1rem" }}> <FormControlLabel control={<Checkbox />} label="Se souvenir de moi" /> </Box> {/* 设置为submit类型,由表单触发提交 */} <Button type="submit" fullWidth sx={{ mt: "1rem" }}> Connexion </Button> </Box> </Box> </form> ); }; export default LoginForm;
额外说明
- App组件无需修改,但需确保
LoginFormProps类型定义中移除password和email参数(因为表单状态不再通过props传递)。 - 修复后,点击登录按钮会先执行yup验证,验证不通过时会显示错误提示,无法触发登录请求;验证通过后才会调用fake store API获取token。
内容的提问来源于stack exchange,提问作者fifietcie
相关产品推荐
相关产品推荐

