Home > Software design >  onClick event not working on icon displayed on hover
onClick event not working on icon displayed on hover

Time:01-08

React newbie here.

There's a ItemsList component which is basically a table displaying some information. I managed to display two icons (edit and delete) when the row in the table is hovered (the same as Gmail). And then I wanted to render a DeleteModal component, which is basically a delete confirmation, when the delete icon is clicked.

The table and the icons on hover

import styles from './ItemsList.module.scss';
import '../../../styles/buttons.scss';

const ItemsList = () => {

const dispatch = useDispatch();

    
    const handleOpenDeleteItemModal = () => {
        dispatch(SET_DELETE_ITEM_MODAL(true));
        dispatch(SET_SIDEBAR(false));
    };

    const handleMouseEnter = (e) => {
        e.currentTarget.lastElementChild.innerHTML = ReactDOMServer.renderToString(
            <>
                <BiMessageSquareEdit className={styles.list__icons} />
                <BsTrash
                    className={styles.list__icons}
                    onClick={handleOpenDeleteItemModal}
                />
            </>
        );
    };

    const handleMouseLeave = (e, createdAt) => {
        e.currentTarget.lastElementChild.innerHTML = ReactDOMServer.renderToString(
            <Moment format="DD/MM/YY">{createdAt}</Moment>
        );
    };

        return (
<div className={styles.list__table}>
                {!isLoading && items.length === 0 ? (
                    <p>No items found, please add an item.</p>
                ) : (
                    <table>
                        <thead>
                            <tr>
                                <th>Number</th>
                                <th>Name</th>
                                <th>Category</th>
                                <th>Price</th>
                                <th>Quantity</th>
                                <th>Value</th>
                                <th>Date</th>
                            </tr>
                        </thead>
                        <tbody>
                            {currentItems.map((item, index) => {
                                const { _id, name, category, price, quantity, createdAt } =
                                    item;
                                return (
                                    <tr
                                        onm ouseEnter={(e) => handleMouseEnter(e)}
                                        onm ouseLeave={(e) => handleMouseLeave(e, createdAt)}
                                        key={_id}
                                    >
                                        <td>{index   1   '.'}</td>
                                        <td>{shortenText(name, 15)}</td>
                                        <td>{category}</td>
                                        <td>
                                            {'£'}
                                            {price}
                                        </td>
                                        <td>{quantity}</td>
                                        <td>
                                            {'£'}
                                            {price * quantity}
                                        </td>
                                        <td>
                                            <Moment format="DD/MM/YY">{createdAt}</Moment>
                                        </td>
                                    </tr>
                                );
                            })}
                        </tbody>
                    </table>
                )}
            </div>)

}

CSS

.list__container {
    padding: 2rem;
    hr {
        border: 1px solid a.$hr;
    }
    .list__topSection {
        padding: 3rem 0 1.5rem 0;
        display: flex;
        align-items: center;
        justify-content: space-between;

        h3 {
            font-size: 1.5rem;
            font-family: a.$roboto;
            font-weight: a.$medium;
        }
    }
    .list__table {
        font-family: a.$roboto;
        table {
            border-collapse: collapse;
            width: 100%;

            font-size: 1rem;
        }
        th,
        td {
            vertical-align: top;
            text-align: left;
            padding: 8px;
        }
        th {
            font-weight: a.$regular;
            background-color: a.$primary-color;
            color: white;
        }
        tr {
            border-bottom: 1px solid #ccc;
        }

        tr:nth-child(odd) {
            background-color: rgb(234, 234, 234);
        }
        tr:nth-child(even) {
            background-color: #fff;
        }
    }
    .pagination {
        list-style: none;
        display: flex;
        justify-content: center;
        align-items: center;
        margin: 2rem 0;
        font-size: 1rem;
        position: absolute;
        bottom: 0;
        right: 0;
        left: 0;
    }

    .pagination,
    .page__num,
    .page__next__prev {
        font-family: a.$roboto;
        padding: 5px 10px;
        cursor: pointer;
        border-radius: 3px;
        margin: 2px;
    }

    .pagination .page__num {
        border: 1px solid a.$primary-color;
    }

    .page__active {
        background-color: a.$primary-color;
        color: rgb(255, 255, 255);
        height: 100%;
    }
    .pagination .page__num:hover {
        color: #fff;
        background-color: a.$primary-color;
    }

    .page__disabled__link {
        color: rgb(182, 182, 182);
        cursor: none;
    }
}
.list__icons {
    font-size: 1.2rem;
    cursor: pointer;
}

When I manually change the modal state to true, it renders normally. So I am assuming there's something wrong when the icons are displayed or with the onClick event.

Thank you in advance.

CodePudding user response:

What you are attempting to do is quite odd and not the way you go about this. You should never call ReactDOMServer.renderToString inside a component -- this is set up basically a whole new react tree disconnected from your existing one. Also, setting innerHTML of anything that's owned by React is not allowed, as it's react-owned DOM and this is an imperative call to change the DOM that is effectively outside of Reacts constructs, which just won't work.

The answer you found on SO for this was, unfortunately, very bad advice.

This is almost certainly the issue. So we need to refactor the hover behavior.

Two options:

  1. Make it so onMouseEnter, the ID of the row is stored in some state, and then in the render of the rows, for the row that matches that ID -- we render the icons.
  2. Always render the buttons into the DOM for every row, but use CSS to display/hide them on hover.

Option 2 is almost certainly better, as you don't need to manage the state unnecessarily when it is known to CSS. Additionally, it will feel more performant as you go up and down the rows since you won't have re-renders happening as the user moves the mouse.

Change the CSS:

.list__container {
    padding: 2rem;
    hr {
        border: 1px solid a.$hr;
    }
    .list__topSection {
        padding: 3rem 0 1.5rem 0;
        display: flex;
        align-items: center;
        justify-content: space-between;

        h3 {
            font-size: 1.5rem;
            font-family: a.$roboto;
            font-weight: a.$medium;
        }
    }
    .list__table {
        font-family: a.$roboto;
        table {
            border-collapse: collapse;
            width: 100%;

            font-size: 1rem;
        }
        th,
        td {
            vertical-align: top;
            text-align: left;
            padding: 8px;
        }
        th {
            font-weight: a.$regular;
            background-color: a.$primary-color;
            color: white;
        }
        tr {
            border-bottom: 1px solid #ccc;
        }

        tr:nth-child(odd) {
            background-color: rgb(234, 234, 234);
        }
        tr:nth-child(even) {
            background-color: #fff;
        }

        tr:hover {
            .list__icons {
                display: inline-block;
            }

            .list__date {
                display: none;
            }
        }
    }
    .pagination {
        list-style: none;
        display: flex;
        justify-content: center;
        align-items: center;
        margin: 2rem 0;
        font-size: 1rem;
        position: absolute;
        bottom: 0;
        right: 0;
        left: 0;
    }

    .pagination,
    .page__num,
    .page__next__prev {
        font-family: a.$roboto;
        padding: 5px 10px;
        cursor: pointer;
        border-radius: 3px;
        margin: 2px;
    }

    .pagination .page__num {
        border: 1px solid a.$primary-color;
    }

    .page__active {
        background-color: a.$primary-color;
        color: rgb(255, 255, 255);
        height: 100%;
    }
    .pagination .page__num:hover {
        color: #fff;
        background-color: a.$primary-color;
    }

    .page__disabled__link {
        color: rgb(182, 182, 182);
        cursor: none;
    }
}
.list__icons {
    display: none;
    font-size: 1.2rem;
    cursor: pointer;
}

Now render it all and apply the right class names:

const ItemsList = () => {
  const dispatch = useDispatch();

  const handleOpenDeleteItemModal = () => {
    dispatch(SET_DELETE_ITEM_MODAL(true));
    dispatch(SET_SIDEBAR(false));
  };

  return (
    <div className={styles.list__table}>
      {!isLoading && items.length === 0 ? (
        <p>No items found, please add an item.</p>
      ) : (
        <table>
          <thead>
            <tr>
              <th>Number</th>
              <th>Name</th>
              <th>Category</th>
              <th>Price</th>
              <th>Quantity</th>
              <th>Value</th>
              <th>Date</th>
            </tr>
          </thead>
          <tbody>
            {currentItems.map((item, index) => {
              const { _id, name, category, price, quantity, createdAt } = item;
              return (
                <tr key={_id}>
                  <td>{index   1   "."}</td>
                  <td>{shortenText(name, 15)}</td>
                  <td>{category}</td>
                  <td>
                    {"£"}
                    {price}
                  </td>
                  <td>{quantity}</td>
                  <td>
                    {"£"}
                    {price * quantity}
                  </td>
                  <td>
                    <Moment format="DD/MM/YY" className={styles.list__date}>{createdAt}</Moment>
                    <>
                      <BiMessageSquareEdit className={styles.list__icons} />
                      <BsTrash
                        className={styles.list__icons}
                        onClick={handleOpenDeleteItemModal}
                      />
                    </>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      )}
    </div>
  );
};
  • Related