React Native自定义Hook调用useQuery报错:Hooks仅能在函数组件体内调用
解决React Native中Apollo Client Hook调用错误:
Hooks can only be called inside the body of a function component. 问题根源
你犯了两个核心错误:
- Hook调用位置违规:自定义Hook
useShopifyLogin在Button的onPress事件回调里调用,违反了React Hook的核心规则——Hook必须在函数组件的顶层作用域内调用,不能嵌套在事件处理函数、循环、条件判断或其他非顶层逻辑里。 - 操作类型误用:登录创建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
相关产品推荐
相关产品推荐

