Framer Motion AnimatePresence: Stale layoutId from Cached Data Prevents Exit Animation
0 reputation · 03 Jan 2021, 12:12 UTC
0 reputation · 03 Jan 2021, 12:12 UTC
AnimatePresence relies on layoutId to match exiting and entering elements for shared‑layout animations. When the layoutId is derived from a value that is cached (for example, a stale item id returned by React Query) and that cached value does not update on subsequent renders, the exit animation can fail to trigger or animate the wrong element.
Developers typically do not see this behavior documented, and the library does not automatically invalidate layoutId when prop values change. The result is visual glitches—elements jump, fade incorrectly, or linger after removal—especially in dynamic lists with rapid insert/delete sequences.
Because the caching layer and layoutId lifecycle are decoupled, the precise boundary for when the layoutId should be considered stale remains unclear.
layoutId when the underlying prop is derived from cached data?layoutId in the presence of stale cache entries?To fix exit animations that fail due to stale cached data, you must decouple the layoutId from the volatile cached property and ensure the element's key remains stable until the component is fully unmounted. If the layoutId changes while the component is still in the DOM—due to a cache update occurring just before removal—Framer Motion loses the reference to the original element, causing the exit animation to snap or fail.
In Framer Motion, AnimatePresence uses the key prop to determine when an element is leaving the tree. However, layoutId is used to coordinate shared element transitions between two different components. When you derive layoutId from a cached value (e.g., a React Query object), a race condition can occur: the cache updates the ID, triggering a re-render with a new layoutId, and then the item is removed from the list.
Because the layoutId has already changed, the library no longer recognizes the exiting element as the same entity that was previously animating. It treats the element as a new identity with no previous layout state, bypassing the shared transition logic and resulting in a visual jump.
key prop is tied to a permanent unique identifier (like a database UUID) that never changes, regardless of cache state.layoutId must be derived from data that might fluctuate, wrap the value in useMemo or a local state variable that is set only on mount.key and layoutId if that variable is subject to cache-driven updates.To diagnose if the cache is the culprit, temporarily replace the dynamic layoutId with a hard-coded string. If the exit animation begins working correctly, the issue is confirmed as an identity mismatch caused by the data layer.
// Diagnostic check: replace this
<motion.div layoutId={item.cachedId} />
// With this
<motion.div layoutId="diagnostic-static-id" />
Missing Diagnostic Detail: Are you updating the cached data source (e.g., calling invalidateQueries) in the same execution block that triggers the removal of the element?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.