Когда я наводил курсор на любую строку в Datagrid, я хочу, чтобы кнопка «Анализ» менялась с варианта, обозначенного в общих чертах, на содержащийся. Я не могу заставить какое-либо событие запускаться при наведении курсора на строку и не могу найти никакой информации о том, как обновить/повторно отобразить ячейку в этой строке, когда мышь находится внутри этой строки.
"@mui/x-data-grid": "^5.17.25", "@mui/x-data-grid-generator": "^6.0.0", "@mui/x-data-grid-pro": "^6.0.0",
import React, { useRef, useState, useEffect } from "react";
import { DataGrid, GridRowsProp, GridColDef } from "@mui/x-data-grid";
import Box from "@mui/material/Box";
import Button from "@mui/material/Button";
import { useTheme } from "@mui/system";
import Link from "next/link";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import SearchIcon from "@mui/icons-material/Search";
import AddIcon from "@mui/icons-material/Add";
import CircularProgress from "@mui/material/CircularProgress";
import { alpha, styled, lighten } from "@mui/material/styles";
export default function PropertiesList({ newProperties }) {
const theme = useTheme();
const boxRef = useRef(null);
const [searchText, setSearchText] = useState("");
const columns = getColumns(theme);
function getColumns(theme) {
// commented because irrelevant
return [
{
field: "id",
headerName: "Actions",
width: 150,
renderCell: (params) => {
return (
<Box
sx = {{
display: "flex",
justifyContent: "space-between",
width: "100%"
}}
>
<Link
href = "/properties/[id]"
as = {`/properties/${params.row.original_doc || params.row.id}`}
>
<Button
size = "small"
variant = "outlined"
startIcon = {<CalculateIcon />}
sx = {{
backgroundColor:
hoveredRowId === params.id
? theme.palette.success.main
: ""
}}
>
Analyze
</Button>
</Link>
</Box>
);
}
}
];
}
useEffect(() => {
if (!boxRef.current) return;
const screenHeight = window.innerHeight;
boxRef.current.style.height = `${screenHeight - 120}px`;
}, []);
const handleRowOver = (params) => {
// change the analyze button from "outlined" to "contained" when hovered.
// The below console.info does not trigger.
console.info(`Row ${params.id} is being hovered over`);
};
return (
<Box ref = {boxRef}>
{!newProperties && (
<Box
sx = {{
height: "calc(100vh - 160px)",
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<CircularProgress size = {32} />
</Box>
)}
{newProperties && (
<>
<Box
sx = {{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
background: theme.palette.background.background2,
marginTop: 3,
// marginBottom: 1,
padding: 2,
border: "1px solid " + theme.palette.contrast.contrast1,
borderTopLeftRadius: 8,
borderTopRightRadius: 8
}}
>
<TextField
label = "Search for property"
placeholder = ""
sx = {{ marginTop: 1, marginBottom: 1 }}
onChange = {(event) => setSearchText(event.target.value)}
InputProps = {{
startAdornment: (
<InputAdornment position = "start">
<SearchIcon />
</InputAdornment>
)
}}
/>
<Link href = "/properties/add">
<Button
size = "medium"
variant = "contained"
sx = {{ height: 50 }}
startIcon = {<AddIcon />}
>
Add Property
</Button>
</Link>
</Box>
<DataGrid
rowMouseEnter = {handleRowOver}
sx = {{
border: "1px solid " + theme.palette.contrast.contrast1,
height: "calc(100vh - 280px)",
background: theme.palette.background.background1,
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar": {
height: "0.4em",
width: "0.4em"
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-track": {
background: theme.palette.contrast.contrast1
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb": {
backgroundColor: theme.palette.contrast.contrast2
},
"& .MuiDataGrid-virtualScroller::-webkit-scrollbar-thumb:hover": {
background: theme.palette.contrast.default
},
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8
}}
rows = {newProperties.filter(
(row) =>
(row.address &&
row.address
.toLowerCase()
.includes(searchText.toLowerCase())) ||
(row.city &&
row.city.toLowerCase().includes(searchText.toLowerCase())) ||
(row.state &&
row.state.toLowerCase().includes(searchText.toLowerCase())) ||
(row.zip &&
row.zip
.toString()
.toLowerCase()
.includes(searchText.toLowerCase()))
)}
columns = {columns}
pageSize = {13}
disableColumnFilter
disableSelectionOnClick
disableColumnSelector
/>
</>
)}
</Box>
);
}





MUI DataGrid не включает никаких функций событий, связанных с зависанием.
(У вас есть только такие функции, как onCellClick(), onCellEditStart(), onCellKeyDown(), ...)
Я думал о прослушивателе событий, таких как onMouseEnter и onMouseLeave, но, как упоминалось здесь , это вызовет так много рендеров.
Возможно, использование реквизита sx поможет стилизовать эти блоки с помощью :hover:
Что-то вроде:
function getColumns(theme) {
return [
{
field: "id",
headerName: "Actions",
width: 150,
renderCell: (params) => {
return (
<Box
sx = {{
display: "flex",
justifyContent: "space-between",
width: "100%",
'&:hover button': {
backgroundColor: theme.palette.success.main,
border: '1px solid transparent',
color: theme.palette.common.white,
},
}}
>
<Link
href = "/properties/[id]"
as = {`/properties/${params.row.original_doc || params.row.id}`}
>
<Button
size = "small"
variant = "outlined"
startIcon = {<CalculateIcon />}
sx = {{
borderColor: theme.palette.success.main,
'&:hover': {
backgroundColor: theme.palette.success.main,
borderColor: 'transparent',
color: theme.palette.common.white,
},
}}
>
Analyze
</Button>
</Link>
</Box>
);
},
},
];
}
Связан с
<DataGrid
onRowMouseEnter = {handleRowOver}
// Other props
/>
(не rowMouseEnter)
Используя аналогичную идею, показанную в строке DataGrid:
<DataGrid
// Other props
slotProps = {{
row: {
onMouseEnter: (event) => handleRowOver(Number(event.currentTarget.getAttribute('data-id'))),
onMouseLeave: () => console.info('Mouse left the row'),
},
}}
/>
с:
const handleRowOver = (rowId) => {
console.info(`Row ${rowId} is being hovered over`);
};
При наведении курсора на строку в DataGrid будет вызываться функция handleRowOver, и вы сможете увидеть лог в консоли браузера.
Когда мышь покинет строку, анонимная функция напечатает сообщение.
Эффект наведения для кнопки «Анализ» по-прежнему будет обрабатываться псевдоклассом :hover в реквизите sx.
Как упомянул @VonC в своем ответе, вы можете использовать slotProps для передачи реквизитов элементу строки, в частности onMouseEnter и onMouseLeave. Используя технику, описанную здесь, я смог довольно лаконично воспроизвести поведение, которого вы пытаетесь добиться.
Основная идея состоит в том, чтобы запустить событие внутри onMouseEnter и onMouseLeave, на которое мы подпишемся в нашем пользовательском компоненте кнопки.
Чтобы добиться изоляции событий между разными строками, мы включим идентификатор строки в имя события.
Запускать ваш компонент без контекста было сложно, поэтому для демонстрации принципа я сам построил минимальный DataGrid.
Вы можете увидеть живой пример здесь:
Код:
import React, { FC, useState, useEffect } from "react";
import Button from "@mui/material/Button";
import { DataGrid, GridColDef } from "@mui/x-data-grid";
import CalculateIcon from "@mui/icons-material/Calculate";
const CustomButtonElement: FC<{ rowId: number | string }> = ({ rowId }) => {
const [rowHovered, setRowHovered] = useState(false);
useEffect(() => {
const handleCustomEvent = (e) => setRowHovered(e.detail.hovered);
document.addEventListener(`row${rowId}HoverChange`, handleCustomEvent);
// cleanup listener
return () =>
document.removeEventListener(`row${rowId}HoverChange`, handleCustomEvent);
}, [rowId]);
return (
<Button variant = {rowHovered ? "outlined" : "contained"}>
<CalculateIcon />
</Button>
);
};
export default function DataGridDemo() {
const rows = [
{ id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
{ id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
{ id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
{ id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
{ id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null }
];
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 90 },
{
field: "",
headerName: "Action",
renderCell: (params) => <CustomButtonElement rowId = {params.id} />
},
{ field: "firstName", headerName: "First Name", width: 90 },
{ field: "lastName", headerName: "Last Name", width: 90 }
];
const handleRowHovered = (event: React.MouseEvent<HTMLElement>) => {
const rowId = event.currentTarget?.dataset?.id;
document.dispatchEvent(
new CustomEvent(`row${rowId}HoverChange`, { detail: { hovered: true } })
);
};
const handleRowLeaved = (event: React.MouseEvent<HTMLElement>) => {
const rowId = event.currentTarget?.dataset?.id;
document.dispatchEvent(
new CustomEvent(`row${rowId}HoverChange`, { detail: { hovered: false } })
);
};
return (
<DataGrid
rows = {rows}
columns = {columns}
slotProps = {{
row: {
onMouseEnter: handleRowHovered,
onMouseLeave: handleRowLeaved
}
}}
/>
);
}
Чтобы решить проблему потери состояния, когда компонент выходит из поля зрения (из-за размонтирования ), я добавил useEffect, который будет запускаться при каждом монтировании кнопки и проверять, находится ли мышь над элементом строки кнопки. Для этого я использую совпадения и объект apiRef для более нативного доступа к элементу строки DataGrid через его контекст.
Как оказалось, благодаря тому же apiRef и хуку useGridApiEventHandler можно подписаться на события более нативно (не создавая кастомные), поэтому код получается еще лаконичнее и выразительнее.
Обновленный код (вышеупомянутая Codesandbox также обновлена):
import React, { FC, useState, useEffect } from "react";
import Button from "@mui/material/Button";
import {
DataGrid,
GridColDef,
GridEventListener,
useGridApiContext,
useGridApiEventHandler
} from "@mui/x-data-grid";
import CalculateIcon from "@mui/icons-material/Calculate";
const CustomButtonElement: FC<{ rowId: number | string }> = ({ rowId }) => {
const [rowHovered, setRowHovered] = useState(false);
const apiRef = useGridApiContext();
// runs only "onComponentMount"
useEffect(() => {
if (apiRef.current.getRowElement(rowId).matches(":hover"))
setRowHovered(true);
}, []);
const handleRowEnter: GridEventListener<"rowMouseEnter"> = ({ id }) =>
id === rowId && setRowHovered(true);
const handleRowLeave: GridEventListener<"rowMouseLeave"> = ({ id }) =>
id === rowId && setRowHovered(false);
useGridApiEventHandler(apiRef, "rowMouseEnter", handleRowEnter);
useGridApiEventHandler(apiRef, "rowMouseLeave", handleRowLeave);
return (
<Button variant = {rowHovered ? "outlined" : "contained"}>
<CalculateIcon />
</Button>
);
};
export default function DataGridDemo() {
const rows = [
{ id: 1, lastName: "Snow", firstName: "Jon", age: 35 },
{ id: 2, lastName: "Lannister", firstName: "Cersei", age: 42 },
{ id: 3, lastName: "Lannister", firstName: "Jaime", age: 45 },
{ id: 4, lastName: "Stark", firstName: "Arya", age: 16 },
{ id: 5, lastName: "Targaryen", firstName: "Daenerys", age: null }
];
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 90 },
{
field: "",
headerName: "Action",
renderCell: (params) => <CustomButtonElement rowId = {params.id} />
},
{ field: "firstName", headerName: "First Name", width: 90 },
{ field: "lastName", headerName: "Last Name", width: 90 },
{ field: "age", headerName: "Age", width: 90 }
];
return <DataGrid rows = {rows} columns = {columns} />;
}
Ты босс! Спасибо! Единственное, что мне нужно было сделать, чтобы заставить его работать на моем, это изменить его с slotProps на componentProps. В случае, если это поможет любому, кто читает это.
Единственная другая ошибка, которую я заметил, заключается в том, что если я прокручиваю полностью вправо таблицу с большим количеством столбцов, когда я прокручиваю назад влево, строка, над которой я зависаю, не имеет стилей зависания.
@EvanHessler Действительно... Я не рассматривал случай, когда кнопка перемонтируется, а курсор не покидает строку и не входит в нее (потому что в нашем случае нет прямой проверки "находится ли мышь над строкой"). Я обновлю свой ответ, чтобы принять это во внимание.
Это кажется более подробным, чем мой ответ (и с рабочим примером). Проголосовал.