Animate exits
Wrap children in AnimatePresence so they animate out before unmounting.
From frontend-patterns, “Animation Patterns: Framer Motion Animations”.
Removed rows: cut or exit
Remove a row with ×, then add one.
No AnimatePresenceBad
exit: 0msInside AnimatePresenceGood
exit: 210msexport function AnimatedMarketList({ markets }: { markets: Market[] }) {
return (
<AnimatePresence>
{markets.map(market => (
<motion.div
key={market.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20, transition: { duration: 0.21 } }}
transition={{ duration: 0.3 }}
>
<MarketCard market={market} />
</motion.div>
))}
</AnimatePresence>
)
}
The key picks who leaves
Remove Solana from the middle.
key={index}Badexit: nonerefilled: 0key={market.id}Goodexit: nonerefilled: 0{markets.map((market, i) => (
<motion.div key={i} …> // last row exits
<motion.div key={market.id} …> // removed row exits
))}
Modal: reverse, don’t cut
Open both, close, then reopen mid-exit.
{isOpen && <Modal/>}Badenter: 300msexit: 0msInside AnimatePresenceGood
enter: 300msexit: 210msexport function Modal({ isOpen, onClose, children }: ModalProps) {
return (
<AnimatePresence>
{isOpen && (
<>
<motion.div
className="modal-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
/>
<motion.div
className="modal-content"
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
>
{children}
</motion.div>
</>
)}
</AnimatePresence>
)
}
Why it matters, pros and cons
Why it matters
React drops an unmounted node at once. AnimatePresence keeps it until exit ends.
Pros
- Removals read as removals.
- Open and close feel like one object.
- Reopening mid-exit reverses smoothly.
Cons
- Exiting nodes stay in the DOM; make them inert.
key={index}animates the wrong row out.- Heavy dependency; CSS transitions suffice for one modal.