Solvedreact slick Drag on sliders fires a click event

When you drag a slider with a tag "<a href="www.google.com />" slick fires a click to finish the drag.
How can i prevent that?

example:
https://jsfiddle.net/nexuszgt/pgoap3bf/4/

thanks.

23 Answers

✔️Accepted Answer

I thought I'd give my two cents and share how I fixed this issue using a React Functional component with Hooks. The key here is to capture the click event using onClickCapture for the slider children (https://javascript.info/bubbling-and-capturing)

import React, { useState, useCallback } from 'react'
import Slick from 'react-slick'

export const Slider = ({children, ...props}) => {
  const [dragging, setDragging] = useState(false)

    const handleBeforeChange = useCallback(() => {
        console.log('handleBeforeChange')
        setDragging(true)
    }, [setDragging])

    const handleAfterChange = useCallback(() => {
        console.log('handleAfterChange')
        setDragging(false)
    }, [setDragging])

    const handleOnItemClick = useCallback(
        e => {
            console.log('handleOnItemClick')
            if (dragging) e.stopPropagation()
        },
        [dragging]
    ) 

   return (
      <Slick 
          beforeChange={handleBeforeChange}
          afterChange={handleAfterChange}
          {...props}
      >
         {React.Children.map(children, child => (
             <div onClickCapture={handleOnItemClick}>{child}</div>
         ))}
      </Slick>
  )
} 

Other Answers:

@costagolub your "hack" works but i think slick-lib should be fix that because it's a behavior we don't expect

@GiancarlosIO
You can achieve the desired effect by using beforeChangeand afterChangemethods and assign an onClick on the anchors.
Based on your sample code this should be working and will not force links to open on dragging. You can also use ES6 classes instead and bind to this instead of a variable.
https://jsfiddle.net/eckrawhy/

@fnhipster Thanks for your solution! I'm a bit sad that there is no built-in way to prevent clicks on the slider content when the user dragged it (should be possible, shouldn't it?), so I built my own drag detection:

import React, { PropsWithChildren, Ref, useState } from "react";
import Slider, { Settings as SliderProps } from "react-slick";

export type DefaultSliderProps = SliderProps;

/**
 * Threshold from which mouse movement with pressed mouse button
 * is considered a drag instead of a click.
 */
const MoveDragThreshold = 10;


function useDragDetection(): {
  handleMouseDown: () => void;
  dragging: boolean;
} {
  const [mouseDown, setMouseDown] = useState(false);
  const [dragging, setDragging] = useState(false);

  useEffect(() => {
    let mouseMove = 0;

    function handleMouseUp(): void {
      setMouseDown(false);
    }

    function handleMouseMove(e: MouseEvent): void {
      mouseMove += Math.abs(e.movementX) + Math.abs(e.movementY);
      setDragging(mouseMove > MoveDragThreshold);
    }

    if (mouseDown) {
      document.addEventListener("mouseup", handleMouseUp);
      document.addEventListener("mousemove", handleMouseMove);
    }

    return () => {
      document.removeEventListener("mouseup", handleMouseUp);
      document.removeEventListener("mousemove", handleMouseMove);
    };
  }, [mouseDown]);

  function handleMouseDown(): void {
    setMouseDown(true);
    setDragging(false);
  }

  return {
    handleMouseDown,
    dragging,
  };
}

export function DefaultSlider(
  props: PropsWithChildren<DefaultSliderProps>
): React.ReactElement {
  const { children, ...sliderProps } = props;

  const {
    handleMouseDown,
    dragging,
  } = useDragDetection();

  function handleChildClick(
    e: React.MouseEvent<HTMLDivElement, MouseEvent>
  ): void {
    if (dragging) {
      e.preventDefault();
    }
  }

  return (
    <Slider {...sliderProps}>
      {React.Children.map(children, (child) => (
        <div
          onMouseDownCapture={handleMouseDown}
          onClickCapture={handleChildClick}
        >
          {child}
        </div>
      ))}
    </Slider>
  );
}

The solution from @fnhipster was already quite good, but I didn't like that it still triggered the click when the user only dragged a bit without changing the to another slide. So I introduced the MoveDragThreshold to distinguish between a click and a drag. It might be useful even outside the sliders. Hope this helps some fellow devs! :)

I like @fnhipster's approach but found that occasionally beforeChange would fire when first mounted but without afterChange also firing. This seems to be related to the issue where initialSlide is not honored sometimes in infinite mode. Probably a deeper timing issue, but at any rate, it meant that sometimes no clicks would register until a swipe event occurred.

I'm getting around that by using the swipe event directly, which as far as I've seen always fires before the child's click event.

import React, { useCallback, useState } from 'react'
import PropTypes from 'prop-types'
import Slick from 'react-slick'

export default ({ children, ...rest }) => {
  const [swiped, setSwiped] = useState(false)

  const handleSwiped = useCallback(() => {
    setSwiped(true)
  }, [setSwiped])

  const handleOnItemClick = useCallback(
    (e) => {
      if (swiped) {
        e.stopPropagation()
        e.preventDefault()
        setSwiped(false)
      }
    },
    [swiped],
  )

  return (
    <Slick
      onSwipe={handleSwiped}
      {...rest}
    >
      {React.Children.map(children, child => (
        <div onClickCapture={handleOnItemClick}>{child}</div>
      ))}
    </Slick>
  )
}

HTH

More Issues: