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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:45:28