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:
- Is this still the same bug mentioned in some of the mentioned sources?
- Why is this specifically happening in Typescript, not in Javascript?
- 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!