I have a list of items that the user can select from. If the user clicks on an item the item is selected. If the user long presses on any item it activates a multi-select mode, where the user can select multiple items, I'm using a custom useLongPress hook from this question. However, I would like to let the user swipe to scroll through the list without selecting any item, I'm using react-swipeable for swipe events, but I don't know how I can cancel the onClick events if the user is swiping, I'm still selecting items even if I'm swiping.
https://codesandbox.io/s/hungry-kilby-vlxmc?file=/src/App.js
App.js
import "./styles.css";
import React, { useState } from "react";
import useLongPress from "./useLongPress";
import { useSwipeable } from "react-swipeable";
const list = [
"apple",
"banana",
"orange",
"mango",
"kiwi",
"lemon",
"watermelon",
"avocado",
"grapes",
"strawberry",
"passion fruit",
"papaya",
"pomegranate",
"blueberry",
"pear",
"pineapple",
"jackfruit"
];
export default function App() {
const [MultiSelect, setMultiSelect] = useState(false);
const [MultiSelectArray, setMultiSelectArray] = useState([]);
const [IsSwiping, setIsSwiping] = useState(false);
const swipe = useSwipeable({
onSwipeStart: (e) => {
setIsSwiping(true);
console.log("swiping", e);
},
onSwiped: (e) => {
setTimeout(setIsSwiping(false), 5000);
console.log("swiped", e);
}
});
/* onClick and LongPress setup */
const onLongPress = (e) => {
setMultiSelect(true);
setMultiSelectArray((oldArray) => [...oldArray, e.target.innerHTML]);
};
const onClick = (e) => {
if (!IsSwiping) {
if (MultiSelect) {
if (MultiSelectArray.includes(e.target.innerHTML)) {
//if element is already selected, deselects it
const newArray = MultiSelectArray.filter(
(element) => element !== e.target.innerHTML
);
setMultiSelectArray(newArray);
} else {
setMultiSelectArray((oldArray) => [...oldArray, e.target.innerHTML]);
}
} else {
console.log("selected", e.target.innerHTML);
}
}
};
const defaultOptions = {
shouldPreventDefault: true,
delay: 500
};
const longPressEvent = useLongPress(onLongPress, onClick, defaultOptions);
/* onClick and LongPress setup */
return (
<>
<ul className="add-list" {...swipe}>
{list.map((fruit, index) => {
return (
<li
{...longPressEvent}
className={
MultiSelectArray.includes(fruit) ? "selected-exercise" : ""
}
>
{fruit}
</li>
);
})}
</ul>
{MultiSelect && (
<div className="multiselect-buttons">
<span
className={"cancel "}
onClick={(e) => {
setMultiSelectArray([]);
setMultiSelect(false);
}}
>
X
</span>
<span
className={"check"}
onClick={(e) => {
console.log("multi-select", MultiSelectArray);
}}
>
OK
</span>
</div>
)}
</>
);
}
useLongPress.js
import { useCallback, useRef, useState } from "react";
const useLongPress = (
onLongPress,
onClick,
{ shouldPreventDefault = true, delay = 300 } = {}
) => {
const [longPressTriggered, setLongPressTriggered] = useState(false);
const timeout = useRef();
const target = useRef();
const start = useCallback(
(event) => {
if (shouldPreventDefault && event.target) {
event.target.addEventListener("touchend", preventDefault, {
passive: false
});
target.current = event.target;
}
timeout.current = setTimeout(() => {
onLongPress(event);
setLongPressTriggered(true);
}, delay);
},
[onLongPress, delay, shouldPreventDefault]
);
const clear = useCallback(
(event, shouldTriggerClick = true) => {
timeout.current && clearTimeout(timeout.current);
shouldTriggerClick && !longPressTriggered && onClick(event);
setLongPressTriggered(false);
if (shouldPreventDefault && target.current) {
target.current.removeEventListener("touchend", preventDefault);
}
},
[shouldPreventDefault, onClick, longPressTriggered]
);
return {
onMouseDown: (e) => start(e),
onTouchStart: (e) => start(e),
onMouseUp: (e) => clear(e),
onMouseLeave: (e) => clear(e, false),
onTouchEnd: (e) => clear(e)
};
};
const isTouchEvent = (event) => {
return "touches" in event;
};
const preventDefault = (event) => {
if (!isTouchEvent(event)) return;
if (event.touches.length < 2 && event.cancelable) {
event.preventDefault();
}
};
export default useLongPress;
setState
is asynchronous as well as it triggers a re-render. You don't want to do that during an interaction like swipe
.
Try using a ref using useRef
const isSwiping = useRef(false);
and use it like
onst onClick = (e) => {
if (!isSwiping.current) {
You can see that useLongPress.js
is also using useRef
for the same reason.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With