thanks for your reading. I'm trying to commit a relay mutation of using the useMutation hook, but got the error:

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 See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

My CreateProjectMutation.js is

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



export default function CreateProjectMutation(content, leader, startDate, callback) {
  console.log("fake CreateProjectMutation");
    const [commit, isInFlight] = useMutation(graphql`
  mutation CreateProjectMutation($input:createProjectInput!) {
      createProject(input:$input) {
        project {
          id
          content
          leader
        }
      } 
    }
  `)

    const variables = {
      input: {
        content,
        leader,
        startDate,
      }
    }

    commit({
      variables,
      onCompleted: () => {
        callback();
      },
    }
    )
}

And the app.js calling it is

import React, { useState } from 'react';
import CreateProjectMutation from '../mutations/CreateProjectMutation';


export default function CreateProject() {
    const [leader, setLeader] = useState("");
    const [content, setContent] = useState("");
    const [date, setDate] = useState("");

    const submitProject = () => {
        CreateProjectMutation(content, leader, date, (data) => { console.log(data) });
        clearForm();
    }

    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}>Submit</button>
        </div>
    )
}

I tried several ways to solve the problem.

  1. Using the old version hook API commitMutation, it's successful. But I cannot transfer it to the useMutation, and the doc here only says Todo... official doc link

  2. Try to explore with the error message. I visit the linked file (https://reactjs.org/link/invalid-hook-call). 1)Mismatching Versions of React and React DOM -> not satisfied 2)Breaking the Rules of Hooks -> I think most likely this is the reason because I used hook inside the onclick. And when I turned the :

    const submitProject = () => {
        CreateProjectMutation(content, leader, date, (data) => { console.log(data) });
        clearForm();
    }

into

    const submitProject = () => {
        //CreateProjectMutation(content, leader, date, (data) => { console.log(data) });
        clearForm();
    }

It's successful.

I'm not very sure the problem is here. However, even though I may find the reason correct, I still didn't find any result telling me how to deal with this situation without putting the hook inside the onclick. Because What I hope to do is when the user clicks the submit button, the CreateProjectMutation API will be called to handle the request. I cannot see how can I avoid the problem.

Could someone please help me? Thanks so much!

1

There are 1 best solutions below

1
On

The error message gave you the answer in the first sentence:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component.

The docs confirm:

Don’t call Hooks inside loops, conditions, or nested functions. Instead, always use Hooks at the top level of your React function, before any early returns. By following this rule, you ensure that Hooks are called in the same order each time a component renders. That’s what allows React to correctly preserve the state of Hooks between multiple useState and useEffect calls. (If you’re curious, we’ll explain this in depth below.)

Your CreateProjectMutation is not a React component, and you cannot call a hook in the manner you've attempted.

Call the hook from the top level of your component instead.

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


export default function CreateProject() {
    const [leader, setLeader] = useState("");
    const [content, setContent] = useState("");
    const [date, setDate] = useState("");

    // Call your hook here, similar to how you've called useState above
    const [commit, isInFlight] = useMutation(...)

    const createProjectMutation = (content, leader, startDate, callback) =>
    {
        // Logic
    }

    const submitProject = () => {
        createProjectMutation (content, leader, date, (data) => { console.log(data) });
        clearForm();
    }

    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}>Submit</button>
        </div>
    )
}