The onClick event.detail contains the number of clicks: double-click has event.detail = 2
.
When the clicked React component is replaced, the event.detail does not reset. I suspect this is because React has optimized this & kept the same div in the page, so the browser has not reset the event.detail counter.
How can I force this event.detail counter to reset when the component is changed? I want to be able to double-click items in the list multiple times while navigating.
Issue reproduction: https://codesandbox.io/p/sandbox/react-on-click-event-detail-6ndl5v?file=%2Fsrc%2FApp.tsx%3A44%2C12
App.tsx:
const ListItem: React.FC<{
content: string;
onClick: (event: React.MouseEvent<HTMLDivElement>) => void;
}> = ({ content, onClick }) => {
return (
<div
onClick={onClick}
style={{ background: "rgba(0,0,0,0.5)", userSelect: "none" }}
>
{content}
</div>
);
};
const ListRenderer: React.FC = () => {
// n tracks the number of double clicks
const [n, setN] = useState(0);
// items simulates an updating list of items when double-clicking
const items = useMemo(
() => Array.from({ length: 10 }, (_, i) => `${n}: item ${i}`),
[n]
);
const handleClick = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
console.log(`Click: event.detail: ${event.detail}`);
if (event.detail === 2) {
// Double-clicked an item.
setN((prev) => prev + 1);
}
}, []);
return (
<>
<div>
{items.map((item, index) => (
<ListItem
key={`items-${n}-${index}`}
content={item}
onClick={handleClick}
/>
))}
</div>
</>
);
};
After some experimenting, I've written this React Hook to detect when the event.detail has not reset correctly after a component is re-mounted and to correct the click count accordingly:
import { useCallback, useRef } from 'react'
// MouseEvent and other events satisfy this.
interface DetailCountEvent {
// detail is the number of times the event occurred.
detail: number
}
// useDetailCountHandler builds an event handler which correctly resets the
// event.detail counter when the component is re-mounted.
//
// The onClick event.detail contains the number of clicks: double-click has
// event.detail = 2. When the clicked React component is replaced, the
// event.detail does not reset.
//
// Question: https://stackoverflow.com/q/77719428/431369
// Issue: https://codesandbox.io/p/sandbox/react-on-click-event-detail-6ndl5v?file=%2Fsrc%2FApp.tsx%3A8%2C23
// Fix: https://codesandbox.io/p/sandbox/react-on-click-event-detail-possible-fix-4zwk7d?file=%2Fsrc%2FApp.tsx%3A59%2C1
export function useDetailCountHandler<E extends DetailCountEvent>(
cb: (e: E, count: number) => void,
) {
const stateRef = useRef({ prev: 0, sub: 0 })
return useCallback(
(e: E) => {
const state = stateRef.current
let count = e.detail
if (state.sub >= count) {
state.sub = 0
}
if (state.prev < count - 1) {
state.sub = count - state.prev - 1
}
count -= state.sub
state.prev = count
cb(e, count)
},
[stateRef, cb],
)
}
Usage:
const handleClick: MouseEventHandler<HTMLDivElement> =
useDetailCountHandler(
useCallback(
(e, count) => {
console.log('onClick', e.detail, count);
if (count === 2) {
// double click
}
},
[],
),
);
return <div onClick={handleClick} />;
Using a key
to force React to re-create the div unfortunately doesn't reset the event.detail in onClick.
The above hook is somewhat of a hack but works reliably. If anyone else has a less hacky way to reset event.detail when the component is changed, I would love to know.