Material-table: Show action only on row hover

Created on 7 May 2020  路  10Comments  路  Source: mbrn/material-table

Is there a way to show the action buttons(edit, delete etc.) only when hovering over a specific row?

has a solution question

Most helpful comment

@dmfarcas

I take that back - I was able to figure this out.. The only downside is it is a little hacky since you have to force a state change in order to re-render the edited row, otherwise the edited data does not show up...

Check out a live demo here

import React, { useState } from "react";
import { render } from "react-dom";
import MaterialTable, { MTableBodyRow, MTableEditRow } from "material-table";
import tableIcons from "./TableIcons.js";
import { Edit } from "@material-ui/icons";

const rando = max => Math.floor(Math.random() * max);
const words = ["Paper", "Rock", "Scissors"];
const rawData = [];

for (let i = 0; i < 100; i++)
  rawData.push({ id: rando(300), name: words[i % words.length] });

const columns = [
  { title: "Id", field: "id" },
  { title: "Name", field: "name" }
];

const App = () => {
  const [data, setData] = useState(rawData);
  const [hoveringOver, setHoveringOver] = useState('');

  // This is the only downside.. very hacky
  const [, setForceUpdate] = useState(0);
  const forceUpdate = () => setForceUpdate(old => old + 1);

  const handleRowHover = (event, propsData) => setHoveringOver(propsData.data.tableData.id);
  const handleRowHoverLeave = (event, propsData) => setHoveringOver('');

  const handleEditClick = (event, rowData) => {
    rowData.tableData.editing = 'update';
    forceUpdate();
  }

  return (
    <MaterialTable
      data={data}
      columns={columns}
      localization={{
        header: { actions: <td style={{ width: '26px' }}></td> }
      }}
      actions={[
        (rowData) => {
          return hoveringOver !== "" && rowData.tableData.id === hoveringOver
            ? { icon: Edit, hidden: false, onClick: handleEditClick }
            : { icon: Edit, hidden: true, onClick: ()=>{} }
        },
      ]}
      components={{
        Row: props => {
          return (
            <MTableBodyRow 
              {...props} 
              onMouseEnter={e => handleRowHover(e, props)} 
              onMouseLeave={e => handleRowHoverLeave(e, props)}
            />
          )
        },
        EditRow: props => {
          return (
            <MTableEditRow 
              {...props}
              onEditingCanceled={(mode, rowData) => {
                rowData.tableData.editing = undefined;
                forceUpdate();
              }}
              onEditingApproved={(mode, newData, oldData) => {
                const dataCopy = [...data];
                dataCopy[oldData.tableData.id] = newData;
                setData(dataCopy);
              }}
            />
          );
        }
      }}
      icons={tableIcons}
    />
  );
};

render(<App />, document.querySelector("#root"));

All 10 comments

I do not believe there is a way to do this.

@dmfarcas

I take that back - I was able to figure this out.. The only downside is it is a little hacky since you have to force a state change in order to re-render the edited row, otherwise the edited data does not show up...

Check out a live demo here

import React, { useState } from "react";
import { render } from "react-dom";
import MaterialTable, { MTableBodyRow, MTableEditRow } from "material-table";
import tableIcons from "./TableIcons.js";
import { Edit } from "@material-ui/icons";

const rando = max => Math.floor(Math.random() * max);
const words = ["Paper", "Rock", "Scissors"];
const rawData = [];

for (let i = 0; i < 100; i++)
  rawData.push({ id: rando(300), name: words[i % words.length] });

const columns = [
  { title: "Id", field: "id" },
  { title: "Name", field: "name" }
];

const App = () => {
  const [data, setData] = useState(rawData);
  const [hoveringOver, setHoveringOver] = useState('');

  // This is the only downside.. very hacky
  const [, setForceUpdate] = useState(0);
  const forceUpdate = () => setForceUpdate(old => old + 1);

  const handleRowHover = (event, propsData) => setHoveringOver(propsData.data.tableData.id);
  const handleRowHoverLeave = (event, propsData) => setHoveringOver('');

  const handleEditClick = (event, rowData) => {
    rowData.tableData.editing = 'update';
    forceUpdate();
  }

  return (
    <MaterialTable
      data={data}
      columns={columns}
      localization={{
        header: { actions: <td style={{ width: '26px' }}></td> }
      }}
      actions={[
        (rowData) => {
          return hoveringOver !== "" && rowData.tableData.id === hoveringOver
            ? { icon: Edit, hidden: false, onClick: handleEditClick }
            : { icon: Edit, hidden: true, onClick: ()=>{} }
        },
      ]}
      components={{
        Row: props => {
          return (
            <MTableBodyRow 
              {...props} 
              onMouseEnter={e => handleRowHover(e, props)} 
              onMouseLeave={e => handleRowHoverLeave(e, props)}
            />
          )
        },
        EditRow: props => {
          return (
            <MTableEditRow 
              {...props}
              onEditingCanceled={(mode, rowData) => {
                rowData.tableData.editing = undefined;
                forceUpdate();
              }}
              onEditingApproved={(mode, newData, oldData) => {
                const dataCopy = [...data];
                dataCopy[oldData.tableData.id] = newData;
                setData(dataCopy);
              }}
            />
          );
        }
      }}
      icons={tableIcons}
    />
  );
};

render(<App />, document.querySelector("#root"));

@oze4 How can we delete when hover on row, with confirmation text.

@keshavashiya you would follow a similar pattern to 'edit'.

Check out a live demo here

// Demo code
import React, { useState } from "react";
import { render } from "react-dom";
import MaterialTable, { MTableBodyRow, MTableEditRow } from "material-table";
import { Delete as DeleteIcon } from '@material-ui/icons';
import tableIcons from "./TableIcons.js";

const originalData = ["Rock", "Paper", "Scissors"].map(word => ({
  id: Math.floor(Math.random() * 300),
  name: word
}));

const columns = [
  { title: "Id", field: "id" },
  { title: "Name", field: "name" }
];

function App() {
  const [data, setData] = useState(originalData);
  const [hoveringOver, setHoveringOver] = useState('');

  // This is the only downside.. very hacky
  const [, setForceUpdate] = useState(0);
  const forceUpdate = () => setForceUpdate(old => old + 1);

  const handleRowHover = (event, propsData) => setHoveringOver(propsData.data.tableData.id);
  const handleRowHoverLeave = (event, propsData) => setHoveringOver('');

  const handleEditClick = (event, rowData) => {
    rowData.tableData.editing = 'delete';
    forceUpdate();
  }

  return (
    <MaterialTable
      data={data}
      icons={tableIcons}
      columns={columns}
      actions={[
        rowData => {
          return hoveringOver !== "" && rowData.tableData.id === hoveringOver
            ? { icon: DeleteIcon, hidden: false, onClick: handleEditClick }
            : { hidden: true };
        }
      ]}
      components={{
        Row: props => {
          return (
            <MTableBodyRow
              {...props}
              onMouseEnter={e => handleRowHover(e, props)}
              onMouseLeave={e => handleRowHoverLeave(e, props)}
            />
          );
        },
        EditRow: props => {
          return (
            <MTableEditRow 
              {...props}
              onEditingApproved={(mode, newData, oldData) => {
                if (oldData.tableData.editing === 'delete') {
                  const dataCopy = [...data];
                  dataCopy.splice(oldData.tableData.id, 1);
                  setData(dataCopy);
                }
              }}
            />
          );
        }
      }}
    />
  );
}

render(<App />, document.querySelector("#root"));

@oze4 I tried similar method but I'm not able to display confirmation delete text It directly delete row.

@keshavashiya did you check out my demo? It prompts for confirmation, etc.. is that what you're looking for?

@oze4 Thanks I want this type of solution.

@dmfarcas

I take that back - I was able to figure this out.. The only downside is it is a little hacky since you have to force a state change in order to re-render the edited row, otherwise the edited data does not show up...

Check out a live demo here

import React, { useState } from "react";
import { render } from "react-dom";
import MaterialTable, { MTableBodyRow, MTableEditRow } from "material-table";
import tableIcons from "./TableIcons.js";
import { Edit } from "@material-ui/icons";

const rando = max => Math.floor(Math.random() * max);
const words = ["Paper", "Rock", "Scissors"];
const rawData = [];

for (let i = 0; i < 100; i++)
  rawData.push({ id: rando(300), name: words[i % words.length] });

const columns = [
  { title: "Id", field: "id" },
  { title: "Name", field: "name" }
];

const App = () => {
  const [data, setData] = useState(rawData);
  const [hoveringOver, setHoveringOver] = useState('');

  // This is the only downside.. very hacky
  const [, setForceUpdate] = useState(0);
  const forceUpdate = () => setForceUpdate(old => old + 1);

  const handleRowHover = (event, propsData) => setHoveringOver(propsData.data.tableData.id);
  const handleRowHoverLeave = (event, propsData) => setHoveringOver('');

  const handleEditClick = (event, rowData) => {
    rowData.tableData.editing = 'update';
    forceUpdate();
  }

  return (
    <MaterialTable
      data={data}
      columns={columns}
      localization={{
        header: { actions: <td style={{ width: '26px' }}></td> }
      }}
      actions={[
        (rowData) => {
          return hoveringOver !== "" && rowData.tableData.id === hoveringOver
            ? { icon: Edit, hidden: false, onClick: handleEditClick }
            : { icon: Edit, hidden: true, onClick: ()=>{} }
        },
      ]}
      components={{
        Row: props => {
          return (
            <MTableBodyRow 
              {...props} 
              onMouseEnter={e => handleRowHover(e, props)} 
              onMouseLeave={e => handleRowHoverLeave(e, props)}
            />
          )
        },
        EditRow: props => {
          return (
            <MTableEditRow 
              {...props}
              onEditingCanceled={(mode, rowData) => {
                rowData.tableData.editing = undefined;
                forceUpdate();
              }}
              onEditingApproved={(mode, newData, oldData) => {
                const dataCopy = [...data];
                dataCopy[oldData.tableData.id] = newData;
                setData(dataCopy);
              }}
            />
          );
        }
      }}
      icons={tableIcons}
    />
  );
};

render(<App />, document.querySelector("#root"));

A great solution for on hover row actions, thanks.

I found this solution using CSS, since above solution is correct but every time it's updating state and then whole component is rendering.

Demo

@dmfarcas , there is more easy way (imho) to do this. Just need to add class for the row.
Here is a demo - https://react-nhwuqk.stackblitz.io

I made the same on my project, but need also to show different icons based on what is it - draft || sent || scheduled

Was this page helpful?
0 / 5 - 0 ratings