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

调用自定义hook返回函数时触发Invalid hook call错误排查

React Invalid Hook Call错误排查与修复

错误日志

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. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
    at Object.throwInvalidHookError (plugin:flow:17739:539)
    at useRef (plugin:flow:1768:25)
    at useSyncExternalStoreWithSelector (plugin:flow:3976:21)
    at useStore$1 (plugin:flow:4083:17)
    at useBoundStore (plugin:flow:4089:51)
    at useOutput (plugin:flow:13158:9)
    at setOutput (plugin:flow:13195:45)
    at onClickButton (plugin:flow:13200:9)
    at HTMLUnknownElement.callCallback (plugin:flow:15894:119)
    at Object.invokeGuardedCallbackDev (plugin:flow:15914:45)
throwInvalidHookError @ plugin:flow:17739
useRef @ plugin:flow:1768
useSyncExternalStoreWithSelector @ plugin:flow:3976
useStore$1 @ plugin:flow:4083
useBoundStore @ plugin:flow:4089
useOutput @ plugin:flow:13158
setOutput @ plugin:flow:13195
onClickButton @ plugin:flow:13200
callCallback @ plugin:flow:15894
invokeGuardedCallbackDev @ plugin:flow:15914
invokeGuardedCallback @ plugin:flow:15933
invokeGuardedCallbackAndCatchFirstError @ plugin:flow:15942
executeDispatch @ plugin:flow:16822
processDispatchQueueItemsInOrder @ plugin:flow:16822
processDispatchQueue @ plugin:flow:16822
dispatchEventsForPlugins @ plugin:flow:16824
eval @ plugin:flow:16878
batchedEventUpdates$1 @ plugin:flow:19004
batchedEventUpdates @ plugin:flow:15839
dispatchEventForPluginEventSystem @ plugin:flow:16878
attemptToDispatchEvent @ plugin:flow:16266
dispatchEvent @ plugin:flow:16244
unstable_runWithPriority @ plugin:flow:14064
runWithPriority$1 @ plugin:flow:17299
discreteUpdates$1 @ plugin:flow:19005
discreteUpdates @ plugin:flow:15839
dispatchDiscreteEvent @ plugin:flow:16234

相关代码

React组件内代码

const flowName = useContext(FlowNameContext);
const setOutput = (output: OutputData, outputId: string) =>
  useOutput(outputId, id, flowName)[1](output);

useOutput Hook代码

export const useOutput = (
  outputId: string,
  nodeId: string,
  flowName: string
): MutableHookResult<OutputData> => {
  return [
    useAppModel(
      (store) =>
        selectOutputs(nodeId, selectFlow(flowName, store)?.editorModel)[
          outputId
        ]
    ),
    (output: OutputData) =>
      useAppModel.setState(
        produce((draft: AppModel) => {
          setOutput(output, outputId, nodeId, flowName, draft);
        })
      ),
  ];
};

setOutput工具函数代码

export const setOutput = (
  output: OutputData,
  id: string,
  nodeId: string,
  flowName: string,
  appModel: AppModel
): void => {
  // update outputs
  const editorModel = selectFlow(flowName, appModel).editorModel;
  const node = selectNode(nodeId, editorModel);
  if (node) node.data.outputs[id] = output;

  // update inputs
  const connectedNodes = selectConnectedNodes(nodeId, false, id, editorModel);

  connectedNodes.forEach((connectedNode) => {
    connectedNode.node.data.inputs[connectedNode.connectedOn] = output;
  });
};

问题根源

  1. 违反Hooks调用规则:组件内定义的setOutput函数,会在按钮点击事件触发时才调用useOutput Hook。Hooks必须在组件渲染的顶层作用域调用,不能在事件处理函数、条件判断、循环或嵌套函数内调用。
  2. Hook内部错误:useOutput返回的更新函数中直接调用useAppModel.setState——useAppModel.setState是Hook提供的方法,同样不能在普通函数内调用,必须在Hook或组件的顶层作用域获取后使用。

修复方案

步骤1:在组件顶层调用useOutput

将useOutput的调用移到组件渲染的顶层,提前获取更新函数,避免在事件处理时触发Hook调用:

const flowName = useContext(FlowNameContext);
// 直接在组件顶层调用useOutput,拿到状态和更新函数
const [, updateOutput] = useOutput(targetOutputId, id, flowName);

// 点击事件直接调用已获取的更新函数
const onClickButton = (output: OutputData) => {
  updateOutput(output);
};

如果需要处理多个outputId,可以用useMemo缓存每个outputId对应的更新函数:

const flowName = useContext(FlowNameContext);
// 假设outputIds是当前组件需要处理的所有outputId集合
const outputUpdaters = useMemo(() => {
  const updaters: Record<string, (output: OutputData) => void> = {};
  outputIds.forEach(outputId => {
    const [, update] = useOutput(outputId, id, flowName);
    updaters[outputId] = update;
  });
  return updaters;
}, [outputIds, id, flowName]);

// 点击时根据outputId调用对应更新函数
const onClickButton = (output: OutputData, outputId: string) => {
  outputUpdaters[outputId]?.(output);
};

步骤2:修复useOutput内部的Hook调用错误

在useOutput的顶层作用域获取setState方法,再在更新函数中使用,避免在普通函数内调用Hook相关方法:

import { useCallback } from 'react';

export const useOutput = (
  outputId: string,
  nodeId: string,
  flowName: string
): MutableHookResult<OutputData> => {
  // 在Hook顶层获取setState方法
  const setState = useAppModel.setState;
  
  // 获取当前输出值
  const output = useAppModel(
    (store) =>
      selectOutputs(nodeId, selectFlow(flowName, store)?.editorModel)[outputId]
  );
  
  // 用useCallback缓存更新函数,避免不必要重渲染
  const updateOutput = useCallback((output: OutputData) => {
    setState(
      produce((draft: AppModel) => {
        setOutput(output, outputId, nodeId, flowName, draft);
      })
    );
  }, [setState, outputId, nodeId, flowName]);
  
  return [output, updateOutput];
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 18:05:28