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

ReactJS登录验证出现无效Hook调用问题求助

解决React中"无效Hook调用"的问题

首先,咱们来定位你代码里的核心问题:

错误根源

你最后一行的导出写法完全错误:

export default LoginForm(useStyles)(Form);

useStyles是一个React Hook,必须在函数组件的主体内部顶层调用,不能像普通工具函数那样传递给组件。而且你把withFormik包装后的Form组件反过来传给LoginForm,这完全搞反了组件的包装逻辑。

另外还有几个小问题:

  • signIn函数里直接用values会报错,因为values是Formik注入到组件的props,需要从props中解构获取
  • 按钮的onClick绑定了signIn,但Formik已经提供了handleSubmit来处理表单提交,应该统一用Formik的提交流程

修正后的完整代码

import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { makeStyles } from '@material-ui/styles';
import { Card, CardHeader, CardContent, CardActions, Divider, Button, TextField, InputAdornment } from '@material-ui/core';
import { useSelector, useDispatch } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { login } from '../../../actions';
import AccountCircle from '@material-ui/icons/AccountCircle';
import LockIcon from '@material-ui/icons/Lock';
import { withFormik } from 'formik';
import * as yup from 'yup'; // 改成import方式更规范

const useStyles = makeStyles((theme) => ({
  root: {},
  cardHeader: {
    backgroundColor: theme.palette.primary.main,
    color: 'white',
    display: 'flex',
    justifyContent: 'center',
    fontSize: '2rem',
    padding: '15px',
  },
  textFieldSection: {
    padding: '40px 40px 0 40px',
  },
  loginButtonSection: {
    padding: '18px 40px 40px 40px',
  },
  loginButton: {
    width: '100%',
    height: '50px',
    textTransform: 'none',
    fontSize: '18px',
  },
}));

const LoginForm = (props) => {
  // 在这里调用useStyles Hook,这是正确的位置
  const classes = useStyles();
  const { className, values, touched, errors, isSubmitting, handleChange, handleBlur, handleSubmit } = props;
  const isLoggedIn = useSelector((state) => state.auth.isLoggedIn);
  const referer = props.referer;
  const dispatch = useDispatch();

  if (isLoggedIn) {
    return <Redirect to={referer} />;
  }

  return (
    <Card className={clsx(classes.root, className)}>
      <form onSubmit={handleSubmit}>
        <CardHeader
          title="LOGIN"
          classes={{ title: classes.cardHeader }}
          className={classes.cardHeader}
        />
        <CardContent className={classes.textFieldSection}>
          <TextField
            fullWidth
            label="Username"
            name="username"
            type="text"
            variant="outlined"
            value={values.username}
            onChange={handleChange}
            onBlur={handleBlur}
            helperText={touched.username ? errors.username : ''}
            error={touched.username && Boolean(errors.username)}
            InputProps={{
              endAdornment: (
                <InputAdornment>
                  <AccountCircle />
                </InputAdornment>
              ),
            }}
          />
          <TextField
            fullWidth
            label="Password"
            name="password"
            style={{ marginTop: '1rem' }}
            type="password"
            variant="outlined"
            value={values.password}
            onChange={handleChange}
            onBlur={handleBlur}
            helperText={touched.password ? errors.password : ''}
            error={touched.password && Boolean(errors.password)}
            InputProps={{
              endAdornment: (
                <InputAdornment>
                  <LockIcon />
                </InputAdornment>
              ),
            }}
          />
        </CardContent>
        <CardActions className={classes.loginButtonSection}>
          {/* 修正:用form的onSubmit,这里按钮改成type="submit" */}
          <Button
            color="primary"
            variant="contained"
            type="submit"
            className={classes.loginButton}
            disabled={isSubmitting}
          >
            Log In
          </Button>
        </CardActions>
      </form>
    </Card>
  );
};

// 修正Formik的handleSubmit,在这里调用dispatch更符合流程
const FormikLoginForm = withFormik({
  mapPropsToValues: ({ username, password }) => {
    return {
      username: username || '',
      password: password || '',
    };
  },
  validationSchema: yup.object().shape({
    username: yup.string().required('Required'),
    password: yup.string().min(8, 'Password must contain at least 8 characters').required('Enter your password'),
  }),
  handleSubmit: (values, { setSubmitting, props }) => {
    const { dispatch } = props;
    dispatch(login(values.username, values.password))
      .then(() => {
        setSubmitting(false);
      })
      .catch(() => {
        setSubmitting(false);
        // 这里可以处理登录失败的逻辑
      });
  },
})(LoginForm);

LoginForm.propTypes = {
  className: PropTypes.string,
};

// 导出Formik包装后的组件
export default FormikLoginForm;

关键修改点说明

  1. Hook的正确调用:把useStyles()移到LoginForm组件的顶层作用域内,这是React Hook的强制要求,确保Hook在每次组件渲染时都能按顺序执行。
  2. 组件导出逻辑修正:用withFormik包装LoginForm得到FormikLoginForm,然后导出这个包装后的组件,彻底纠正之前错误的嵌套调用逻辑。
  3. 表单提交逻辑统一:把登录的dispatch逻辑移到Formik的handleSubmit里,按钮改成type="submit",这样Formik会自动处理表单验证、提交状态管理等流程,避免手动绑定onClick的混乱。
  4. 规范导入:把require('yup')改成import * as yup from 'yup',符合ES模块的规范写法。

这样修改后,"无效Hook调用"的错误应该就会消失,同时表单的逻辑也更清晰,符合Formik和React的最佳实践。

内容的提问来源于stack exchange,提问作者Joseph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 20:57:29