React子组件调用父组件closeCard函数状态不更新求助
卡片关闭按钮点击后状态变更不生效的问题解决
我有一个包含多张卡片的父组件LayoutGrid,父组件与子组件代码在同一个tsx文件中。实现了两种关闭选中卡片的方式:
- 点击父组件网格区域:正常工作
- 点击选中卡片上的关闭按钮:控制台显示closeCard函数已调用,但界面状态未更新
LayoutGrid.tsx
export const LayoutGrid = ({ className, cards }: { className?: string, cards: LayoutCardProps[] }) => { const [selected, setSelected] = useState<LayoutCardProps | null>(null); const [lastSelected, setLastSelected] = useState<LayoutCardProps | null>(null); const [blockScroll, allowScroll] = useScrollBlock(); const handleClick = (card: LayoutCardProps) => { setLastSelected(selected); setSelected(card); }; const closeCard = () => { setLastSelected(selected); setSelected(null); console.log("yes?") }; useEffect(() => { if (selected) { handleSmoothScroll('layout-grid'); blockScroll(); } else { allowScroll(); } }, [selected]) return (<> {/* {selected&& <div className="h-[100vh] w-[100vw] fixed inset-0 bg-slate-400 z-40"/> } */} <div id="layout-grid" className={cn('w-full h-full p-2 sm:p-5 md:p-10 grid grid-cols-1 md:grid-cols-3 max-w-7xl mx-auto gap-4 relative', className)}> {cards.map((card, i) => ( <div key={i} className={cn(card.className, "")}> <motion.div // id={selected?.id === card?.id ? 'selected-card' : `card-${i}`} onClick={() => handleClick(card)} className={cn( card.className, "relative scroll-offset-top-biography", selected?.id === card.id ? "rounded-lg fixed inset-0 top-10 md:top-20 h-[75vh] md:h-[85vh] w-[95%] sm:w-[85%] md:w-[80%] m-auto z-50 flex justify-center items-center flex-wrap flex-col" : `bg-white rounded-xl h-full w-full border ${lastSelected?.id === card.id && 'z-40'}` )} layoutId={`card-${card.id}`} > {card.thumbnail && <ImageComponent card={card} />} {selected?.id === card.id ? <SelectedCard selected={selected} closeCard={closeCard}/> : <ContentCard card={card} /> } </motion.div> </div> ))} <motion.div onClick={closeCard} className={cn( "absolute h-full w-full left-0 top-0 bg-black opacity-0 z-40", selected ? "pointer-events-auto" : "pointer-events-none" )} animate={{ opacity: selected ? 0.3 : 0 }} /> </div> </>); };
const SelectedCard = ({ selected, closeCard, }: { selected: LayoutCardProps | null, closeCard: ()=>void; }) => { return (<> <div className="relative h-full w-full flex flex-col justify-start rounded-lg shadow-2xl z-[70]" > <motion.div initial={{ opacity: 0, }} animate={{ opacity: 0.6, }} className="absolute inset-0 h-full w-full bg-black opacity-60 rounded-xl" /> <motion.div layoutId={`content-${selected?.id}`} initial={{ opacity: 0, y: 100, }} animate={{ opacity: 1, y: 0, }} exit={{ opacity: 0, y: 100, }} transition={{ duration: 0.3, ease: "easeInOut", }} className="relative h-full w-full p-2" > {selected?.contentFull} </motion.div> <button onClick={closeCard} className="absolute top-0 right-0 p-2 mr-4 mt-3 bg-slate-700 hover:bg-orange-400 rounded-full" > <MdOutlineCloseFullscreen size={20} className="text-slate-50 hover:text-slate-900"/> </button> </div> </>); };
问题原因
核心是事件冒泡:点击关闭按钮时,事件会向上传递到外层的<motion.div>,而这个div绑定了handleClick(card)方法。这会导致closeCard刚把selected设为null,handleClick又立刻把selected重新设为当前卡片,界面自然看不到变化。
解决方案
在关闭按钮的点击事件中阻止冒泡,修改SelectedCard里的按钮代码:
<button onClick={(e) => { e.stopPropagation(); // 阻止事件冒泡到父元素 closeCard(); }} className="absolute top-0 right-0 p-2 mr-4 mt-3 bg-slate-700 hover:bg-orange-400 rounded-full" > <MdOutlineCloseFullscreen size={20} className="text-slate-50 hover:text-slate-900"/> </button>
或者另一种方式,在SelectedCard最外层容器添加事件阻止冒泡:
<div className="relative h-full w-full flex flex-col justify-start rounded-lg shadow-2xl z-[70]" onClick={(e) => e.stopPropagation()} > {/* 原有内容不变 */} </div>
这样修改后,点击关闭按钮时事件不会传递到外层的motion.div,handleClick不会被触发,closeCard的状态变更就能正常生效,界面会同步更新。
内容的提问来源于stack exchange,提问作者Christian Arvin
相关产品推荐
相关产品推荐

