react-grid-layout / react-draggable

React draggable component
MIT License
9.01k stars 1.02k forks source link

How to prevent execution of "click" event after drag? #531

Open akaribrahim opened 3 years ago

akaribrahim commented 3 years ago

Hi, I have Draggable element and inside of it, I have a component with onClick event. At the end of the drag, the click event is triggered. My question is how to prevent this action and how to seperate these to event? Thanks. Ekran Alıntısı

STRML commented 3 years ago

There's not any way that I'm aware of to stop the event from reaching the child. I assume you want a click to keep working, but a drag to prevent the click from happening. You can accomplish this with some local state.

https://codesandbox.io/s/nervous-burnell-e2r95?file=/src/App.js

Holybasil commented 3 years ago

Same problem

marcocheungkami commented 3 years ago

Hi guys, you may take a look at this example https://codesandbox.io/s/material-demo-gc0le?file=/demo.js from https://stackoverflow.com/questions/59136239/how-handle-long-press-event-in-react-web. You may consider drag as a 'long press'. So you can distinguish between a click event and a drag event by checking their pressing time. Just need to return the onClick event if a long press is detected. onClick={(e) => { e.stopPropagation() if (isCommandHandled) return //some actions here }}

imanderson commented 3 years ago

Hi guys,

I just had a simular problem, and the way I solved it was by checking if a drag happened by checking the deltaX and Y. If not drag happened, then I call the "onClick" handler. Maybe this could be incorporated in the source code? I would gladly send a PR

pbenard73 commented 2 years ago

I use this, to handle the drag only after a time lapse

const PRESS_TIME_UNTIL_DRAG_MS = 250:

const IconDraggable = ({ children, onClick }) => {
  const [isDragging, setDragging] = useState(false)

  const handleClick = () => {
    if (isDragging === true) {
      return
    }

    onClick()
  }

  return (
    <Draggable
      onStart={() => setTimeout(() => setDragging(true) , PRESS_TIME_UNTIL_DRAG_MS)}
      onDrag={e => {
          if (isDragging === false) {
             e.preventDefault()
            return false
          }
      }}
      >
      <div style={{ position: 'absolute' }} onClick={() => handleClick(isDragging)}>
        {children}
      </div>
    </Draggable>
  )
}
MichaelKim39 commented 2 years ago

Hi guys,

I just had a simular problem, and the way I solved it was by checking if a drag happened by checking the deltaX and Y. If not drag happened, then I call the "onClick" handler. Maybe this could be incorporated in the source code? I would gladly send a PR

I've tried all the solutions above and this seems like the best solution, where less accurate clicks that drag for a few pixels are still counted as clicks. The below code shows the logic implemented.

`

const [dragStartPos, setDragStartPos] = useState({ x: 0, y: 0 });

const onStart = (e) => {
    setDragStartPos({ x: e.screenX, y: e.screenY });
};

const onStop = (e) => {
    const dragX = Math.abs(dragStartPos.x - e.screenX);
    const dragY = Math.abs(dragStartPos.y - e.screenY);
    if (dragX < 5 || dragY < 5) {
        console.log(`click with drag of ${dragX}, ${dragY}`);
        // onClick functionality here
    } else {
        console.log(`click cancelled with drag of ${dragX}, ${dragY}`);
    }
};

`

KDKHD commented 11 months ago

Using a ref to store the "isDragged" state works well for me.

const Parent: React.FC<Props> = () => {

  const draggedRef = useRef<boolean>(false)

  return (
    <Draggable
      handle=".handle"
      onDrag={() => {
        draggedRef.current = true
      }}
    >
      <div className="handle">
        <Child draggedRef={draggedRef}/>
      </div>
    </Draggable>
  )
}

const Child: React.FC<Props> = ({
  draggedRef,
}: {
  draggedRef: React.MutableRefObject<boolean>
}) => {
  function onClick() {
    const dragged = draggedRef.current
    draggedRef.current = false
    if (!dragged) {
      alert('button was clicked but not dragged')
    }
  }

  return <button onClick={onClick}>A Button</button>
}
developerjhp commented 11 months ago

add this css to your child wrapper comp.

for example

 <Draggable ...>
     <ChildCompWrapper isDragging={isDragging}>
              {...............}
     </ChildCompWrapper>    
 </Draggable>  

const ChildCompWrapper = styled.div pointer-events: ${({ isDragging }) => isDragging && 'none'};

ChoiYongWon commented 6 months ago

Using a ref to store the "isDragged" state works well for me.

const Parent: React.FC<Props> = () => {

  const draggedRef = useRef<boolean>(false)

  return (
    <Draggable
      handle=".handle"
      onDrag={() => {
        draggedRef.current = true
      }}
    >
      <div className="handle">
        <Child draggedRef={draggedRef}/>
      </div>
    </Draggable>
  )
}

const Child: React.FC<Props> = ({
  draggedRef,
}: {
  draggedRef: React.MutableRefObject<boolean>
}) => {
  function onClick() {
    const dragged = draggedRef.current
    draggedRef.current = false
    if (!dragged) {
      alert('button was clicked but not dragged')
    }
  }

  return <button onClick={onClick}>A Button</button>
}

Thanks It Worked!!!