调用自定义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; }); };
问题根源
- 违反Hooks调用规则:组件内定义的
setOutput函数,会在按钮点击事件触发时才调用useOutputHook。Hooks必须在组件渲染的顶层作用域调用,不能在事件处理函数、条件判断、循环或嵌套函数内调用。 - 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
相关产品推荐
相关产品推荐

