Next.js页面加载报错:Rendered more hooks than during the previous render
解决Next.js中"Rendered more hooks than during the previous render"错误
问题核心是React钩子的调用规则:每次渲染时钩子的调用顺序和数量必须完全一致,不能在条件return之后调用钩子。你之前把if (!router.isReady) return null放在useDocument前面,第一次渲染router未就绪时直接return,跳过了后面的钩子调用;第二次router就绪时才调用钩子,导致两次渲染的钩子数量不一致,触发报错。
但直接把判断移到钩子后面,又会让useDocument拿到空的solutionId,调用Firestore的doc函数时出错。下面给你两个可行的解决办法:
方法一:修改useDocument钩子,兼容空id的场景
在自定义钩子内部处理id不存在的情况,避免无效的Firestore请求:
export const useDocument = (c, id) => { const [document, setDocument] = useState(null) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { // id不存在时直接重置状态,不发起Firestore请求 if (!id) { setIsLoading(false) setDocument(null) setError(null) return } const ref = doc(db, c, id) const unsubscribe = onSnapshot( ref, (snapshot) => { setIsLoading(false) if (snapshot.data()) { setDocument({ ...snapshot.data(), id: snapshot.id }) setError(null) } else { setError("No such document exists") } }, (err) => { console.log(err.message) setIsLoading(false) setError("failed to get document") } ) return () => unsubscribe() }, [c, id]) return { document, isLoading, error } }
然后调整组件代码,把所有钩子放在最前面,条件判断移到JSX渲染阶段:
const SolutionEditForm = () => { const [formData, setFormData] = useState(INITIAL_STATE) const router = useRouter() const { solutionId } = router.query // 所有钩子始终调用,保证每次渲染的钩子数量、顺序一致 const { document } = useDocument("solutions", solutionId) const { updateDocument, response } = useFirestore("solutions") useEffect(() => { if (document) { setFormData(document) } }, [document]) // 在JSX层面做条件渲染,而非提前return跳过钩子 if (!router.isReady) { return <div>加载中...</div> // 也可以返回骨架屏或空白节点 } return ( <div> // JSX code </div> ) }
方法二:用服务端渲染提前获取参数(适用场景有限)
如果你的页面是通过服务端路由访问的,可以用Next.js的getServerSideProps提前解析路由参数,组件渲染时直接拿到确定的solutionId,彻底避免router未就绪的问题:
export async function getServerSideProps(context) { const { solutionId } = context.params return { props: { solutionId }, // 直接传递给组件 } } const SolutionEditForm = ({ solutionId }) => { const [formData, setFormData] = useState(INITIAL_STATE) // 直接使用服务端传递的solutionId,无需等待router就绪 const { document } = useDocument("solutions", solutionId) const { updateDocument, response } = useFirestore("solutions") useEffect(() => { if (document) { setFormData(document) } }, [document]) return ( <div> // JSX code </div> ) }
总结:核心原则是保证每次渲染时钩子的调用顺序和数量完全一致,把条件判断移到JSX渲染阶段,同时让自定义钩子兼容空参数的情况,避免无效请求和报错。
内容的提问来源于stack exchange,提问作者r121
相关产品推荐
相关产品推荐

