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

React中如何在useEffect内调用返回值的自定义Hook?

解决React Hooks调用违规问题:切换用户时重新生成Builder对象

问题根源

原代码在useEffect中调用自定义Hook useBuilder,违反了Hooks必须在函数组件顶层调用的规则,导致出现以下报错:

Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. React与渲染器版本不匹配
  2. 违反Hooks规则
  3. 应用中存在多个React副本

核心解决方案(无需修改useBuilder)

方案1:直接在组件顶层调用useBuilder

利用Hooks的依赖追踪机制,将useBuilder直接放在组件顶层,传入当前选中的用户参数。当用户切换时,useBuilder会自动重新执行所有依赖用户的逻辑,返回新的builder对象。

修改后的Api.tsx代码:

export default function User() {
  const [users, setUsers] = useState([])
  const [selectedUser, setSelectedUser] = useState({})
  // 直接在顶层调用useBuilder,依赖selectedUser
  const builder = useBuilder(selectedUser)

  // 修正异步数据获取逻辑
  useEffect(() => {
    const fetchUsers = async () => {
      const usersData = await api.get('/../users')
      if (usersData?.length) {
        setUsers(usersData)
        setSelectedUser(usersData[0])
      }
    }
    fetchUsers()
  }, [])

  return (
    <Select
      value={selectedUser}
      // 修正onChange参数逻辑
      onChange={(selected) => {
        setSelectedUser(selected)
      }}
    >
      {users.map(user => (
        <MenuItem key={user.id} value={user}>
          {user.name}
        </MenuItem>
      ))}
    </Select>
  )
}

说明

  • 原代码中usersData = api.get('/../users')为同步写法,实际异步请求需用async/await配合useEffect执行
  • 将状态变量user重命名为selectedUser,避免和循环变量冲突
  • 直接使用useBuilder返回的builder,无需额外用useState存储,减少不必要的状态同步

方案2:用包装组件隔离Hook调用(特殊场景)

如果必须将builder存在组件本地状态中(比如需要手动控制更新时机),可以创建轻量包装组件,在其中合规调用useBuilder,再将结果传递给父组件:

// 包装组件:合规调用useBuilder
function BuilderWrapper({ user, onBuilderChange }) {
  const builder = useBuilder(user)
  useEffect(() => {
    onBuilderChange(builder)
  }, [builder, onBuilderChange])
  return null
}

// 原User组件
export default function User() {
  const [users, setUsers] = useState([])
  const [selectedUser, setSelectedUser] = useState({})
  const [builder, setBuilder] = useState({})

  useEffect(() => {
    const fetchUsers = async () => {
      const usersData = await api.get('/../users')
      if (usersData?.length) {
        setUsers(usersData)
        setSelectedUser(usersData[0])
      }
    }
    fetchUsers()
  }, [])

  return (
    <>
      <BuilderWrapper 
        user={selectedUser} 
        onBuilderChange={setBuilder} 
      />
      <Select
        value={selectedUser}
        onChange={(selected) => {
          setSelectedUser(selected)
        }}
      >
        {users.map(user => (
          <MenuItem key={user.id} value={user}>
            {user.name}
          </MenuItem>
        ))}
      </Select>
    </>
  )
}

说明

  • 包装组件BuilderWrapper在顶层调用useBuilder,完全符合Hooks规则
  • 当user变化时,useBuilder返回新的builder,通过回调同步到父组件状态中

补充:原代码的其他问题

  1. Select组件onChange参数错误:原代码中setUser(user)未正确获取选中值,需改为接收选中的用户对象或e.target.value
  2. 数据获取逻辑错误:api.get为异步操作,直接赋值会得到Promise对象,需用async/await处理

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 21:13:19