Next.js中getStaticProps调用自定义Hook出现useState读取错误求助
你遇到的TypeError: Cannot read properties of null (reading 'useState')错误,核心原因是React Hook不能在非React组件/自定义Hook的函数中调用。
useCollection是自定义React Hook,内部依赖useState、useEffect这些Hook,而getStaticProps是Next.js提供的服务器端数据获取函数,它不属于React组件或自定义Hook范畴,完全脱离了React的Hook调用规则环境,直接调用Hook会导致React无法初始化对应的状态上下文,进而抛出这个错误。
另外,useCollection里用了onSnapshot(实时监听Firestore数据变化),但getStaticProps的作用是在构建时一次性获取数据用于预渲染页面,实时监听在这里完全没必要,反而会造成资源浪费。
我们需要把Firestore数据获取逻辑从Hook中抽离出来,改成普通的异步函数,在getStaticProps中调用这个函数来获取数据:
步骤1:创建数据获取工具函数
新建一个工具文件(比如utils/firebaseUtils.js),写一个异步函数来获取挑战数据:
import { collection, query, orderBy, getDocs } from "firebase/firestore" import { db } from "../firebase/config" export async function getChallenges() { // 构建查询条件,和原Hook逻辑一致 const challengesRef = collection(db, "challenges") const q = query(challengesRef, orderBy("createdAt", "desc")) // 一次性获取数据(替代实时监听的onSnapshot) const snapshot = await getDocs(q) // 转换数据格式 return snapshot.docs.map(doc => ({ ...doc.data(), id: doc.id })) }
步骤2:修改Challenges页面的getStaticProps
删除对useCollection的导入,改用上面的工具函数:
import Card from "../components/reusable/Card" import { getChallenges } from "../utils/firebaseUtils" const Challenges = ({ challenges }) => { return ( <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8"> {challenges.map((challenge) => { return ( <Card key={challenge.id} card={challenge} /> ) })} </div> ) } export default Challenges export async function getStaticProps() { // 调用工具函数获取数据 const challenges = await getChallenges() return { props: { challenges, }, // 可选:如果需要页面定期重新生成以更新数据,添加revalidate参数 // revalidate: 60, // 每60秒重新获取数据并生成页面 } }
额外说明
如果你的页面需要实时更新Firestore数据,可以在Challenges组件内部调用useCollection Hook,但此时不需要getStaticProps(或者保留getStaticProps做预渲染,客户端再用Hook做实时更新):
import Card from "../components/reusable/Card" import { useCollection } from "../hooks/useCollection" const Challenges = () => { const { documents: challenges, isLoading, error } = useCollection("challenges", null, null, ["createdAt", "desc"]) if (isLoading) return <div>加载中...</div> if (error) return <div>{error}</div> return ( <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 justify-items-center mt-8"> {challenges.map((challenge) => { return ( <Card key={challenge.id} card={challenge} /> ) })} </div> ) } export default Challenges // 此时不需要getStaticProps
内容的提问来源于stack exchange,提问作者r121

