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

React表单调用GraphQL登录接口遇Hook错误及Apollo异常求助

问题分析与解决方案

1. 核心错误:误用useQuery处理登录请求

登录属于提交凭证并获取授权令牌的操作,对应GraphQL的Mutation类型,而非用于查询数据的Query类型。你用urql的useQuery来处理登录是根本问题:

  • useQuery会在组件挂载时自动发起请求,导致页面刷新后首次触发空请求
  • useQuery默认缓存请求结果,所以更换账号密码后仍发送旧数据
  • 若你在按钮点击的事件回调里直接调用自定义hook,会违反React Hook调用规则,触发Invalid hook call错误

2. 修复Invalid hook call错误

确保你的自定义hook严格遵循Hook规则:

  • 只能在React组件的顶层或其他自定义Hook的顶层调用,不能在事件处理函数、条件判断、循环、嵌套函数内调用
  • 自定义Hook必须以use开头(这一点你已经符合要求)

3. 正确实现登录逻辑(基于urql)

步骤1:定义登录Mutation

mutation Login($email: String!, $password: String!) {
  login(email: $email, password: $password) {
    token
  }
}

步骤2:创建useLogin自定义Hook(改用useMutation)

import { useMutation } from 'urql';
import Swal from 'sweetalert2';
import { useNavigate } from 'react-router-dom';

export const useLogin = () => {
  const navigate = useNavigate();
  const [, loginMutation] = useMutation(`
    mutation Login($email: String!, $password: String!) {
      login(email: $email, password: $password) {
        token
      }
    }
  `);

  const handleLogin = async (email, password) => {
    try {
      const result = await loginMutation({ email, password });
      
      if (result.error) {
        Swal.fire('错误', '账号或密码错误', 'error');
        return;
      }

      const { token } = result.data.login;
      localStorage.setItem('authToken', token);
      navigate('/'); // 跳转到主页
    } catch (err) {
      Swal.fire('错误', '登录失败,请稍后重试', 'error');
    }
  };

  return { handleLogin };
};

步骤3:在登录组件中使用Hook

import { useState } from 'react';
import { useLogin } from './hooks/useLogin';

const LoginForm = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const { handleLogin } = useLogin();

  const onSubmit = (e) => {
    e.preventDefault();
    handleLogin(email, password);
  };

  return (
    <form onSubmit={onSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="邮箱"
        required
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="密码"
        required
      />
      <button type="submit">Login</button>
    </form>
  );
};

export default LoginForm;

4. 避免旧问题的关键要点

  • 用useMutation替代useQuery:mutation不会自动缓存结果,每次调用都会发送最新的变量值
  • 将登录逻辑封装在handleLogin函数中,仅在表单提交时触发,避免组件挂载时自动发起请求
  • 直接传递实时的email和password状态作为mutation变量,确保每次请求都是最新输入

最佳实践建议

  • 给urql客户端配置认证拦截器,让后续请求自动携带localStorage中的token
  • 添加登录加载状态:在handleLogin中设置loading标记,禁用提交按钮避免重复请求
  • 处理token过期逻辑:后续请求捕获401错误时,清除localStorage并跳转回登录页

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 20:01:02