React+Apollo中执行GQL删除Mutation后调用导航Hook触发无效Hook调用错误的问题排查
React+Apollo中执行GQL删除Mutation后调用导航Hook触发无效Hook调用错误的问题排查
我来帮你定位并解决这个问题——你遇到的「Invalid hook call」错误,核心原因是违反了React Hook的调用规则,同时对React Router的导航逻辑使用有误,具体分析和修复方案如下:
错误的核心原因
你在useMutation的onCompleted回调里写的(Navigate("/decks"))存在两个关键问题:
- 如果你是把React Router v6的
Navigate组件当作普通函数调用,这是完全错误的——组件只能在JSX渲染树中使用,不能在普通回调函数里直接执行。 - 如果你原本想在回调里调用
useNavigateHook(比如const navigate = useNavigate(); navigate("/decks")),那也会触发Hook调用错误,因为React Hook只能在函数组件的顶层作用域调用,不能在嵌套的回调、循环或条件判断里使用,而onCompleted属于嵌套回调,显然违反了这个规则。
正确的修复方案
我们需要先在组件顶层通过useNavigate Hook获取导航函数,再在onCompleted回调里调用这个函数来实现页面跳转,具体步骤如下:
1. 导入并在组件顶层获取导航函数
在DeckDeleteModal组件的最顶层(所有Hook和JSX之前),导入useNavigate并调用它拿到导航函数:
import { useNavigate } from 'react-router-dom'; function DeckDeleteModal({showModal, setShowModal, deckId}) { // 必须在组件顶层调用useNavigate,不能放在回调里 const navigate = useNavigate(); const [error, setError] = useState(null); // ... 其他代码 }
2. 修改onCompleted回调实现导航
在useMutation的配置里,调用已获取的navigate函数完成跳转,同时建议先关闭模态框,提升用户体验:
const [deleteDeck, { loading }] = useMutation(DELETE_DECK, { onCompleted() { setShowModal(false); // 先关闭模态框,避免导航后模态框残留 navigate("/decks"); // 执行页面跳转 }, onError: (error) => { setError(error.graphQLErrors[0].message) } });
3. 修复额外的小问题
你的Alert组件原本渲染的是字符串"error",应该渲染错误变量error,否则用户看不到具体的错误信息:
if (error) { return <Alert variant="danger">{error}</Alert>; }
完整的修复后代码
import { useState } from 'react'; import { useMutation } from '@apollo/client'; import { useNavigate } from 'react-router-dom'; import { Modal, Button, Alert, Spinner } from 'react-bootstrap'; function DeckDeleteModal({showModal, setShowModal, deckId}) { const navigate = useNavigate(); const [error, setError] = useState(null); const [deleteDeck, { loading }] = useMutation(DELETE_DECK, { onCompleted() { setShowModal(false); navigate("/decks"); }, onError: (error) => { setError(error.graphQLErrors[0].message) } }); if (error) { return <Alert variant="danger">{error}</Alert>; } return ( <Modal size="sm" variant="dark" centered show={showModal}> <Modal.Header> <Modal.Title id="contained-modal-title-vcenter"> Delete deck? </Modal.Title> </Modal.Header> <Modal.Body> Are you sure you want to delete this deck? </Modal.Body> <Modal.Footer> <Button variant="danger" disabled={loading} onClick={() => deleteDeck({ variables: { id: deckId} })} > { loading ? <Spinner size="sm" /> : <>Delete</>} </Button> <Button onClick={() => setShowModal(false)} variant="light">Close</Button> </Modal.Footer> </Modal> ); } export default DeckDeleteModal;
额外注意事项
确保你的DeckDeleteModal组件被包裹在React Router的路由上下文(比如BrowserRouter)之内,否则useNavigate会无法获取路由上下文,导致新的错误。
内容来源于stack exchange
相关产品推荐
相关产品推荐

