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

React Router结合Zustand引发渲染钩子数量异常问题求助

解决React Hook调用数量不一致的错误

问题根源

你的代码违反了React Hook的核心规则,导致渲染时Hook调用数量不稳定:

  1. 循环内调用useState:const [edit, setEdit] = useState(false);写在了currentIncome.map的回调函数里。当数组长度变化(添加/删除收入项)时,Hook的调用数量会跟着改变,直接触发“渲染Hook数量与上一次不一致”的错误。
  2. 事件函数内调用自定义Hook:useModifyData(id, "income", "delete")写在了deleteData这个事件处理函数里。Hook只能在组件顶层或自定义Hook顶层调用,不能在嵌套函数/事件逻辑内执行,这会破坏Hook的调用顺序。

修复方案

1. 拆分循环内逻辑为独立组件

创建单独的IncomeItem组件,把每个收入项的状态和操作逻辑封装进去,确保useState处于组件顶层:

// IncomeItem.tsx
import { useState } from 'react'
import Card from "~/components/tailwindcss/Card";
import useModifyData from "~/hooks/useModifyData";
import EditIncome from "~/components/income/EditIncome";
import SingleIncomeRender from "~/components/income/SingleIncomeRender";

type IncomeItemProps = {
  item: Income;
  colorOptions: {
    title: string;
    body: string;
    text: string;
    border: string;
  };
  removeIncomeFromState: (id: number) => void;
}

function IncomeItem({ item, colorOptions, removeIncomeFromState }: IncomeItemProps) {
  const [edit, setEdit] = useState(false);
  const modifyData = useModifyData(); // 自定义Hook返回操作函数

  const deleteData = () => {
    modifyData(item.id!, "income", "delete")
    removeIncomeFromState(item.id!)
  }

  const options = {
    id: item.id!,
    setEditing: () => setEdit(!edit),
    setDelete: deleteData,
    edit: edit,
    showEdit: true,
  }

  return (
    <Card key={item.id} title={item.name} colorOptions={colorOptions} options={options}>
      {edit ? <EditIncome id={item.id!} setEditing={() => setEdit(!edit)}/> : <SingleIncomeRender id={item.id!} edit={edit}/>}
    </Card>
  )
}

export default IncomeItem

2. 重构IncomeRender组件

修改原组件,调用拆分后的IncomeItem,避免在循环内直接使用Hook:

import IncomeItem from "./IncomeItem";
import {useIncomeStore} from "~/state/incomeStore";

function IncomeRender() {
    const currentIncome = useIncomeStore.use.currentIncome()
    const renderIncome = useIncomeStore.use.renderIncome()
    const removeIncomeFromState = useIncomeStore.use.removeIncome()

    const colorOptions = {
        title: "bg-green-700",
        body: "bg-green-100",
        text: "text-black",
        border: "border-green-700"
    }

    if (renderIncome) {
        return <div>Loading....</div>
    }
    
    return (
        <div className="grid grid-cols-2 gap-4 place-items-center mb-5">
            {currentIncome.map((item) => (
                <IncomeItem 
                    key={item.id} 
                    item={item} 
                    colorOptions={colorOptions} 
                    removeIncomeFromState={removeIncomeFromState} 
                />
            ))}
        </div>
    )
}

export default IncomeRender

3. 调整useModifyData的实现

确保自定义Hook返回可在事件中调用的函数,而非直接在事件内执行Hook逻辑:

// useModifyData.ts
function useModifyData() {
  // 这里可以正常使用其他Hook(如useEffect、useState)
  const modify = (id: number, type: string, action: string) => {
    // 执行数据修改的具体逻辑
  }

  return modify;
}

export default useModifyData;

核心规则回顾

React Hook必须遵守两个铁则:

  • 只能在组件顶层或自定义Hook顶层调用
  • 每次渲染时,Hook的调用顺序必须完全一致

按上述方案修改后,添加/删除数据时Hook的调用数量和顺序会保持稳定,错误即可解决。

内容的提问来源于stack exchange,提问作者Chris Williamson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 05:27:04