如何控制Material UI ClickAway,避免模态框提交时触发?
解决ClickAwayListener在模态框内触发的问题
问题核心在于:ClickAwayListener会把模态框判定为"点击外部"(因为模态框通常挂载在body或独立容器中,不属于DataGrid的父级范围),导致点击模态框提交按钮时触发清除逻辑。下面提供两种可靠的解决方案:
方案一:通过Ref判断点击目标是否属于模态框
利用DOM元素的contains方法,判断点击事件的目标是否在模态框内部,若是则跳过清除操作:
- 父组件中为模态框创建Ref
import React, { useRef, useState } from 'react'; import MyTable from './MyTable'; import EditJobModal from './EditJobModal'; const ParentComponent = () => { const modalRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(false); // 其他业务逻辑... return ( <> <MyTable columns={columns} data={data} childToParent={childToParent} modalRef={modalRef} /> <EditJobModal ref={modalRef} open={isModalOpen} onClose={() => setIsModalOpen(false)} // 模态框其他属性 /> </> ); };
- 修改MyTable组件的handleClickAway逻辑
import React, { useState } from 'react'; import { ClickAwayListener } from '@mui/base'; import { DataGrid, GridToolbar} from '@mui/x-data-grid'; const MyTable = ({columns, data, childToParent, modalRef}) => { const [selectionModel, setSelectionModel] = useState([]); const [selectedRows, setSelectedRows] = useState([]); const handleClickAway = (event) => { // 检查点击目标是否在模态框内部,是则直接返回 if (modalRef.current && modalRef.current.contains(event.target)) { return; } const emptyRow = [{id: ''}]; setSelectionModel([]); setSelectedRows(emptyRow); childToParent(emptyRow); }; return ( <div style={{ height: 600, width: '100%' }}> <ClickAwayListener onClickAway={handleClickAway}> <DataGrid columns={columns} rows={data} pageSize={5} rowsPerPageOptions={[10]} checkboxSelection={false} components={{ Toolbar: GridToolbar }} selectionModel={selectionModel} onSelectionModelChange={(ids) => { if (ids.length > 1) { let selectionSet = new Set(selectionModel); let result = ids.filter((s) => !selectionSet.has(s)); setSelectionModel(result); } else { let selectedIDs = new Set(ids); let selectedRows = data.filter((row) => selectedIDs.has(row.id), ); setSelectedRows(selectedRows); setSelectionModel(ids[0]); childToParent(selectedRows); } }} /> </ClickAwayListener> </div> ); }; export default MyTable;
方案二:根据模态框状态控制ClickAway逻辑
在父组件中维护模态框的打开状态,传递给MyTable,让ClickAway逻辑在模态框打开时失效:
- 父组件传递模态框状态
const ParentComponent = () => { const [isModalOpen, setIsModalOpen] = useState(false); // 其他业务逻辑... return ( <> <MyTable columns={columns} data={data} childToParent={childToParent} isModalOpen={isModalOpen} /> <EditJobModal open={isModalOpen} onClose={() => setIsModalOpen(false)} // 模态框其他属性 /> </> ); };
- 修改MyTable的ClickAway逻辑
方式A:在handleClickAway中判断状态
const MyTable = ({columns, data, childToParent, isModalOpen}) => { // ...其他状态定义 const handleClickAway = () => { // 模态框打开时不执行清除操作 if (isModalOpen) return; const emptyRow = [{id: ''}]; setSelectionModel([]); setSelectedRows(emptyRow); childToParent(emptyRow); }; // ...其余代码不变 };
方式B:条件渲染ClickAwayListener
const MyTable = ({columns, data, childToParent, isModalOpen}) => { // ...其他状态定义 const handleClickAway = () => { const emptyRow = [{id: ''}]; setSelectionModel([]); setSelectedRows(emptyRow); childToParent(emptyRow); }; return ( <div style={{ height: 600, width: '100%' }}> {isModalOpen ? ( <DataGrid columns={columns} rows={data} pageSize={5} rowsPerPageOptions={[10]} checkboxSelection={false} components={{ Toolbar: GridToolbar }} selectionModel={selectionModel} onSelectionModelChange={(ids) => { if (ids.length > 1) { let selectionSet = new Set(selectionModel); let result = ids.filter((s) => !selectionSet.has(s)); setSelectionModel(result); } else { let selectedIDs = new Set(ids); let selectedRows = data.filter((row) => selectedIDs.has(row.id), ); setSelectedRows(selectedRows); setSelectionModel(ids[0]); childToParent(selectedRows); } }} /> ) : ( <ClickAwayListener onClickAway={handleClickAway}> <DataGrid columns={columns} rows={data} pageSize={5} rowsPerPageOptions={[10]} checkboxSelection={false} components={{ Toolbar: GridToolbar }} selectionModel={selectionModel} onSelectionModelChange={(ids) => { if (ids.length > 1) { let selectionSet = new Set(selectionModel); let result = ids.filter((s) => !selectionSet.has(s)); setSelectionModel(result); } else { let selectedIDs = new Set(ids); let selectedRows = data.filter((row) => selectedIDs.has(row.id), ); setSelectedRows(selectedRows); setSelectionModel(ids[0]); childToParent(selectedRows); } }} /> </ClickAwayListener> )} </div> ); };
额外优化点
原代码中handleClickAway里的childToParent(selectedRows)传递的是旧状态(因为setSelectedRows是异步更新),建议直接传递新的空行对象,避免数据不一致问题。
内容的提问来源于stack exchange,提问作者gwydion93
相关产品推荐
相关产品推荐

