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

Relay使用useMutation报Invalid hook call错误如何解决

问题根因

报错的核心原因是违反React Hooks调用规则:useMutation属于React Hook,仅允许在函数组件/自定义Hook的顶层作用域调用,不能在普通工具函数、事件回调、条件分支、循环嵌套中调用。
你之前的写法把useMutation封装到了普通工具函数CreateProjectMutation中,还在按钮点击触发的submitProject事件回调里调用这个普通函数,完全不符合Hook的调用要求,所以触发Invalid hook call报错。

修复方法

核心原则:Hook永远在组件顶层调用,事件回调里只执行Hook返回的commit提交方法,不要在回调里调用Hook本身。

  • 不需要把整个mutation逻辑封装成独立的普通函数,可以把graphql的mutation定义单独抽离,但是useMutation的调用必须放在组件顶层
  • 点击提交时,只需要调用useMutation返回的commit方法,传入对应变量和回调即可

修复后完整代码

import React, { useState } from 'react';
import { graphql, useMutation } from 'react-relay';

// 可单独抽离graphql mutation定义,不违反规则
const CREATE_PROJECT_MUTATION = graphql`
  mutation CreateProjectMutation($input:createProjectInput!) {
      createProject(input:$input) {
        project {
          id
          content
          leader
        }
      } 
    }
`;

export default function CreateProject() {
    const [leader, setLeader] = useState("");
    const [content, setContent] = useState("");
    const [date, setDate] = useState("");
    // 组件顶层调用useMutation,拿到提交方法和加载状态
    const [commitCreateProject, isCreateLoading] = useMutation(CREATE_PROJECT_MUTATION);

    const submitProject = () => {
        const variables = {
            input: {
                content,
                leader,
                startDate: date,
            }
        };

        commitCreateProject({
            variables,
            onCompleted: (res) => {
                console.log('提交成功', res);
                // 提交成功后再清空表单,避免请求未完成就丢失用户输入
                clearForm();
            },
            onError: (err) => {
                console.error('提交失败', err);
            }
        });
    }

    const clearForm = () => {
        setLeader("");
        setContent("");
        setDate("");
    }


    return (
        <div>
            <label>Leader</label>
            <input
                value={leader}
                onChange={(event) => { setLeader(event.target.value) }} />
            <label>content</label>
            <input
                value={content}
                onChange={(event) => { setContent(event.target.value) }} />
            <label>start date</label>
            <input
                value={date}
                onChange={(event) => { setDate(event.target.value) }} />
            <button onClick={submitProject} disabled={isCreateLoading}>
                {isCreateLoading ? '提交中...' : 'Submit'}
            </button>
        </div>
    )
}

避坑提示:不要把clearForm()直接写在commitCreateProject调用的后面,commit方法是异步执行的,直接同步调用清空表单会导致请求还未返回就丢失用户输入,正确做法是放到onCompleted回调中,等接口返回成功后再执行清空操作。

  • useMutation返回的第二个值是请求的加载状态(示例中重命名为isCreateLoading),可以直接用来控制按钮禁用、展示加载提示,不需要额外手动维护loading状态
  • 如果需要在多个组件复用同一个mutation逻辑,可以将相关逻辑封装为自定义Hook(函数名以use开头),自定义Hook内部允许调用其他Hook,使用时同样需要遵循Hook规则在组件顶层调用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 10:31:02