--- name: mastering-animate-presence description: Audit Motion/Framer Motion code for AnimatePresence best practices. Use when reviewing exit animations, modals, or presence state. Outputs file:line findings. license: MIT metadata: author: raphael-salaja version: "2.0.0" source: /content/mastering-animate-presence/index.mdx --- # Mastering AnimatePresence Review Motion code for AnimatePresence and exit animation best practices. ## How It Works 1. Read the specified files (or prompt user for files/pattern) 2. Check against all rules below 3. Output findings in `file:line` format ## Rule Categories | Priority | Category | Prefix | |----------|----------|--------| | 1 | Exit Animations | `exit-` | | 2 | Presence Hooks | `presence-` | | 3 | Mode Selection | `mode-` | | 4 | Nested Exits | `nested-` | ## Rules ### Exit Animation Rules #### `exit-requires-wrapper` Conditional motion elements must be wrapped in AnimatePresence. **Fail:** ```tsx {isVisible && ( )} ``` **Pass:** ```tsx {isVisible && ( )} ``` #### `exit-prop-required` Elements inside AnimatePresence should have exit prop defined. **Fail:** ```tsx {isOpen && ( )} ``` **Pass:** ```tsx {isOpen && ( )} ``` #### `exit-key-required` Dynamic lists inside AnimatePresence must have unique keys. **Fail:** ```tsx {items.map((item, index) => ( ))} ``` **Pass:** ```tsx {items.map((item) => ( ))} ``` #### `exit-matches-initial` Exit animation should mirror initial for symmetry. **Fail:** ```tsx ``` **Pass:** ```tsx ``` ### Presence Hook Rules #### `presence-hook-in-child` useIsPresent must be called from child of AnimatePresence, not parent. **Fail:** ```tsx function Parent() { const isPresent = useIsPresent(); // Wrong location return ( {show && } ); } ``` **Pass:** ```tsx function Child() { const isPresent = useIsPresent(); // Correct location return ; } ``` #### `presence-safe-to-remove` When using usePresence, always call safeToRemove after async work. **Fail:** ```tsx function AsyncComponent() { const [isPresent, safeToRemove] = usePresence(); useEffect(() => { if (!isPresent) { cleanup(); // Never calls safeToRemove } }, [isPresent]); } ``` **Pass:** ```tsx function AsyncComponent() { const [isPresent, safeToRemove] = usePresence(); useEffect(() => { if (!isPresent) { cleanup().then(safeToRemove); } }, [isPresent, safeToRemove]); } ``` #### `presence-disable-interactions` Disable interactions on exiting elements using isPresent. **Fail:** ```tsx function Card() { const isPresent = useIsPresent(); return ; // Button clickable during exit } ``` **Pass:** ```tsx function Card() { const isPresent = useIsPresent(); return ( ); } ``` ### Mode Selection Rules #### `mode-wait-doubles-duration` Mode "wait" nearly doubles animation duration; adjust timing accordingly. **Fail:** ```tsx // Total time: ~600ms (too slow) ``` **Pass:** ```tsx // Total time: ~300ms (acceptable) ``` #### `mode-sync-layout-conflict` Mode "sync" causes layout conflicts; position exiting elements absolutely. **Fail:** ```tsx {items.map(item => ( {item} ))} // Exiting and entering elements compete for space ``` **Pass:** ```tsx {items.map(item => ( {item} ))} ``` #### `mode-pop-layout-for-lists` Use popLayout mode for list reordering animations. **Fail:** ```tsx {items.map(item => )} // Layout shifts during exit ``` **Pass:** ```tsx {items.map(item => )} ``` ### Nested Exit Rules #### `nested-propagate-required` Nested AnimatePresence must use propagate prop for coordinated exits. **Fail:** ```tsx {isOpen && ( {items.map(item => ( ))} )} // Children vanish instantly when parent exits ``` **Pass:** ```tsx {isOpen && ( {items.map(item => ( ))} )} ``` #### `nested-consistent-timing` Parent and child exit durations should be coordinated. **Fail:** ```tsx // Parent exits in 100ms, children in 500ms ``` **Pass:** ```tsx // Parent waits for children or exits simultaneously ``` ## Output Format When reviewing files, output findings as: ``` file:line - [rule-id] description of issue Example: components/modal/index.tsx:23 - [exit-requires-wrapper] Conditional motion.div not wrapped in AnimatePresence components/modal/index.tsx:45 - [exit-prop-required] Missing exit prop on motion element ``` ## Summary Table After findings, output a summary: | Rule | Count | Severity | |------|-------|----------| | `exit-requires-wrapper` | 2 | HIGH | | `exit-prop-required` | 3 | HIGH | | `mode-wait-doubles-duration` | 1 | MEDIUM | ## References - [Motion AnimatePresence Documentation](https://motion.dev/docs/react-animate-presence) - [MDN @starting-style](https://developer.mozilla.org/en-US/docs/Web/CSS/@starting-style)