React-grid-layout: Capturing grid item click event

Created on 22 Jul 2016  路  18Comments  路  Source: STRML/react-grid-layout

Is it possible to capture the grid item's click and cancel the drag event? I want to open a modal window when a grid item is clicked, but I can't figure out how to implement this. I'm capturing the click with onClick, but stopPropagation and preventDefault don't prevent the mousedown event that starts the dragging process.

Most helpful comment

This may help

<RGL ... >
  <div> // If you add onMouseDown here react-draggable will override it. You will have to use nested element to capture clicks
    <div onMouseDown={ e => e.stopPropagation() }>
      ...
    </div>
  <div>
</RGL>

But you can achieve same effect using state and static elements or draggableCancel selectors.

All 18 comments

Having the same issue!

I opened a question on stackoverflow as well: http://stackoverflow.com/q/38543470/173630

If it's static then it can't be dragged. I want to keep the dragging behavior intact.

Did you tried onMouseDown instead on onClick?

This may help

<RGL ... >
  <div> // If you add onMouseDown here react-draggable will override it. You will have to use nested element to capture clicks
    <div onMouseDown={ e => e.stopPropagation() }>
      ...
    </div>
  <div>
</RGL>

But you can achieve same effect using state and static elements or draggableCancel selectors.

mouseDown is not the event I want to capture: I need to respond when the user completes the click. I'm trying to implement this with static elements now - it looks like I have to re-implement the dragging behavior, in mouseDown/mouseUp events, is that correct?

Hmm.. Can you describe the flow? I mean, when the dragging should be canceled?

React Grid passes onMouseDown, onMouseUp, onTouchStart, onTouchEnd, className and style props to each child of <ReactGridLayout>. My solution was to pass custom GridElement components as children of <ReactGridLayout> and just execute those events whenever I see fit, so onDragStart and onDragOver events.
Here's a snippet of how my GridElement looks:

  onDragStart = (e) => {
    e.preventDefault();
    e.stopPropagation();
    this.props.onMouseDown(e);
  }

  onDragEnd = (e) => {
    e.preventDefault();
    e.stopPropagation();
    this.props.onMouseUp(e);
  }

  // ===== Render Methods ======

  render() {
    // React grid defines some important style, so it needs to be passed
    const { children, style, className } = this.props;

    const child = children[0];
    const dragHandle = children[1]; // always a second element. First element is an array of real children;

    return (
      <div
        draggable
        className={className}
        onDragStart={this.onDragStart}  // this fixes rgl's onClick and onMouseDown
        onMouseUp={this.onDragEnd}  // use onMouseUp because onDragStart prevents default
        style={style}
      >
        {child}
        {dragHandle}
      </div>
    );
  }

}

GridElement.propTypes = {
  // React-grid-layout injected props
  className: PropTypes.string,
  onMouseDown: PropTypes.func,
  onMouseUp: PropTypes.func,
  onTouchStart: PropTypes.func,
  onTouchEnd: PropTypes.func,
  'data-grid': PropTypes.object,
};

GridElement.defaultProps = {
  // When element is static, rgl doesn't pass these props
  onMouseDown: () => {},
  onMouseUp: () => {},
  onTouchStart: () => {},
  onTouchEnd: () => {},
};

Any update on that? No better solution since August?

If you would like a better solution, you are free to contribute one - remember that this is a collaborative project and work in progress.

Thanks for your quick reply, I've used @martynasj solution and it works nicely but have done a bit of hacking for the resize to not trigger a click. I was quite surprise that such a great component haven't that feature in. I'll give a try to backed this feature in.

I've just tried this solution on Firefox, unfortunately it does work properly...

@DigitalMarc I've worked around this issue by using a <button> inside the grid item and responding to the onClick event there. My code is here: https://github.com/ccnmtl/juxtapose/blob/master/src/TrackElement.jsx#L83

Cheers @nikolas. I went for the idiomatic hacky way but with the lowest friction to sort this out on both firefox and chrome as event handling behave differently.
I listen for onDrag (was not working with onDragStart) and on onDragStop, setting a flag. Once dragging is done I reset that flag with a bit of delay, otherwise onClick is triggered.
Simply put the flag is used to secure onClick.
Same logic is used for resize. Hope that can help:

import React from 'react';
import { Responsive, WidthProvider } from 'react-grid-layout';
import _ from 'lodash';

const ResponsiveReactGridLayout = WidthProvider(Responsive);

class Exemple extends React.Component {
    constructor(props) {
        super(props)
        this.isDragging = false;
        this.isResizing = false;
    }

    static propTypes = {
        onItemClick: React.PropTypes.func.isRequired
    };

    onItemClick = (e) => {
        // idiomatic way to prevent a click when resizing
        if (!this.isDragging && !this.isResizing)
            this.props.onItemClick(e);
    }

    onDrag = (e) => {
        this.isDragging = true;
    }
    onDragStop = (e) => {
        // HACK: add some delay otherwise a click event is sent
        setTimeout((obj) => { obj.isDragging = false }, 200, this)
    }
    onResizeStart = (e) => {
        this.isResizing = true;
    }
    onResizeStop = (e) => {
        // HACK: add some delay otherwise a click event is sent
        setTimeout((obj) => { obj.isResizing = false }, 200, this)
    }
    createElement = (el) => {
        return (
            <div key={el.datagrid.i} data-grid={el.datagrid} onClick={this.onItemClick.bind(this, el.datagrid.i)} >
                <div>....</div>
            </div>
        );
    }
    render() {
        return (
            <ResponsiveReactGridLayout 
            ... // other props
                onDrag={this.onDrag}
                onDragStop={this.onDragStop}
                onResizeStart={this.onResizeStart}
                onResizeStop={this.onResizeStop}
                >
                {_.map(this.props.items, this.createElement)}
            </ResponsiveReactGridLayout>
        );
    }
}

About what @RiiD wrote:

<RGL ... >
  <div> // If you add onMouseDown here react-draggable will override it. You will have to use nested element to capture clicks
    <div onMouseDown={ e => e.stopPropagation() }>
      ...
    </div>
  <div>
</RGL>

This doesn't work, because the e.stopPropagation() prevents react-draggable from working.
It happens in this EventPluginUtils.js:

if (Array.isArray(dispatchListeners)) {
    for (var i = 0; i < dispatchListeners.length; i++) {
      if (event.isPropagationStopped()) {
        break;
      }

My case is nested RGL with dragging, and when I drag a GridItem of the nested RGL , both RGL start dragging.
I would like to have only the inner RGL drag, so I need to stopPropagation. My GridItems are Custom Components which add a <button> child. As @RiiD wrote, if I put my onMouseDown event listener on the GridItem, react-draggable will override it, and it will not be called. I experienced that.

So How do I prevent propagation and still have react-draggable work?
I open issue https://github.com/STRML/react-grid-layout/issues/502 for this.

after trying out different things, the thing that worked the best for me was to create a draggable handle (draggable cancel also worked, but this suits my needs better).
I did manage to do it through handle drag start event and event handlers on all nested components that needed it, but that feel quite right.

@minjaitk if you define a drag handle, does this problem go away? you're able to handle onClick as usual? i tried that but didnt work

import GridLayout, { Layout } from "react-grid-layout";

export default () => {
  const layout = [
    { i: "a", x: 0, y: 0, w: 1, h: 2, static: true },
    { i: "b", x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
    { i: "c", x: 4, y: 0, w: 1, h: 2 },
  ];

  return (
    <GridLayout
      className="layout"
      layout={layout}
      cols={12}
      rowHeight={30}
      width={1200}
      draggableHandle=".drag"
    >
      {layout.map((l) => (
        <div key={l.i}>
          <Elem />
        </div>
      ))}
    </GridLayout>
  );
};

const Elem = () => {
  return (
    <div onMouseDown={() => console.log("this works")}>
      <div className="drag">Header</div>
      <div>{/* onClick on this doesnt work */}
        <div>Body</div> 
        <div>Body</div>
        <div>Body</div>
        <div>Body</div>
        <div>Body</div>
        <div>Body</div>
      </div>
    </div>
  );
};

Was this page helpful?
0 / 5 - 0 ratings