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

React Native自定义Hook调用useQuery报错:Hooks仅能在函数组件体内调用

解决React Native中Apollo Client Hook调用错误:Hooks can only be called inside the body of a function component.

问题根源

你犯了两个核心错误:

  1. Hook调用位置违规:自定义Hook useShopifyLogin 在Button的onPress事件回调里调用,违反了React Hook的核心规则——Hook必须在函数组件的顶层作用域内调用,不能嵌套在事件处理函数、循环、条件判断或其他非顶层逻辑里。
  2. 操作类型误用:登录创建access token属于数据修改操作,应该用Apollo的useMutation而非useQuery,useQuery仅用于查询只读数据。

修复步骤

步骤1:重构API层,改用useMutation处理登录操作

修改api.tsx,将登录相关逻辑改为mutation,并调整自定义Hook的结构:

import { useMutation, useQuery } from "@apollo/client";
import * as query from './shopify_query';
import Loader from "../components/Loader/Loader";

// 登录创建access token的mutation Hook
export const useCreateCustomerToken = () => {
  const [mutate, { loading, error, data }] = useMutation(
    query.CREATE_CUSTOMER_TOKEN.query,
    { fetchPolicy: 'network-only' }
  );

  const login = async (username: string, password: string) => {
    try {
      const result = await mutate({
        variables: {
          ...query.CREATE_CUSTOMER_TOKEN.variables,
          email: username,
          password: password
        }
      });
      return result.data.customerAccessTokenCreate.customerAccessToken.accessToken;
    } catch (err: any) {
      throw new Error(err.toString());
    }
  };

  return { login, loading, error };
};

// 获取用户信息的查询Hook
export const useGetUserInfo = (accessToken?: string) => {
  const { loading, error, data } = useQuery(
    query.GET_CUSTOMER_INFO.query,
    {
      variables: { ...query.GET_CUSTOMER_INFO.variables, accessToken },
      // 只有当accessToken存在时才执行查询
      skip: !accessToken
    }
  );

  if (loading) return { loading: true };
  if (error) throw new Error(error.toString());
  
  return { loading: false, user: data };
};

步骤2:调整Login组件的Hook调用逻辑

将Hook移到组件顶层,在事件回调里调用mutation的触发函数:

import { Image, Pressable, Text, View } from 'react-native'
import { useCreateCustomerToken, useGetUserInfo } from '../../api/api';
//....other imports

const Login = (props:any) => {
    const nav = useNavigation();
    const [email,setEmail] = useState('');
    const [password,setPassword] = useState('');
    const [showPassword,setShowPassword] = useState(false);
    // 把Hook移到组件顶层
    const { login, loading: loginLoading, error: loginError } = useCreateCustomerToken();
    const [accessToken, setAccessToken] = useState<string | undefined>(undefined);
    const { loading: userLoading, user } = useGetUserInfo(accessToken);

    // 处理登录点击事件
    const handleLogin = async () => {
      try {
        const token = await login(email, password);
        setAccessToken(token);
        // 获取到用户信息后跳转
        if (user) {
          nav.goBack();
        }
      } catch (err) {
        console.error('登录失败:', err);
      }
    };

    return (
      // ...其他组件代码
      <Button 
          buttonStyle={[styles.loginButton]}
          radius={20}
          disabled={!email || !password || loginLoading}
          title={'Log In'}
          disabledTitleStyle={{fontSize:16}}
          onPress={handleLogin}
      />
      // ...其他组件代码
    )
}

export default Login

额外注意事项

  • 始终遵循React Hook规则:所有Hook必须在函数组件的顶层调用,不能放在任何条件、循环或嵌套函数中。
  • 区分Query和Mutation:数据查询用useQuery,数据修改/创建/删除用useMutation,这是GraphQL的核心规范。
  • 处理加载和错误状态:在UI中根据loading状态显示加载提示,根据error状态显示错误信息,提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 17:07:11