When I hover over any row in the DataGrid, I want the "Analyze" button to change from variant outlined to contained. I cannot get any event to trigger when the row is hovered over, nor can I find any information on how to update/re-render a cell within that row when the mouse is within that row.
"@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.log does not trigger.
console.log(`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>
);
}
As @VonC mentioned in his answer, you can use slotProps to pass props to a row element, in particular onMouseEnter and onMouseLeave. Using the technique described here, I was able to reproduce the behavior you are trying to achieve in a fairly concise manner.
The main idea is to fire an event inside onMouseEnter and onMouseLeave that we will subscribe to in our custom button component.
In order to achieve isolation of events between different rows, we will include the row id in the event name.
It was difficult to run your component without a context, so to demonstrate the principle, I built a minimal DataGrid myself.
You can see a live example here:
Code:
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
}
}}
/>
);
}
To address the issue of state loss when the component goes out of view (due to unmount ), I added a useEffect which will run on every button mount and check if the mouse is hovering over the button row element. To do this, I use the matches and the apiRef object for more native access to the DataGrid row element through its context.
As it turned out, thanks to the same apiRef and the useGridApiEventHandler
hook, you can subscribe to events in a more native way (withou creating custom ones), so the code is even more concise and expressive.
Updated Code (the above Codesandbox is also updated):
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} />;
}