React Material UI Datagrid rowMouseEnter не срабатывает

Когда я наводил курсор на любую строку в 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>
  );
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
0
0
127
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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} />;
}

Это кажется более подробным, чем мой ответ (и с рабочим примером). Проголосовал.

VonC 11.05.2023 08:59

Ты босс! Спасибо! Единственное, что мне нужно было сделать, чтобы заставить его работать на моем, это изменить его с slotProps на componentProps. В случае, если это поможет любому, кто читает это.

Evan Hessler 11.05.2023 17:56

Единственная другая ошибка, которую я заметил, заключается в том, что если я прокручиваю полностью вправо таблицу с большим количеством столбцов, когда я прокручиваю назад влево, строка, над которой я зависаю, не имеет стилей зависания.

Evan Hessler 12.05.2023 00:06

@EvanHessler Действительно... Я не рассматривал случай, когда кнопка перемонтируется, а курсор не покидает строку и не входит в нее (потому что в нашем случае нет прямой проверки "находится ли мышь над строкой"). Я обновлю свой ответ, чтобы принять это во внимание.

Олексій Холостенко 12.05.2023 05:10

Другие вопросы по теме