reactjstypescriptmaterial-uireact-virtualizedreact-sortable-hoc

React Click/Mouse Event Handling [react-sortable-hoc, material-ui, react-virtualized]


I have the following example

Stackblitz Live Working Example

If you press the delete button on the red bin icon, it doesn't fire my onClick event handler (instead sorting starts straight away).

If you press the delete button background of the button (circle around the bin icon), it fires the button onClick handler and acts accordingly.

I have tried putting different onClick (including xxxxCapture versions and e.preventDefault()) but not having any success.

Questions

Therefore I have 2 questions:

  1. Is there a good way to figure out where in the tree the click is being first handled?
  2. How can I make my button onClick consistent across the background as well as the icon itself?

Code

For those who can't open the Stackblitz, the code is:

import React from 'react';
import { Button, IconButton, Tooltip, Typography } from "@material-ui/core";

import { Add, DeleteForever } from "@material-ui/icons";
import { AutoSizer, Column, Index, Table, TableCellProps, TableProps, TableRowProps, WindowScroller, defaultTableRowRenderer } from "react-virtualized";
import { SortableContainer, SortableElement, SortEnd, SortEndHandler, SortEvent } from "react-sortable-hoc";


import 'react-virtualized/styles.css';

const SortableTable = SortableContainer<TableProps>((props: TableProps) => (
  <Table {...props} />
));

const SortableRow = SortableElement<TableRowProps>(
  (props: TableRowProps) => defaultTableRowRenderer(props) as any
);

const sortableRowRenderer = (props: TableRowProps) => {
  return <SortableRow {...props} />;
};

interface IRow {
  value: string;
}

class Grid extends React.Component<any, { items: IRow[] }> {

  remove(rowData: any): any {
    const items = this.state.items;

    if (!items) {
      return;
    }

    const index = items.indexOf(rowData);

    const newItems = [...items.slice(0, index), ...items.slice(index + 1)];

    this.setState({ items: newItems });
  }

  constructor(props: any) {
    super(props);

    this.state = {
      items: this.getDefaultItems()
    };
  }

  private getDefaultItems = () => {
    return [
      { value: "one" },
      { value: "two" },
      { value: "three" },
      { value: "four" },
      { value: "five" }
    ]
  }

  private rowRenderer = (props: TableRowProps) => {
    return defaultTableRowRenderer(props);
  };

  public render() {
    return (
      <div>
        <WindowScroller>
          {({ height, isScrolling, onChildScroll, scrollTop }) => (
            <AutoSizer disableHeight={true}>
              {size => (
                <SortableTable
                  headerHeight={38}
                  autoHeight={true}
                  height={height}
                  rowCount={this.state.items.length}
                  scrollTop={scrollTop}
                  rowGetter={this.getItem}
                  rowHeight={37}
                  width={size.width}
                  rowRenderer={sortableRowRenderer}
                >
                  <Column label={"value"} dataKey={"value"} width={160} />
                  <Column
                    dataKey={"buttons"}
                    cellRenderer={this.buttonsCellRenderer}
                    width={48}
                    minWidth={48}
                    maxWidth={48}
                  />
                </SortableTable>
              )}
            </AutoSizer>
          )}
        </WindowScroller>
        <Button onClick={this.reset}>Reset</Button>
      </div>
    );
  }

  private reset = () => {
    this.setState({ items: this.getDefaultItems() });
  }

  private buttonsCellRenderer = (props: TableCellProps) => {
    const remove = (event: any) => {
      console.log("remove");
      console.log(event);

      this.remove(props.rowData);
    };

    const removeWithPrevent = (event: any) => {
      console.log("removeWithPrevent");
      console.log(event);

      this.remove(props.rowData);
    };

    return (
      <Tooltip title="Delete Line Item" enterDelay={500}>
        <IconButton onClick={remove}>
          <DeleteForever fontSize="small" color="error" onClick={remove} />
        </IconButton>
      </Tooltip>
    );
  };

  private getItem = (info: Index) => {
    const rows = this.state.items;

    const row = rows[info.index];

    return row;
  };
}

export default Grid;

Solution

  • i am working on same kind of application where i want to perform some operation when i clicked somthing on cell and also made them sortable. it can be achieved in two ways

    1. add delay prop pressDelay props in SortableContainer HOC. docs let's say pressDelay=500. what this will do is that if you hold your click for 500ms the row will become sortable otherwise just normal click event will fire.in my opinion this will work just fine

    2.add Handle at starting of the row and make sortable by only by dragging the Handle. for this to achieve you need to implement customTableRowRenderer. just copy the code from here rowRenderer and import the Handle from sortable hoc and just change the return function

      import { SortableHandle } from 'react-sortable-hoc';
    
         const DragHandle = SortableHandle(() => columns[0]);
          return (
            <div
              {...a11yProps}
              className={className}
              key={key}
              role='row'
              style={style}>
              <DragHandle />
              {columns.slice(1, columns.length)}
            </div>
          );
    

    do not forgot to add Handle as first column.