Search code examples
javascriptreactjsdom-eventsreact-tablestoppropagation

stopPropagation does not work in a hierarchy of components


I am trying to avoid an onClick event that is on each particular row, whenever show/less is being clicked under one column's data. I have a grid that basically has select, and multi select features. Also there is show more/show less for one of the column. Whenever I click on Show More/Less, the row selection is triggered. I want to avoid this. Can someone tell what is wrong here. say If a have multiple instances of similar grids, I would want the row select functionality to that grid which has prop in it

Sandbox: https://codesandbox.io/s/react-table-row-table-alternate-single-row-working-5fr81

 import * as React from "react";
import ReactTable from "react-table";
import "react-table/react-table.css";

export default class DataGrid extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      selectedRows: []
    };
  }

  rowClick = (state, rowInfo) => {
    if (rowInfo) {
      let selectedRows = new Set();
      return {
        onClick: e => {
          e.stopPropagation();
          if (e.ctrlKey) {
            selectedRows = this.state.selectedRows;
            rowInfo._index = rowInfo.index;
            if (
              selectedRows.filter(row => row._index === rowInfo._index)
                .length === 0
            )
              selectedRows.push(rowInfo);
            else
              selectedRows = selectedRows.filter(
                row => row._index !== rowInfo._index
              );
            this.setState({
              selectedRows
            });
          } else {
            selectedRows = [];
            rowInfo._index = rowInfo.index;
            selectedRows.push(rowInfo);
          }
          this.setState(
            {
              selectedRows
            },
            () => {
              console.log("final values", this.state.selectedRows);
              this.props.rowClicked(this.state.selectedRows);
            }
          );
        },
        style: {
          background:
            this.state.selectedRows.some(e => e._index === rowInfo.index) &&
            "#9bdfff"
        }
      };
    } else {
      return "";
    }
  };

  render() {
    return (
      <ReactTable
        data={this.props.data}
        columns={this.props.columns}
        getTrProps={this.rowClick}
      />
    );
  }
}


Solution

  • On your ShowMore component, you can modify your onClick event handler to call event.stopPropagation(). This will stop further propagation of the current event in the capturing and bubbling phases, and allow you to click on Show More/Less without triggering row selection.

    const toggleTruncate = (e) => {
      e.stopPropagation();
      setShowMore(!isShowMore);
    }
    

    React actually defines the synthetic event, which will be available on your click event handler, without the need to call document.addEventListener(). You may read more about event handling in React over here.

    And answering your second part of the question, given that the rowClicked event is an optional props of the DataGrid component, you will need to manually check it on the DataGrid component to ensure that it will only be called if it is defined. This is how it can be done:

    if (rowClicked) {
     // do the rest
    }
    

    And this is how your rowClick method should look like. Do take note that I have destructured the props object on the second line. This will ensure that row selection logic will be carried out only if rowClicked is defined.

    rowClick = (state, rowInfo) => {
      const { rowClicked } = this.props;
      if (rowInfo && rowClicked) {
        let selectedRows = new Set();
        return {
          onClick: e => {
            e.stopPropagation();
            if (e.ctrlKey) {
              selectedRows = this.state.selectedRows;
              rowInfo._index = rowInfo.index;
              if (
                selectedRows.filter(row => row._index === rowInfo._index)
                  .length === 0
              )
                selectedRows.push(rowInfo);
              else
                selectedRows = selectedRows.filter(
                  row => row._index !== rowInfo._index
                );
              this.setState({
                selectedRows
              });
            } else {
              selectedRows = [];
              rowInfo._index = rowInfo.index;
              selectedRows.push(rowInfo);
            }
            this.setState(
              {
                selectedRows
              },
              () => {
                rowClicked(this.state.selectedRows);                
              }
            );
          },
          style: {
            background:
              this.state.selectedRows.some(e => e._index === rowInfo.index) &&
              "#9bdfff"
          }
        };
      } else {
        return "";
      }
    };