onDrag event clientX returns 0 in Firefox when converting component to Typescript

Viewed 149

I am converting a React JS custom slider component to a React Typescript component (jsx) and run into the issue that it stops sliding in Firefox. e.clientX is always 0, but the vanilla React JS component works as expected in Firefox. They both work fine in Chrome.

I'm not sure whether this still is the known Firefox bug regarding the "drag" event. I've read these sources: 1, 2, 3, 4, 5.

Questions:

  1. Is this still the same bug mentioned in some of the mentioned sources?
  2. Why is this specifically happening in Typescript, not in Javascript?
  3. What solution would you suggest for full browser support (Firefox, Chrome, Safari)?

Javascript version

import {useRef, useState} from "react";

export default function Slider({initialValue, onChange}) {
    const sliderEl = useRef(null)
    const [dragging, setDragging] = useState(false)
    const [value, setValue] = useState(initialValue)

    const calculateValue = (x) => {
        const {x: sliderX, width: sliderWidth} = sliderEl.current.getBoundingClientRect()
        const value = (x - sliderX) / sliderWidth

        return Math.max(0, Math.min(value, 1))
    }

    const handleDrag = (e) => {
        if (e.clientX === 0) {
            return
        }

        setValue(calculateValue(e.clientX))
    }

    const handleClick = (e) => {
        const value = calculateValue(e.clientX)
        setValue(value)
        onChange(value)
    }

    const offsetLeft = `calc(${value * 100}% - 0.5rem)`

    return <div className="relative h-4" onMouseDown={handleClick} ref={sliderEl}>
        <div className="bg-primary-5 rounded-full h-1 w-full top-1.5 absolute"/>
        <div className="bg-primary-500 rounded-l-full h-1 top-1.5 absolute" style={{width: `${value * 100}%`}}/>
        <div className="rounded-full bg-primary-700 w-4 h-4 absolute top-0" style={{left: offsetLeft}}/>
        <div className="rounded-full bg-primary-500 w-4 h-4 absolute top-0 cursor-pointer"
            draggable={true}
            style={{left: offsetLeft, opacity: dragging ? 0 : 1}}
            onDragStart={(e) => { setDragging(true) }}
            onDrag={handleDrag}
            onDragEnd={(e) => {
                setDragging(false)
                onChange && onChange(value)
            }}
    />
    </div>
}

Typescript conversion

import React, { useRef, useState } from 'react'

type SliderProps = {
    initialValue: number,
    onChange: (value: number) => void
}

export default function Slider({ initialValue = 0, onChange }: SliderProps) {
    const sliderEl = useRef<HTMLDivElement>(null)
    const [dragging, setDragging] = useState(false)
    const [value, setValue] = useState<number>(initialValue)

    const calculateValue = (x: number): number => {
        if(!sliderEl.current) return 0
        const { x: sliderX, width: sliderWidth }: DOMRect = sliderEl.current.getBoundingClientRect()
        const value = (x - sliderX) / sliderWidth

        return Math.max(0, Math.min(value, 1))
    }

    const handleDrag = (e: React.MouseEvent<HTMLDivElement>) => {
        if (e.clientX === 0) {
            return
        }
        
        setValue(calculateValue(e.clientX))
    }

    const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
        const value = calculateValue(e.clientX)

        if(value) {
            setValue(value)
            onChange(value)
        }
    }

    const offsetLeft = `calc(${value * 100}% - 0.5rem)`

    return <div className="relative h-4" onMouseDown={handleClick} ref={sliderEl}>
        <div className="bg-primary-5 rounded-full h-1 w-full top-1.5 absolute"/>
        <div className="bg-primary-500 rounded-l-full h-1 top-1.5 absolute" style={{width: `${value * 100}%`}}/>
        <div className="rounded-full bg-primary-700 w-4 h-4 absolute top-0" style={{left: offsetLeft}}/>
        <div className="rounded-full bg-primary-500 w-4 h-4 absolute top-0 cursor-pointer"
            draggable={true}
            style={{left: offsetLeft, opacity: dragging ? 0 : 1}}
            onDragStart={(e) => { setDragging(true) }}
            onDrag={handleDrag}
            onDragEnd={(e) => {
                setDragging(false)
                onChange && onChange(value)
            }}
    />
    </div>
}

import React, { useRef, useState } from 'react'

type SliderProps = {
    initialValue: number,
    onChange: (value: number) => void
}

export default function Slider({ initialValue = 0, onChange }: SliderProps) {
    const sliderEl = useRef<HTMLDivElement>(null)
    const [dragging, setDragging] = useState(false)
    const [value, setValue] = useState<number>(initialValue)

    const calculateValue = (x: number): number => {
        if(!sliderEl.current) return 0
        const { x: sliderX, width: sliderWidth }: DOMRect = sliderEl.current.getBoundingClientRect()
        const value = (x - sliderX) / sliderWidth

        return Math.max(0, Math.min(value, 1))
    }

    const handleDrag = (e: React.MouseEvent<HTMLDivElement>) => {
        e.preventDefault()
        console.log(e.clientX)
        if (e.clientX === 0) {
            return
        }
        
        setValue(calculateValue(e.clientX))
    }

    const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
        const value = calculateValue(e.clientX)

        if(value) {
            setValue(value)
            onChange(value)
        }
    }

    const offsetLeft = `calc(${value * 100}% - 0.5rem)`

    return <div className="relative h-4" onMouseDown={handleClick} ref={sliderEl}>
        <div className="bg-primary-5 rounded-full h-1 w-full top-1.5 absolute"/>
        <div className="bg-primary-500 rounded-l-full h-1 top-1.5 absolute" style={{width: `${value * 100}%`}}/>
        <div className="rounded-full bg-primary-700 w-4 h-4 absolute top-0" style={{left: offsetLeft}}/>
        <div className="rounded-full bg-primary-500 w-4 h-4 absolute top-0 cursor-pointer"
            draggable={true}
            style={{left: offsetLeft, opacity: dragging ? 0 : 1}}
            onDragStart={(e) => { setDragging(true) }}
            onDrag={handleDrag}
            onDragEnd={(e) => {
                setDragging(false)
                onChange && onChange(value)
            }}
    />
    </div>
}

Thank you!

0 Answers
Related