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

React Hooks报错:调用useQuery时出现Invalid hook call问题

问题描述

我有一个带提交按钮的表单,提交时会调用lookup函数。编译生成GraphQL请求时,出现了Invalid hooks错误。

版本信息

"react": "^17.0.2",
"react-dom": "^17.0.2",

代码示例

import React, { useEffect, useState, useCallback } from "react";
import ReactDOM from "react-dom";
import { Button, Form, Input, Row, Card, Col } from "antd";
import { SearchOutlined, LoginOutlined } from "@ant-design/icons";
import { useQuery, useMutation, gql } from "@apollo/client";

const IDP_LOOKUP = gql`
  query getBusiness($name: String!) {
    business(name: $name) {
      id
      name
      domain {
        DomainType
        ... on Domains {
          DomainName
        }
      }
    }
  }
`;

const lookup = (values) => {
  let DomainName = "https://" + values.Orgname + ".com";

  const { data, loading, error } = useQuery(IDP_LOOKUP, {
    variables: {
      name: DomainName
    }
  });
};

const Login = () => {
  return (
    <div>
      <Card size="small" title="Okta Login">
        <Row type="flex">
          <Col lg={24}>
            <Form name="lookup_login" className="login-form" onFinish={lookup}>
              <Row gutter={16} align="center" justify="center" type="flex">
                <Col lg={18}>
                  <Form.Item
                    label="Org Name"
                    name="Orgname"
                    rules={[
                      {
                        required: true,
                        message: "Please enter org name!"
                      }
                    ]}
                  >
                    <Input addonBefore="https://" addonAfter=".com" />
                  </Form.Item>
                </Col>
                <Col lg={6}>
                  <Form.Item>
                    <Button
                      type="primary"
                      htmlType="submit"
                      className="login-form-button"
                    >
                      <SearchOutlined /> Lookup
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
            </Form>
          </Col>
        </Row>
      </Card>
    </div>
  );
};

错误信息

Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons:

  1. 你可能使用了版本不匹配的React和渲染器(比如React DOM)
  2. 你可能违反了Hooks的使用规则
  3. 你的应用中可能存在多个React副本

解决方案

问题根源

你直接违反了React Hooks的核心规则:所有Hooks必须在函数组件的顶层作用域调用,绝对不能在普通函数、事件处理函数、条件判断或循环内部调用。你的lookup是表单提交的事件处理函数,在里面调用useQuery自然触发了这个错误。

修复方案

要在表单提交这类手动事件中触发GraphQL查询,不能用useQuery(它是用于组件挂载/依赖更新时自动发起查询的Hook),推荐用以下两种方式:

方式1:使用useLazyQuery(推荐)

useLazyQuery会返回一个可手动调用的查询函数,完美适配表单提交、按钮点击这类场景:

import React from "react";
import { Button, Form, Input, Row, Card, Col } from "antd";
import { SearchOutlined } from "@ant-design/icons";
import { useLazyQuery, gql } from "@apollo/client";

const IDP_LOOKUP = gql`
  query getBusiness($name: String!) {
    business(name: $name) {
      id
      name
      domain {
        DomainType
        ... on Domains {
          DomainName
        }
      }
    }
  }
`;

const Login = () => {
  // 初始化懒查询,得到查询函数和结果对象
  const [lookupBusiness, { data, loading, error }] = useLazyQuery(IDP_LOOKUP);

  const handleLookup = (values) => {
    const domainName = `https://${values.Orgname}.com`;
    // 在事件处理中手动调用查询函数
    lookupBusiness({ variables: { name: domainName } });
  };

  return (
    <div>
      <Card size="small" title="Okta Login">
        <Row type="flex">
          <Col lg={24}>
            <Form name="lookup_login" className="login-form" onFinish={handleLookup}>
              <Row gutter={16} align="center" justify="center" type="flex">
                <Col lg={18}>
                  <Form.Item
                    label="组织名称"
                    name="Orgname"
                    rules={[
                      {
                        required: true,
                        message: "请输入组织名称!"
                      }
                    ]}
                  >
                    <Input addonBefore="https://" addonAfter=".com" />
                  </Form.Item>
                </Col>
                <Col lg={6}>
                  <Form.Item>
                    <Button
                      type="primary"
                      htmlType="submit"
                      className="login-form-button"
                      loading={loading} // 新增加载状态提示
                    >
                      <SearchOutlined /> 查找
                    </Button>
                  </Form.Item>
                </Col>
              </Row>
            </Form>
            {/* 展示查询结果或错误 */}
            {error && <div style={{color: 'red', marginTop: 16}}>查询出错:{error.message}</div>}
            {data && (
              <div style={{marginTop: 16}}>
                <h4>查询结果:</h4>
                <p>ID: {data.business.id}</p>
                <p>名称: {data.business.name}</p>
                <p>域名: {data.business.domain.DomainName}</p>
              </div>
            )}
          </Col>
        </Row>
      </Card>
    </div>
  );
};

方式2:使用Apollo Client实例的query方法

如果你的应用已经通过ApolloProvider注入了客户端,也可以直接获取客户端实例手动发起查询:

import React from "react";
import { Button, Form, Input, Row, Card, Col } from "antd";
import { SearchOutlined } from "@ant-design/icons";
import { useApolloClient, gql } from "@apollo/client";

const IDP_LOOKUP = gql`
  query getBusiness($name: String!) {
    business(name: $name) {
      id
      name
      domain {
        DomainType
        ... on Domains {
          DomainName
        }
      }
    }
  }
`;

const Login = () => {
  const client = useApolloClient();

  const handleLookup = async (values) => {
    const domainName = `https://${values.Orgname}.com`;
    try {
      const { data } = await client.query({
        query: IDP_LOOKUP,
        variables: { name: domainName }
      });
      // 处理查询结果
      console.log('查询结果:', data);
    } catch (error) {
      // 处理错误
      console.error('查询出错:', error);
    }
  };

  // 表单部分和之前一致,仅替换onFinish为handleLookup
  return (
    <div>
      <Card size="small" title="Okta Login">
        <Row type="flex">
          <Col lg={24}>
            <Form name="lookup_login" className="login-form" onFinish={handleLookup}>
              {/* 表单内容省略 */}
            </Form>
          </Col>
        </Row>
      </Card>
    </div>
  );
};

关键提醒

  • 所有React Hooks(包括Apollo的useQuery、useMutation)都必须在函数组件的顶层调用,不能嵌套在任何子函数、条件语句里。
  • useQuery适合组件加载时自动执行的查询,useLazyQuery适合手动触发的场景,别搞混了。

内容的提问来源于stack exchange,提问作者Maria Jeysingh Anbu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 12:24:19