英文:
My React (not strict mode) function executes twice in swipe gesture
问题
我在React应用程序中的列表项组件上有某种滑动功能。滑动功能有效,但是右滑函数似乎执行了两次。我没有使用React StrictMode,所以这不是问题。
以下是我的列表项组件简化版:
<Card
isPressable={!finished}
onClick={() => {
handleClick(false);
}}
onTouchStart={(e) => {
setTouchEnd(null);
setTouchStart(e.targetTouches[0].clientX);
}}
onTouchMove={(e) => {
setTouchEnd(e.targetTouches[0].clientX);
}}
onTouchEnd={() => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if(isLeftSwipe && finished) {
handleLeftSwipe();
}
else if(isRightSwipe && !finished) {
handleRightSwipe();
}
}}
onContextMenu={() => handleClick(true)}
/>
minSwipeDistance 是一个常量:const minSwipeDistance = 50;
当我向右滑动时,onTouchEnd 也会执行两次。handleSwipeRight 函数本身不需要调试,因为我仅将其替换为一个控制台日志,它仍然会执行两次。
在我的 useEffects
中,我没有做任何特殊处理。
在未完成的情况下,列表项不应该能向左滑动,反之亦然。
完整的列表项组件如下:
// 代码略...
希望这可以帮到您找到问题所在。如果您有任何其他疑问,请随时告诉我。
英文:
I have some kind of a swipe functionality on list items in my React application. The swipe functionality is working, but somehow the right swipe function gets executed twice. I am not using React StrictMode, so that isn't the problem.
Here is my list item component simplified:
<Card
isPressable={!finished}
onClick={() => {
handleClick(false);
}}
onTouchStart={(e) => {
setTouchEnd(null);
setTouchStart(e.targetTouches[0].clientX);
}}
onTouchMove={(e) => {
setTouchEnd(e.targetTouches[0].clientX);
}}
onTouchEnd={() => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if(isLeftSwipe && finished) {
handleLeftSwipe();
}
else if(isRightSwipe && !finished) {
handleRightSwipe();
}
}}
onContextMenu={() => handleClick(true)}
minSwipeDistance is a const: const minSwipeDistance = 50;
The onTouchEnd also executes twice when I swipe right. The handleSwipeRight function itself doesn't need to be debugged, because I literally exchanged it for only a console log and it was still being executed twice.
For the rest I am not doing anything special in my useEffects
.
A listitem should not be able to be swiped left when the item is not finished and vice versa for finished items with right swipe.
My whole list Item component:
(swipedRight state is only for CSS purposes)
import { Avatar, Card, Modal, Row, Text } from "@nextui-org/react";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { finishOrderRule, showToast, getOrderRule } from "../../utils/api.js";
import { useAuth } from "../../App.js";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faInfo } from "@fortawesome/free-solid-svg-icons";
export default function OrderRule(props){
const [amountOfFields, setAmountOfFields] = useState(props.datafields?.length);
const [finished, setFinished] = useState(props.data?.Finished);
const [swipedLeft, setSwipedLeft] = useState(false);
const [swipedRight, setSwipedRight] = useState(false);
const [isOpen, setIsOpen] = useState(false);
const [touchStart, setTouchStart] = useState(null);
const [touchEnd, setTouchEnd] = useState(null);
const [selected, setSelected] = useState(props.selected === props.data?.Oid);
const order = props.order;
const isAllRules = props.isAllRules;
const rule = props.data;
const datafields = props.datafields;
const navigate = useNavigate();
const { token } = useAuth();
const minSwipeDistance = 50;
const noteField = {
name: "Note",
type: "Regels",
label: "Notitie"
}
useEffect(() => {
setSelected(props.selected === rule?.Oid);
}, [props.selected]);
useEffect(() => {
setAmountOfFields(props.datafields.length);
}, [props.datafields]);
function getParts(fieldname, isOrderProperty){
const parts = fieldname.split(".");
if(parts.length === 1) return isOrderProperty ? order?.[fieldname] : rule?.[fieldname];
else if (parts.length === 2) return isOrderProperty ? order?.[parts[0]]?.[parts[1]] : rule?.[parts[0]]?.[parts[1]];
}
function handleClick(long){
if (long) navigate(`/orders/${order?.Oid}/${rule?.Oid}`, { state: rule, replace: false });
else props.setSelectedRule(rule?.Oid);
}
function handleIconClick(e){
e.stopPropagation();
setIsOpen(true);
}
function handleRightSwipe(){
console.log("right swipe");
setSwipedRight(true);
setFinished(true);
finishOrderRule(rule?.Oid, true, token)
.catch(error => {
setFinished(false);
if(error.statusCode === 401) {
showToast("Je moet opnieuw inloggen of je hebt geen rechten om deze actie uit te voeren.", error.statusCode);
navigate("/login");
}
else {
showToast(error.message, error.statusCode)
console.log(error);
}
})
.finally(() => {
getOrderRule(rule?.Oid, token)
.then((data) => {
props.modifyRule(data.value[0]);
})
.catch(error => {
setFinished(true);
if(error.statusCode === 401) {
showToast("Je moet opnieuw inloggen of je hebt geen rechten om deze actie uit te voeren.", error.statusCode);
navigate("/login");
}
else {
showToast(error.message, error.statusCode)
console.log(error);
}
})
.finally(() => {
setSwipedRight(false);
props.setSelectedRule(null);
});
});
}
function handleLeftSwipe(){
console.log("left swipe");
setSwipedLeft(true);
setFinished(false);
finishOrderRule(rule?.Oid, false, token)
.catch(error => {
setFinished(true);
if(error.statusCode === 401) {
showToast("Je moet opnieuw inloggen of je hebt geen rechten om deze actie uit te voeren.", error.statusCode);
navigate("/login");
}
else {
showToast(error.message, error.statusCode)
console.log(error);
}
})
.finally(() => {
setSwipedLeft(false);
getOrderRule(rule?.Oid, token)
.then((data) => {
props.modifyRule(data.value[0]);
})
.catch(error => {
setFinished(true);
if(error.statusCode === 401) {
showToast("Je moet opnieuw inloggen of je hebt geen rechten om deze actie uit te voeren.", error.statusCode);
navigate("/login");
}
else {
showToast(error.message, error.statusCode)
console.log(error);
}
})
.finally(() => {});
});
}
function isDate(fieldname){ return fieldname.toLowerCase().includes("date") }
const ModalComponent = () => {
return (
<Modal css={{zIndex: 10, m: 10}} closeButton open={isOpen} onClose={() => { setIsOpen(false); }}>
<Modal.Header css={{p: 0}}><Text color="primary" size={26}>{rule?.Product?.Name}</Text></Modal.Header>
<Modal.Body css={{p: 20, pt: 0}}>
<Text weight="medium" size={15} css={{textAlign: "center"}}>{rule?.Product?.Description}</Text>
<Text weight="medium" size={15} css={{textAlign: "center"}}>{rule?.Note}</Text>
</Modal.Body>
</Modal>
);
}
const NoteField = ({ width }) => {
return (
<div style={{pointerEvents: "none", width: width, display: "flex", justifyContent: "center", alignContent: "center"}}>
{(rule?.Note !== null &&
<Avatar onClick={(e) => handleIconClick(e)} color={"white"} size="sm" css={{display: "flex", pointerEvents: "auto", border: "2px solid black", alignContent: "center", justifyContent: "center", justifyItems: "center", alignItems: "center"}} bordered icon={<FontAwesomeIcon icon={faInfo} />} />) || (
<Avatar color={"white"} size="sm" css={{border: "2px solid black", opacity: "10%"}} bordered icon={<FontAwesomeIcon icon={faInfo} />} />)}
</div>
);
}
const Field = ({ field }) => {
const obj = field.type === "Orders" ? getParts(field.name, true) : getParts(field.name, false);
const wi = ((100 / amountOfFields).toString() + "%").toString();
return field.name === "Note" ? <NoteField width={wi} /> : <Text weight="medium" size={15} css={{pointerEvents: "none", width: wi, textAlign: "center", lineHeight: "100%"}}>{isDate(field.name) ? new Date(obj).toLocaleDateString().toString() : typeof(obj) === "boolean" ? (obj === true ? "Ja" : "Nee") : obj === "" ? "-" : obj === null ? "-" : obj}</Text>;
}
return (
<Card
isPressable={!finished}
onClick={() => { handleClick(false); }}
onTouchStart={(e) => {
setTouchEnd(null);
setTouchStart(e.targetTouches[0].clientX);
}}
onTouchMove={(e) => {
setTouchEnd(e.targetTouches[0].clientX);
}}
onTouchEnd={() => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if(isLeftSwipe && finished) {
handleLeftSwipe();
}
if(isRightSwipe && !finished) {
handleRightSwipe();
}
}}
onContextMenu={() => handleClick(true)}
css={{p: "0px 10px 0px 10px", w: 'auto', m: "6.5px", h: "55px", justifyContent: "center"}}
className={
finished && swipedRight ? "listItem swipedRight finished" :
finished && swipedLeft ? "listItem swipedLeft finished" :
selected && swipedRight ? "listItem selected swipedRight" :
selected ? "listItem selected" :
swipedLeft ? "listItem swipedLeft" :
swipedRight ? "listItem swipedRight" :
finished ? "listItem finished" :
"listItem"
}
>
<ModalComponent key="modal" />
{isAllRules && <div style={selected ? {position: 'absolute', left: 2, top: -4.5} : {position: 'absolute', left: 5, top: -2.5}}><Text size={13} color="primary">{`Order ${order?.Number}`}</Text></div>}
<Row justify="space-evenly" css={{alignItems: "center"}}>
{datafields.map((field) => (
<Field key={field.name} field={field} />
))}
<Field key="Notitie" field={noteField} />
</Row>
</Card>
);
}
答案1
得分: 0
我从Next.UI切换到Mantine.dev的UI库,不知怎么地,问题就解决了。
我觉得这可能与Card组件的渲染有关。
英文:
I switched UI-libraries from Next.UI to Mantine.dev and somehow that fixed this issue.
I think it had something to do with the rendering of the Card component.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论