1

I have a table which shows a list. Each row has 3 columns: item, delete-button-1, delete-button-2. When hovered over delete button, the corresponding item is highlighted in red.

I am using React Hook useState for storing the index of row of which delete button is hovered.

I have two implementations of delete-button, the only difference is in the onClick attribute. Both implementations have a problem.


Delete Button: Implementation 1

  1. Add items to the list
  2. Delete all the items
  3. Add new items to the list

The problem is that the first item of the new list is highlighted in red, which isn't removed until a delete button is hovered.


Delete Button: Implementation 2

  1. Add Items to the list
  2. Delete an item

The problem is that, though the delete-button of the next row comes in the place of delete-button of deleted row, the item doesn't get highlighted until this delete-button is re-hovered.


let id = 0
const {useState} = React

const Table = () => {
  const [list, setList] = useState([])
  const [warningIndex, setWarning] = useState(null)

  const rows = list.map((item, index) => {
    const rowClass = (warningIndex === index) ? "warning" : ""

    return (
      <tr key={index}>
        <td className={rowClass}>{item}</td>
        <td
          className="delete-button"
          onMouseEnter={() => setWarning(index)}
          onMouseLeave={() => setWarning(null)}
          onClick={() => setList([...list.slice(0, index), ...list.slice(index + 1)])}>
          -
        </td>
        <td
          className="delete-button"
          onMouseEnter={() => setWarning(index)}
          onMouseLeave={() => setWarning(null)}
          onClick={() => {
            setList([...list.slice(0, index), ...list.slice(index + 1)])
            setWarning(null)
          }}>
          -
        </td>
      </tr>
    )
  })

  return (
    <table>
      <tbody>
      <tr>
        <td
          className="add-button"
          colSpan="3"
          onClick={() => setList([...list, id++])}>
          +
        </td>
      </tr>
      {rows}
      </tbody>
    </table>
  )
}

ReactDOM.render(<Table/>, document.getElementById('root'));
table {
  width: 200px;
  border-collapse: collapse;
}

td {
  padding: 4px 8px;
  border: 1px solid lightgrey;
}

td.add-button, td.delete-button {
  cursor: pointer;
  width: 1%;
}

td.add-button:hover {
  background-color: lightgrey;
}

td.add-button:active {
  background-color: darkgrey;
}

td.delete-button:hover {
  background-color: #ffcdd2;
}

td.delete-button:active, td.warning {
  background-color: #ef9a9a;
}
<script <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

I want an implementation of delete button which don't have any of these problems. I only want one delete button, these two are what I've tried.

Akshdeep Singh
  • 1,301
  • 1
  • 19
  • 34
  • 1
    Interestingly, your first button works fine in Firefox. There's an outstanding Chrome bug for `mouseenter` and `mouseleave` not working properly when there are layout changes: https://bugs.chromium.org/p/chromium/issues/detail?id=276329 – Jacob Oct 17 '19 at 17:18
  • @Jacob I am also using `electronjs`, so is there any alternative, maybe to `mouseenter` and `mouseleave`, since I do not need cross-browser support? – Akshdeep Singh Oct 17 '19 at 20:11
  • 1
    Their bug tracker does mention some `UpdateHoverPostLayout` runtime flag; maybe there's a way you can get electron to turn on that flag. Looks like this answer may give a way to do that: https://stackoverflow.com/a/54126513/119549 – Jacob Oct 17 '19 at 20:27

2 Answers2

1

Any time your mapping in react and use index as key, it has the potential to cause problems, especially if adding and removing components.

  return (
      <tr key={index}>

Using a unique key may solve your problem. More on react keys React Keys

Mike
  • 106
  • 5
0

How about this? If you check the list is empty. =)

let id = 0
const {useState} = React

const Table = () => {
  const [list, setList] = useState([])
  const [warningIndex, setWarning] = useState(null)

  const rows = list.map((item, index) => {
    const rowClass = (warningIndex === index) ? "warning" : ""

    return (
      <tr key={index}>
        <td className={rowClass}>{item} - index: {index}</td>
        <td
          className="delete-button"
          onMouseEnter={() => setWarning(index)}
          onMouseLeave={() => setWarning(null)}
          onClick={() => {
            const newList = [...list.slice(0, index), ...list.slice(index + 1)]
            if (newList.length === 0){
              setWarning(null)
              }
            setList(newList)
           }}>
          -
        </td>
      </tr>
    )
  })

  return (
    <table>
      <tbody>
      <tr>
        <td
          className="add-button"
          colSpan="3"
          onClick={() => setList([...list, id++])}>
          +
        </td>
      </tr>
      {rows}
      </tbody>
    </table>
  )
}

ReactDOM.render(<Table/>, document.getElementById('root'));
table {
  width: 200px;
  border-collapse: collapse;
}

td {
  padding: 4px 8px;
  border: 1px solid lightgrey;
}

td.add-button, td.delete-button {
  cursor: pointer;
  width: 1%;
}

td.add-button:hover {
  background-color: lightgrey;
}

td.add-button:active {
  background-color: darkgrey;
}

td.delete-button:hover {
  background-color: #ffcdd2;
}

td.delete-button:active, td.warning {
  background-color: #ef9a9a;
}
<script <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Benjie
  • 74
  • 5