You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 10:05:56