framer motion exit animation not working (using react and animate presence) - reactjs

it doesn't work. why?
goal: fire animation before component unmounts
example component:
import React from "react";
import { useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
export default function NotExit() {
const [show, toggle] = useState(true);
const MyComponent = ({ isVisible }) => (
<AnimatePresence>
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
)
return (
<div className="relative w-screen h-screen flex flex-col-reverse justify-center items-center">
<button
className="bg-purple-600 p-2 rounded"
onClick={() => {
toggle(!show);
}}
>
show
</button>
{show && (
<AnimatePresence>
<motion.section
initial={{ y: 100 }}
animate={{ y: 0 }}
exit={{ opacity: 1, transition: { duration: 1 } }}
key="cock"
className="w-44 h-44 bg-red-500 rounded-xl"
transition={{ duration: 1 }}
></motion.section>
</AnimatePresence>
)}
</div>
);
solutions tried:
adding key prop,
adding duration to exit prop if for some reason the animation happens but quickly so i don't notice it
adding mode="wait" to AnimatePresence
I haven't really tried the switch location thingy mainly because i couldn't import it and i think routes replaced it

The placement of the AnimatePresence component here is not correct:
{show && (
<AnimatePresence>
<motion.section
...
></motion.section>
</AnimatePresence>
)}
You need to move it outside to wrap the animated component:
<AnimatePresence>
{show && (
<motion.section
...
></motion.section>
)}
</AnimatePresence>

Related

I am trying to get framer-motions layout property work on my react-app with grid (auto-col). Can someone please tell me what is wrong in my code?

I want my display grid to be animated on delete and addition on new task element. Can someone please attach a tutorial if possible, because the ones that I have watched were not able to solve this problem.
import React from "react";
import styles from "./OutputDisplay.module.css";
import Task from "./Task";
function OutputDisplay(props) {
const container = {
hidden: { opacity: 1 },
visible: {
opacity: 1,
scale: 1,
transition: {
staggerChildren: 0.2,
},
},
};
return (
<div className={styles.wrapper}>
<div className={styles.taskWrapper}>
<motion.div
variants={container}
initial="hidden"
animate="visible"
className={styles.taskContainer}
>
<AnimatePresence>
{props.taskList.map((element, index) => {
return (
<Task
key={index}
index={index}
element={element}
taskList={props.taskList}
setTaskList={props.setTaskList}
/>
);
})}
</AnimatePresence>
</motion.div>
</div>
</div>
);
}
export default OutputDisplay;

Animating height using Framer Motion not working in React

So, I have been trying to use Framer Motion for my React project. I basically want to animate the height from 0 to "auto", when the div gets rendered.
I tried the below code, but the height is not getting animated
<motion.div
initial={{ height: 0 }}
animate={{ height: "auto" }}
transition={{ duration: 0.5 }}
key={searchQuery?.length}
>
When I replaced height with width, the animation works fine, but can't figure out why the height is not getting animated. And I was unable to find any proper documentation regarding this.
Here is the CodeSandbox Link for demo.
Fixed versinn
What was wrong?
Your conditional rendering logic was in the wrong place, AnimatePresence works only if its direct child disappears.
exit prop was missing
key prop has to be stable, it can't be the length of the string
overflow: hidden have to be added so the children are invisible
Final code:
export default function App() {
const ref = useRef(null);
const [isActive, setIsActive] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>
<input
placeholder={"Enter Keyword"}
value={searchQuery}
onChange={(e) => {
setSearchQuery(e.target.value);
}}
/>
<AnimatePresence>
{searchQuery?.length >= 1 && (
<motion.div
style={{ overflow: "hidden" }}
initial={{ height: 0 }}
animate={{ height: "auto" }}
transition={{ duration: 0.5 }}
exit={{ height: 0 }}
key={"container"}
>
{dataList?.map((listItem) => (
<div
style={{
padding: "1rem",
color: "#E090EE",
borderBottom: "1px solid #E1E1E1",
}}
>
{listItem.activity_title}
</div>
))}
</motion.div>
)}
</AnimatePresence>
</div>
</div>
);
}

Framer Motion Reorder misplacing my items

For some reason, in the first "reordering" framer-motion miscalculates my items' position. It definitely has something to do with the fact that I'm using this within a side panel because it is positioning them to a distance in the x-axis precisely the same as the width of my panel. Another problem I'm having is that the AnimatePrescese applies the height animation to the element itself, but not to the children... Any suggestions?
<Reorder.Group
axis="y"
className="flex flex-col p-0 m-0 list-none relative"
values={orderedItems}
onReorder={setOrderedItems}
ref={listRef}
>
<AnimatePresence>
{orderedItems?.map(item => {
const {
id,
referencePayload: { abbreviation, description, title, url }
} = item
return (
<Reorder.Item
key={id}
value={item}
dragConstraints={listRef}
dragElastic={0.1}
dragMomentum={false}
onDragStart={() => onDragStart(id)}
onDragEnd={() => onDragEnd(id)}
initial={{ height: 0 }}
animate={{ height: 'auto' }}
exit={{ height: 0 }}
>
<ProductItem
id={id}
url={url}
abbreviation={abbreviation}
title={title}
description={description}
onRemoveItem={handleRemoveFavourite}
hasUpdateError={isRemovalError}
isDragging={activeItemId === id}
/>
</Reorder.Item>
)
})}
</AnimatePresence>
</Reorder.Group>
Video with example => https://www.loom.com/share/cb2bf5f334f446459eeb1fc16772a3e4

AnimatePresence show both elements when animating

I am to make a step-like animation, hence, when I click a button, another div than the previous is shown, e.g:
import { motion, AnimatePresence } from 'framer-motion'
const MyApp= props => {
const [count, setCount] = useState(0)
return (
<>
<AnimatePresence>
{count == 0 && (
<motion.div
transition={{ duration: 2 }}
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
Hello 1
<button onClick={() => { setCount(count + 1) }}
</motion.div>
)}
</AnimatePresence>
<AnimatePresence>
{count == 1 && (
<motion.div
transition={{ duration: 2 }}
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
Hello 2
<button onClick={() => { setCount(count + 1) }}
</motion.div>
)}
</AnimatePresence>
</>
)
}
export default MyApp
However, the issue is, that when one div is fading out/in, the other is also fading in/out, hence, I have two elements at the same time. Instead I thought it would just animate one, remove it, and animate the other.
Am I doing something wrong here ?
Since you want to animate them together they should be under one <AnimatePresence />
AnimatePresence requires you to add an explicit key on the component you are animating beneath it so that it can track the components as they are added and removed https://www.framer.com/api/motion/animate-presence/#unmount-animations
Since you want to replace one component with another you need to add the prop exitBeforeEnter on the AnimatePrensence. https://www.framer.com/api/motion/animate-presence/#animatepresenceprops.exitbeforeenter
<AnimatePresence exitBeforeEnter> // Note on #3
{isGreenBox ? (// Note on #1
<GreenBox
initial={{ scale: 0 }}
animate={{ scale: 1 }}
exit={{ scale: 0 }}
key="green" // Note on #2
/>
) : (
<PurpleBox
initial={{ scale: 0 }}
animate={{ scale: 1 }}
exit={{ scale: 0 }}
key="purple" // Note on #2
/>
)}
</AnimatePresence>
Here is a working example I created :) https://codesandbox.io/s/framer-motion-animation-presence-exitbeforeenter-0miq0

How can i switch between react components using framer motion?

In my react app i need to switch between components like in a carousel. I found this example to build an image carousel only using framer motion: https://codesandbox.io/s/framer-motion-image-gallery-pqvx3?file=/src/Example.tsx:1715-1725
I want to adapt this to switching between components. At the moment my page looks something like this:
const variants = {
enter: (direction: number) => {
return {
x: direction > 0 ? 100 : -100,
opacity: 0,
}
},
center: {
zIndex: 1,
x: 0,
opacity: 1,
},
exit: (direction: number) => {
return {
zIndex: 0,
x: direction < 0 ? 100 : -100,
opacity: 0,
}
},
}
const Page = () => {
const [[page, direction], setPage] = useState([0, 0])
const paginate = (newDirection: number) => {
setPage([page + newDirection, newDirection])
}
return (
<motion.div
key={page}
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
>
<!-- my components, between which I want to switch, should appear here -->
</motion.div>
)
}
how would I have to build the logic to be able to switch dynamic between my components (slides)? In the codesandbox example the images were changed via an array:
const imageIndex = wrap(0, images.length, page);
<motion.img key={page} src={images[imageIndex]} />
How could i do that to switch between jsx elements?
Edit
The answer from Joshua Wootonn is correct, but you need to add the custom prop also to the TestComp to get the animation working with dynamic variants like this:
const TestComp = ({ bg }: { bg: string }) => (
<motion.div
custom={direction}
variants={variants}
initial="enter"
animate="center"
exit="exit"
transition={{
x: { type: "spring", stiffness: 100, damping: 30 },
opacity: { duration: 0.2 },
}}
className="absolute w-full h-full"
style={{
background: bg,
}}
/>
)
A couple of things were missing from the above answer to get exit animations working.
If you want exit animations to work within AnimationPresense you need to set keys on its children
<AnimatePresence initial={false} custom={direction}>
{page === 0 && <TestComp key="0" bg="rgb(171, 135, 255)" />}
{page === 1 && <TestComp key="1" bg="rgb(68, 109, 246)" />}
{page === 2 && <TestComp key="2" bg="rgb(172, 236, 161)" />}
</AnimatePresence>
If you want to animate something in while something is still animating out without having massive content shifting, you need to take them out of the flow. (use absolute positioning and wrap with relatively positioned container)
<div style={{ position: "relative", height: "300px", width: "300px" }}>
<AnimatePresence initial={false} custom={direction}>
...
</AnimatePresence>
</div>
and on the child components
height: 100%;
width: 100%;
position: absolute;
Working codesandbox: https://codesandbox.io/s/framer-motion-carousel-animation-wetrf?file=/src/App.tsx:658-708
Your components should return <motion.div> (or <motion.section>, <motion.span> etc.).
And in the page component you should use <AnimatePresence /> component (like in the example):
<AnimatePresence initial={false} custom={direction}>
{COMPONENTS}
</AnimatePresence>
Then you have to decide which component will appear:
{page === 0 && <ComponentOne />}
{page === 1 && <ComponentTwo/>}
{page === 2 && <ComponentThree/>}
The animations you can control with variants.
You can see a quick demo here: https://codesandbox.io/s/quizzical-hypatia-7wqjc?file=/src/App.tsx

Resources