使用React Query调用Firestore时触发Invalid hook call错误排查
解决Next.js中useRouter在数据获取函数里触发的Hook调用错误
问题背景
在Next.js项目中,尝试通过router.query提取URL参数并调用Firestore获取单个文档时遇到问题:此前用相同方式获取Firestore集合(无需路由参数)可正常运行,但当前fetchArtist函数内调用useRouter Hook时触发**"Invalid hook call"**错误,数据返回undefined;但直接调用该函数却能正常获取数据,无法定位违反React Hooks规则的位置。
错误原因
React Hooks的核心规则之一是:只能在React函数组件的顶层,或者自定义Hook的顶层调用Hook。你的fetchArtist是普通异步函数(既不是React组件,也不是自定义Hook),在内部调用useRouter完全违反了这一规则,因此触发错误。
修复方案
将路由参数的获取移到组件顶层,再作为参数传给数据获取函数;同时控制React Query的请求时机,确保路由参数就绪后再发起请求。
修改后的页面组件代码
import React, { useEffect, useState } from "react"; import { useSession } from "next-auth/react"; import { useRouter } from "next/router"; import { useQuery } from "@tanstack/react-query"; import fetchArtist from "../../hooks/getArtists"; import Head from "next/head"; import ArtistHeader from "../../components/ArtistHeader"; import UploadButton from "../../components/UploadButton"; import styles from "../../styles/artistPage.module.css"; export default function Artist() { const { status } = useSession(); const router = useRouter(); const { artistId } = router.query; // 未登录用户跳转至登录页 useEffect(() => { if (status === "unauthenticated") { router.push("/auth/signin"); } }, [status, router]); // 仅当路由就绪且存在artistId时发起请求 const { data, status: artistStatus } = useQuery( ["artist", artistId], // 将artistId加入查询键,参数变化时自动重新请求 () => fetchArtist(artistId), { staleTime: 1000 * 60 * 60 * 24, cacheTime: 1000 * 60 * 60 * 24, enabled: router.isReady && !!artistId, // 控制请求触发时机 } ); const [artist, setArtist] = useState(); useEffect(() => { if (artistStatus === "success") { setArtist(data); } }, [artistStatus, data]); // 添加data依赖,确保获取最新数据 return ( <section className={styles.wrapper}> {/* 确保artist存在后再渲染内容,避免undefined报错 */} {artist && ( <> <Head> <title>{artist.screenName}</title> </Head> <div className={styles.artistPage}> <ArtistHeader artist={artist} /> <div className={styles.songContainer}> <UploadButton /> </div> </div> </> )} </section> ); }
修改后的fetchArtist函数代码
import { getDoc, doc } from "firebase/firestore"; import { db } from "../pages/api/auth/firebase/config"; // 接收artistId作为参数,不再内部调用useRouter export default async function fetchArtist(artistId) { if (!artistId) return null; const artistRef = doc(db, "users", artistId); const artistDoc = await getDoc(artistRef); if (artistDoc.exists()) { return artistDoc.data(); } else { console.log("指定的艺术家文档不存在"); return null; } }
关键优化点
- 遵守Hook规则:将
useRouter移到组件顶层调用,避免在普通函数内使用Hook - 控制请求时机:通过
enabled选项确保路由参数就绪后再发起请求,避免无效调用 - 缓存与更新:将
artistId加入React Query的查询键,确保参数变化时自动重新请求,避免缓存旧数据 - 空值处理:添加
artist存在性判断后再渲染内容,避免undefined引发的渲染错误
内容的提问来源于stack exchange,提问作者Yan
相关产品推荐
相关产品推荐

