React-table按钮触发MUI Modal失效及Invalid hook call报错解决
问题根因
报错触发原因是违反React Hooks调用规则,和React/ReactDOM版本不匹配、项目存在多份React副本无关,具体问题点有两个:
- Hooks调用位置非法:React明确要求
useState这类Hook只能在函数组件、自定义Hook的顶层作用域调用,你的代码把useState写在点击事件回调handleModal内部,直接触发Invalid hook call报错。 - 组件渲染逻辑错误:普通事件回调的返回值不会被React解析挂载,你在
handleModal里return的<BasicModal />根本不会进入DOM渲染树,即使绕过Hook报错,弹窗也不可能正常显示。
正确实现方案
核心调整思路是把弹窗相关的状态和渲染逻辑移到合法的组件作用域中:
- 弹窗显隐状态、当前选中的行数据,统一放在渲染表格的父函数组件顶层管理
- 按钮点击回调只做状态更新:标记弹窗为打开、存入当前点击的行数据,不做Hook调用、不返回组件
- 把
BasicModal直接放在父组件的JSX渲染树中,通过props接收状态和回调,靠状态值控制显隐
代码示例
表格列配置+父组件(渲染表格的组件)
import { useState } from 'react'; import { useTable } from 'react-table'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { someIcon } from '@fortawesome/free-solid-svg-icons'; import BasicModal from './BasicModal'; // 替换成你实际的表格数据源 const TABLE_DATA = []; const COLUMNS = [ // ...保留你其他列的配置 { Header: 'Actions', Cell: ({ row, meta }) => ( <span> {row.original.id !== null ? ( <button value={row.original.id} onClick={() => meta.handleOpenModal(row.original)} > <FontAwesomeIcon icon={someIcon} /> </button> ) : "No ID!"} </span> ), className: 'actions' } ]; export default function DataTable() { // 弹窗相关状态必须放在组件顶层 const [modalOpen, setModalOpen] = useState(false); const [currentRow, setCurrentRow] = useState(null); // 点击回调只负责更新状态,禁止在此调用Hook、返回组件 const handleOpenModal = (rowData) => { setCurrentRow(rowData); setModalOpen(true); }; const handleCloseModal = () => { setModalOpen(false); }; const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, } = useTable({ columns: COLUMNS, data: TABLE_DATA, // 把打开方法通过meta传给单元格 meta: { handleOpenModal } }); return ( <div> {/* 原有表格渲染逻辑 */} <table {...getTableProps()}> <thead> {headerGroups.map(headerGroup => ( <tr {...headerGroup.getHeaderGroupProps()}> {headerGroup.headers.map(column => ( <th {...column.getHeaderProps()}>{column.render('Header')}</th> ))} </tr> ))} </thead> <tbody {...getTableBodyProps()}> {rows.map(row => { prepareRow(row); return ( <tr {...row.getRowProps()}> {row.cells.map(cell => ( <td {...cell.getCellProps()} className={cell.column.className}> {cell.render('Cell')} </td> ))} </tr> ) })} </tbody> </table> {/* Modal直接放入渲染树,靠状态控制显隐 */} <BasicModal open={modalOpen} onClose={handleCloseModal} row={currentRow} /> </div> ) }
调整后的BasicModal组件
import Modal from '@mui/material/Modal'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; // MUI Modal弹窗位置样式 const style = { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: 400, bgcolor: 'background.paper', boxShadow: 24, p: 4, }; export default function BasicModal({ open, onClose, row }) { return ( <div> <Modal open={open} onClose={onClose} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" > <Box sx={style}> <Typography id="modal-modal-title" variant="h6" component="h2"> 行详情 </Typography> <Typography id="modal-modal-description" sx={{ mt: 2 }}> 当前行ID:{row?.id} {/* 在此处补充你需要展示的其他行字段 */} </Typography> </Box> </Modal> </div> ); }
注意事项
- 所有Hook必须写在函数组件、自定义Hook的顶层作用域,禁止在事件回调、条件判断、循环嵌套中调用Hook
- 条件渲染的组件必须放在JSX渲染树中,通过状态控制显隐,不要试图在事件回调中返回组件触发渲染
内容的提问来源于stack exchange,提问作者Semb
相关产品推荐
相关产品推荐

