React项目删除功能总是删除最后一条数据,如何获取正确绑定的post_id?
问题根源
你所有卡片共用了同一套popover相关的状态和引用:
show、target、ref三个值都定义在ListPosts组件顶层,遍历posts渲染卡片时,所有卡片都读写这三个值- 遍历过程中每次都会覆盖
ref的指向,最终ref.current只会保留最后一张卡片的DOM元素 - 触发popover显示时,所有卡片的Overlay都会响应同一个
show状态,最终拿到的post对象自然是最后一次遍历的结果,所以ID永远是最后一条的ID
解决方案
方案1(推荐):抽离独立的PostCard子组件
把单张卡片的渲染逻辑、popover状态、删除逻辑都放到子组件里,每张卡片自己维护自己的状态,完全不会互相干扰:
// 单独抽离的子组件 const PostCard = ({ post, onDelete }) => { const [show, setShow] = useState(false); const [target, setTarget] = useState(null); const ref = useRef(null); const handleClick = (event) => { setShow(!show); setTarget(event.target); }; const handleDelete = async () => { try { await fetch(`http://localhost:3000/posts/${post.post_id}`, { method: "DELETE" }); onDelete(post.post_id); } catch (err) { console.error(err.message); } }; return ( <Card className="post-card" style={{ marginTop: "15px" }}> <Card.Body> <div className="post-container"> <div className="post-header row"> <div className="user-photo-icon col-1"></div> <div className="user-names col-9"></div> <div className="options-menu col-2 "> <div ref={ref}> <Button className="options-btn-popover" onClick={handleClick}> <FontAwesomeIcon icon={ faEllipsisH } color="#848484" size="1x" className="options-icon" /> </Button> {post.post_id} <Overlay show={show} target={target} placement="left" container={ref.current} > <Popover className="shadow-sm" id="popover-contained" > {post.post_id} <Popover.Content> <div className="mb-2"> <Button className="options-btn-popover"> <FontAwesomeIcon icon={ faPencilAlt } size="1x" className="post-options-icon"/> 编辑帖子 </Button> </div> <div> <Button className="options-btn-popover" onClick={handleDelete}> <FontAwesomeIcon icon={ faTrashAlt } color="" size="1x" className="post-options-icon" /> 删除帖子 </Button> </div> </Popover.Content> </Popover> </Overlay> </div> </div> </div> <div className="post-text">{post.post_body}</div> <div className="post-media"></div> <div className="post-actions"></div> <div className="post-comments"> <div className="post-subcomments"></div> </div> </div> </Card.Body> </Card> ) } // 原ListPosts组件简化后 const ListPosts = (props) => { const [posts, setPosts] = useState([]); const deletePost = (id) => { // 用函数式更新避免依赖旧posts数组 setPosts(prev => prev.filter(post => post.post_id !== id)); } const getPosts = async() => { try { const response = await fetch("http://localhost:3000/posts") const jsonData = await response.json() setPosts(jsonData); } catch (err) { console.error(err.message) } }; useEffect(() => { getPosts(); }, []); return ( <Fragment> {posts.map(post => ( <PostCard key={post.post_id} post={post} onDelete={deletePost} /> ))} </Fragment> ) };
方案2(不抽组件临时修复):把激活状态绑定到帖子ID
修改状态为存储当前激活的帖子ID,同时给每个卡片分配独立的ref:
// 把show状态改成存激活的post id const [activePostId, setActivePostId] = useState(null); const [target, setTarget] = useState(null); // 用对象存储每个卡片的ref const popoverRefs = useRef({}); const handleClick = (event, postId) => { setActivePostId(activePostId === postId ? null : postId); setTarget(event.target); }
遍历的时候给每个ref赋值,同时判断Overlay的显示条件:
<div ref={el => popoverRefs.current[post.post_id] = el}> <Button className="options-btn-popover" onClick={(e) => handleClick(e, post.post_id)}> <FontAwesomeIcon icon={ faEllipsisH } color="#848484" size="1x" className="options-icon" /> </Button> {post.post_id} <Overlay show={activePostId === post.post_id} target={target} placement="left" container={popoverRefs.current[post.post_id]} > {/* 剩余popover内容不变 */} </Overlay> </div>
额外优化点
原deletePost方法中更新posts状态时用函数式写法,避免依赖闭包中缓存的旧posts数组,删除后更新列表更稳定。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

