React使用react-tinder-card手动滑动卡片后state数组未更新问题
问题核心原因
- 普通变量
buttonClicked未使用响应式状态管理,组件每次重渲染都会被重置为初始值not clicked,导致手动滑动的判断逻辑异常 - 状态更新时直接引用当前闭包中的旧状态值,多次滑动时会读取到过时的状态快照,导致更新结果不符合预期
- React的useState更新是异步行为,调用setter后直接打印的是更新前的旧状态,并非状态更新失败
- 卡片列表的key使用数组下标,删除元素后下标重排,导致组件复用逻辑错乱
- 手动滑动触发的
swiped函数和按钮触发的swipe函数状态更新逻辑不对齐
修复代码
import React, { useState, useEffect, useMemo } from 'react'; import './IslamCards.css'; import Cards from 'react-tinder-card'; import database from './firebase'; import hate from "./Cross.png" import replayb from "./Replay.png" import love from "./Love.png" import IconButton from "@material-ui/core/IconButton" function IslamCards(props) { const [people, setPeople] = useState([]) useEffect(() => { database.collection("People").onSnapshot(snapshot => { setPeople(snapshot.docs.map(doc => doc.data())) }) }, []) const [alreadyRemoved, setAlreadyRemoved] = useState([]) // 改为state管理,避免重渲染重置 const [buttonClicked, setButtonClicked] = useState("not clicked") const childRefs = useMemo(() => Array(people.length).fill(0).map(() => React.createRef()), [people.length]) const swiped = () => { if(buttonClicked!=="clicked"){ // 所有状态更新使用函数式写法,获取最新状态 setPeople(prevPeople => { setAlreadyRemoved(prevRemoved => { const cardsLeft = prevPeople.filter(person => !prevRemoved.includes(person)) if (cardsLeft.length) { const toBeRemoved = cardsLeft[cardsLeft.length - 1] return [...prevRemoved, toBeRemoved] } return prevRemoved }) const cardsLeft = prevPeople.filter(person => !alreadyRemoved.includes(person)) if (cardsLeft.length) { const toBeRemoved = cardsLeft[cardsLeft.length - 1] const index = prevPeople.map(person => person.name).indexOf(toBeRemoved.name) return prevPeople.filter((_, personIndex) => personIndex !== index) } return prevPeople }) } setButtonClicked("not clicked") } const swipe = (dir) => { setButtonClicked("clicked") setPeople(prevPeople => { setAlreadyRemoved(prevRemoved => { const cardsLeft = prevPeople.filter(person => !prevRemoved.includes(person)) if (cardsLeft.length) { const toBeRemoved = cardsLeft[cardsLeft.length - 1] const index = prevPeople.map(person => person.name).indexOf(toBeRemoved.name) childRefs[index].current.swipe(dir) setTimeout(() => { setPeople(innerPrev => innerPrev.filter((_, personIndex) => personIndex !== index)) }, 1000) return [...prevRemoved, toBeRemoved] } return prevRemoved }) return prevPeople }) } const replay = () => { setAlreadyRemoved(prevRemoved => { if (prevRemoved.length) { const toBeReset = prevRemoved[prevRemoved.length - 1] setPeople(prevPeople => [...prevPeople, toBeReset]) return prevRemoved.slice(0, -1) } return prevRemoved }) } return ( <div> <div className="cardContainer"> {/* key改用唯一字段name,避免下标导致的复用异常 */} {people.map((person, index) => { return ( <Cards ref={childRefs[index]} onSwipe={swiped} className="swipe" key={person.name} preventSwipe={['up', 'down']} > <div style={{ backgroundImage: `url(${person.url})` }} className="Cards"> <h3>{person.name}</h3> </div> </Cards>); })} </div> <div className="reactionButtons"> <IconButton onClick={() => swipe('left')}> <img id="hateButton" alt="d" src={hate} style={{ width: "10vh", marginBottom: "5vh", pointerEvents: "all" }} /> </IconButton> <IconButton onClick={() => replay()}> <img id="replayButton" alt="e" src={replayb} style={{ width: "11vh", marginBottom: "5vh", pointerEvents: "all" }} /> </IconButton> <IconButton onClick={() => swipe('right')}> <img id="loveButton" alt="f" src={love} style={{ width: "11vh", marginBottom: "5vh", pointerEvents: "all" }} /> </IconButton> </div> </div> ); } export default IslamCards;
内容的提问来源于stack exchange,提问作者Anas HAMOUTNI
相关产品推荐
相关产品推荐

