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:
- 你可能使用了版本不匹配的React和渲染器(比如React DOM)
- 你可能违反了Hooks的使用规则
- 你的应用中可能存在多个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
相关产品推荐
相关产品推荐

