Use useCallback.
This commit is contained in:
+100
-119
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import type { Dispatch, SetStateAction, RefObject } from "react";
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { minmax } from "../util";
|
||||
import type { CartesianSpace } from "../types";
|
||||
import { useScroll } from "./scroll";
|
||||
@@ -26,6 +26,20 @@ function chooseValueByDirection(
|
||||
return direction === Direction.HORIZONTAL ? xValue : yValue;
|
||||
}
|
||||
|
||||
function extractEventCoordinate(
|
||||
event: MouseEvent | TouchEvent,
|
||||
direction: Direction,
|
||||
): number {
|
||||
if (isTouchEvent(event)) {
|
||||
return chooseValueByDirection(
|
||||
direction,
|
||||
event.touches[0].clientX,
|
||||
event.touches[0].clientY,
|
||||
);
|
||||
}
|
||||
return chooseValueByDirection(direction, event.clientX, event.clientY);
|
||||
}
|
||||
|
||||
export function useSlider({
|
||||
direction,
|
||||
origin,
|
||||
@@ -36,21 +50,10 @@ export function useSlider({
|
||||
origin: CartesianSpace;
|
||||
dimensions: CartesianSpace;
|
||||
setPosition: Dispatch<SetStateAction<number>>;
|
||||
}): { sliderRef: RefObject<HTMLDivElement | null>; isDragging: boolean } {
|
||||
}) {
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const sliderRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Construct event handler refs
|
||||
// Prevents unnecessary function recreation
|
||||
const calculatePositionRef = useRef((_: MouseEvent | TouchEvent) => {});
|
||||
const startSliderInteractionRef = useRef((_: MouseEvent | TouchEvent) => {});
|
||||
const processSliderInteractionRef = useRef(
|
||||
(_: MouseEvent | TouchEvent) => {},
|
||||
);
|
||||
const endSliderInteractionRef = useRef((_: MouseEvent | TouchEvent) => {});
|
||||
|
||||
// Store dependencies as refs
|
||||
// Always use latest values
|
||||
const directionRef = useRef(direction);
|
||||
const originRef = useRef(origin);
|
||||
const dimensionsRef = useRef(dimensions);
|
||||
@@ -61,147 +64,125 @@ export function useSlider({
|
||||
dimensionsRef.current = dimensions;
|
||||
}, [direction, origin, dimensions]);
|
||||
|
||||
// Setup scroll handlers
|
||||
const handleScrollUp = () => {
|
||||
const dir = directionRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
|
||||
setPosition((prev: number) =>
|
||||
minmax(prev - 1, 0, chooseValueByDirection(dir, dims.x, dims.y)),
|
||||
);
|
||||
};
|
||||
|
||||
const handleScrollDown = () => {
|
||||
const dir = directionRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
|
||||
setPosition((prev: number) =>
|
||||
minmax(prev + 1, 0, chooseValueByDirection(dir, dims.x, dims.y)),
|
||||
);
|
||||
};
|
||||
|
||||
// Initialize scroll handling
|
||||
const { addScrollListener, removeScrollListener } = useScroll({
|
||||
targetRef: sliderRef,
|
||||
onScrollUp: handleScrollUp,
|
||||
onScrollDown: handleScrollDown,
|
||||
});
|
||||
|
||||
// Update handler functions when dependencies change via reference
|
||||
useEffect(() => {
|
||||
calculatePositionRef.current = (event: MouseEvent | TouchEvent) => {
|
||||
// Setup drag handlers
|
||||
const calculatePosition = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const dir = directionRef.current;
|
||||
const orig = originRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
|
||||
const clientCoord = isTouchEvent(event)
|
||||
? chooseValueByDirection(
|
||||
dir,
|
||||
event.touches[0].clientX,
|
||||
event.touches[0].clientY,
|
||||
)
|
||||
: chooseValueByDirection(dir, event.clientX, event.clientY);
|
||||
const clientCoord = extractEventCoordinate(event, dir);
|
||||
const positionValue = minmax(
|
||||
clientCoord - chooseValueByDirection(dir, orig.x, orig.y),
|
||||
0,
|
||||
chooseValueByDirection(dir, dims.x, dims.y),
|
||||
);
|
||||
setPosition(positionValue);
|
||||
};
|
||||
},
|
||||
[setPosition],
|
||||
);
|
||||
|
||||
startSliderInteractionRef.current = (event: MouseEvent | TouchEvent) => {
|
||||
const processSliderInteraction = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
event.preventDefault();
|
||||
calculatePositionRef.current(event);
|
||||
calculatePosition(event);
|
||||
},
|
||||
[calculatePosition],
|
||||
);
|
||||
|
||||
const endSliderInteraction = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
setIsDragging(false);
|
||||
if (!isTouchEvent(event)) {
|
||||
document.removeEventListener("mousemove", processSliderInteraction);
|
||||
document.removeEventListener("mouseup", endSliderInteraction);
|
||||
} else {
|
||||
document.removeEventListener("touchmove", processSliderInteraction);
|
||||
document.removeEventListener("touchend", endSliderInteraction);
|
||||
document.removeEventListener("touchcancel", endSliderInteraction);
|
||||
}
|
||||
},
|
||||
[processSliderInteraction],
|
||||
);
|
||||
|
||||
const startSliderInteraction = useCallback(
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
event.preventDefault();
|
||||
calculatePosition(event);
|
||||
setIsDragging(true);
|
||||
|
||||
if (!isTouchEvent(event)) {
|
||||
document.addEventListener(
|
||||
"mousemove",
|
||||
processSliderInteractionRef.current,
|
||||
);
|
||||
document.addEventListener("mouseup", endSliderInteractionRef.current, {
|
||||
document.addEventListener("mousemove", processSliderInteraction);
|
||||
document.addEventListener("mouseup", endSliderInteraction, {
|
||||
passive: true,
|
||||
});
|
||||
} else {
|
||||
document.addEventListener(
|
||||
"touchmove",
|
||||
processSliderInteractionRef.current,
|
||||
);
|
||||
document.addEventListener("touchend", endSliderInteractionRef.current, {
|
||||
document.addEventListener("touchmove", processSliderInteraction);
|
||||
document.addEventListener("touchend", endSliderInteraction, {
|
||||
passive: true,
|
||||
});
|
||||
document.addEventListener("touchcancel", endSliderInteraction, {
|
||||
passive: true,
|
||||
});
|
||||
document.addEventListener(
|
||||
"touchcancel",
|
||||
endSliderInteractionRef.current,
|
||||
{
|
||||
passive: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
[calculatePosition, processSliderInteraction, endSliderInteraction],
|
||||
);
|
||||
|
||||
processSliderInteractionRef.current = (event: MouseEvent | TouchEvent) => {
|
||||
event.preventDefault();
|
||||
calculatePositionRef.current(event);
|
||||
};
|
||||
// Setup scroll handlers
|
||||
const handleScrollUp = useCallback(() => {
|
||||
const dir = directionRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
|
||||
endSliderInteractionRef.current = (event: MouseEvent | TouchEvent) => {
|
||||
setIsDragging(false);
|
||||
if (!isTouchEvent(event)) {
|
||||
document.removeEventListener(
|
||||
"mousemove",
|
||||
processSliderInteractionRef.current,
|
||||
);
|
||||
document.removeEventListener(
|
||||
"mouseup",
|
||||
endSliderInteractionRef.current,
|
||||
);
|
||||
} else {
|
||||
document.removeEventListener(
|
||||
"touchmove",
|
||||
processSliderInteractionRef.current,
|
||||
);
|
||||
document.removeEventListener(
|
||||
"touchend",
|
||||
endSliderInteractionRef.current,
|
||||
);
|
||||
document.removeEventListener(
|
||||
"touchcancel",
|
||||
endSliderInteractionRef.current,
|
||||
);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
setPosition((prev: number) =>
|
||||
minmax(prev - 1, 0, chooseValueByDirection(dir, dims.x, dims.y)),
|
||||
);
|
||||
}, [setPosition]);
|
||||
|
||||
const handleScrollDown = useCallback(() => {
|
||||
const dir = directionRef.current;
|
||||
const dims = dimensionsRef.current;
|
||||
|
||||
setPosition((prev: number) =>
|
||||
minmax(prev + 1, 0, chooseValueByDirection(dir, dims.x, dims.y)),
|
||||
);
|
||||
}, [setPosition]);
|
||||
|
||||
const { addScrollListener, removeScrollListener } = useScroll({
|
||||
targetRef: sliderRef,
|
||||
onScrollUp: handleScrollUp,
|
||||
onScrollDown: handleScrollDown,
|
||||
});
|
||||
|
||||
// Set up entry listeners
|
||||
useEffect(() => {
|
||||
const currentRef = sliderRef.current;
|
||||
if (currentRef) {
|
||||
addScrollListener();
|
||||
currentRef.addEventListener(
|
||||
"mousedown",
|
||||
startSliderInteractionRef.current,
|
||||
);
|
||||
currentRef.addEventListener(
|
||||
"touchstart",
|
||||
startSliderInteractionRef.current,
|
||||
);
|
||||
currentRef.addEventListener("mousedown", startSliderInteraction);
|
||||
currentRef.addEventListener("touchstart", startSliderInteraction);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (currentRef) {
|
||||
removeScrollListener();
|
||||
currentRef.removeEventListener(
|
||||
"mousedown",
|
||||
startSliderInteractionRef.current,
|
||||
);
|
||||
currentRef.removeEventListener(
|
||||
"touchstart",
|
||||
startSliderInteractionRef.current,
|
||||
);
|
||||
currentRef.removeEventListener("mousedown", startSliderInteraction);
|
||||
currentRef.removeEventListener("touchstart", startSliderInteraction);
|
||||
}
|
||||
|
||||
document.removeEventListener("mousemove", processSliderInteraction);
|
||||
document.removeEventListener("mouseup", endSliderInteraction);
|
||||
document.removeEventListener("touchmove", processSliderInteraction);
|
||||
document.removeEventListener("touchend", endSliderInteraction);
|
||||
document.removeEventListener("touchcancel", endSliderInteraction);
|
||||
};
|
||||
}, []);
|
||||
}, [
|
||||
addScrollListener,
|
||||
removeScrollListener,
|
||||
startSliderInteraction,
|
||||
processSliderInteraction,
|
||||
endSliderInteraction,
|
||||
]);
|
||||
|
||||
return { sliderRef, isDragging };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user