Framer Motion Reorder misplacing my items - reactjs

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

Related

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

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>

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>
);
}

Can't find the missing key for the child

novice developer here.
I can't seem to solve this missing key error. I've used the key prop for each time i've mapped but I can't seem to find the child with the missing key. please help!
I've tried putting key props in some of the child divs but still no success.
<h2 className="head-text">Skills & Experience</h2>
<div className="app__skills-container">
<motion.div className="app__skills-list">
{skills.map((skill) => (
<motion.div
whileInView={{ opacity: [0, 1] }}
transition={{ duration: 0.5 }}
className="app__skills-item app__flex"
key={skill.name}
>
<div
className="app__flex"
style={{ backgroundColor: skill.bgColor }}
>
<img src={urlFor(skill.icon)} alt={skill.name}/>
</div>
<p className="p-text">{skill.name}</p>
</motion.div>
))}
</motion.div>
<motion.div className="app__skills-exp">
{experience.map((experience) => (
<motion.div className="app__skills-exp-item" key={experience.year}>
<div className="app__skills-exp-year">
<p className="bold-text">{experience.year}</p>
</div>
<motion.div className="app__skills-exp-works">
{experience.works.map((work) => (
<>
<motion.div
whileInView={{ opacity: [0, 1] }}
transition={{ duration: 0.5 }}
className="app__skills-exp-work"
data-tip
data-for={work.name}
key={work.name}
>
<h4 className="bold-text">{work.name}</h4>
<p className="p-text">{work.company}</p>
</motion.div>
<ReactTooltip
id={work.name}
effect="solid"
arrowColor="#fff"
className="skills-tooltip"
>
{work.desc}
</ReactTooltip>
</>
))}
</motion.div>
</motion.div>
))}
</motion.div>
</div>
</>
);
};
export default AppWrap(
MotionWrap(Skills, 'app__skills'),
'skills', "app__whitebg"
);
Error is with <>...</> items in your
{experience.works.map((work) => (
<>
<motion.div key={work.name}>
{/* ... */}
</motion.div>
</>
))}
They are top-level DOM nodes inside of your map function, so they should have the key, not the child motion.div.
But they cant have key, so you will need to use Fragments. They are just the same.
import {Fragment} from "react";
{experience.works.map((work) => (
<Fragment key={work.name}>
<motion.div
whileInView={{ opacity: [0, 1] }}
transition={{ duration: 0.5 }}
className="app__skills-exp-work"
data-tip
data-for={work.name}
>
<h4 className="bold-text">{work.name}</h4>
<p className="p-text">{work.company}</p>
</motion.div>
<ReactTooltip
id={work.name}
effect="solid"
arrowColor="#fff"
className="skills-tooltip"
>
{work.desc}
</ReactTooltip>
</Fragment>
))}
According to react docs, the key has to be
a string that uniquely identifies a list item among its siblings
It is possible that the key you have provided for each element is not unique. For eg key={skill.name} key={experience.year} will not work if there are two list items with the same name or year respectively.
What you can do is use any uniquely identifiable property of the list item or the index of the array.
{skills.map((skill, index) => (
<motion.div
whileInView={{ opacity: [0, 1] }}
transition={{ duration: 0.5 }}
className="app__skills-item app__flex"
key={index}
>
<div
className="app__flex"
style={{ backgroundColor: skill.bgColor }}
>
<img src={urlFor(skill.icon)} alt={skill.name}/>
</div>
<p className="p-text">{skill.name}</p>
</motion.div>
))}

multiple Swiper Thumb carousel navigation does not work

I am very new in React. I have multiple Swiper Thumb caroucels in one page.
also I have two filtered elements in my code which I want to show on display.my first carousel works perfectly in at first filtered item, but second one's navigation does not work, when I click, nothing happens.
const [thumbsSwiper, setThumbsSwiper] = useState(null);
const [thumbsSwiper1, setThumbsSwiper1] =useState(null)
this one works perfectly
<Swiper
style={{
"--swiper-navigation-color": "#fff",
"--swiper-pagination-color": "#fff",
}}
spaceBetween={10}
navigation={false}
thumbs={{ swiper: thumbsSwiper }}
modules={[FreeMode, Navigation, Thumbs]}
className="mySwiper2"
>
{movie.images.map((item,index)=>{
return (
<SwiperSlide key={index} >
<img src={`http://.../${item}`} />
</SwiperSlide>
)
})}
</Swiper>
<Swiper
onSwiper={setThumbsSwiper}
spaceBetween={10}
slidesPerView={movie.images.length}
freeMode={true}
simulateTouch = {true}
watchSlidesProgress={true}
modules={[FreeMode, Navigation, Thumbs]}
className="mySwiper"
>
{movie.images.map((item,index)=>{
return (
<SwiperSlide key={index} >
<img src={`http://.../${item}`} />
</SwiperSlide>
)
})}
</Swiper>
this one does not work, it is multiple array inside it and maybe this is a reason
<Swiper
style={{
"--swiper-navigation-color": "#fff",
"--swiper-pagination-color": "#fff",
}}
spaceBetween={10}
navigation={false}
thumbs={{ swiper: thumbsSwiper1 }}
modules={[FreeMode, Navigation, Thumbs]}
className="mySwiper2"
>
{item.images.map((item,index)=>{
return (
<SwiperSlide key={index} >
<img src={`http://apicity.cgroup.ge/${item}`} />
</SwiperSlide>
)
})}
</Swiper>
<Swiper
onSwiper={setThumbsSwiper1}
spaceBetween={10}
slidesPerView={item.images.length}
freeMode={true}
watchSlidesProgress={true}
modules={[FreeMode, Navigation, Thumbs]}
className="mySwiper"
>
{item.images.map((item,index)=>{
return (
<SwiperSlide key={index}>
<img src={`http://apicity.cgroup.ge/${item}`}/>
</SwiperSlide>
)
})}
</Swiper>
I thought I should change classes but it still does not worked, then I tried to change some property but still same result. help please

When new items are added, the first existing items are not animated using framer motion

When there are only two items initially, clicking Add will add new item (3rd item). The 2nd item will go below and show a heading. Going below is not animated.
In the same way, when there are 3 items and clicked Delete, the last movement is not animated.
Demo with full source can be found here: https://codesandbox.io/s/practical-dream-229d2y?file=/src/App.tsx
This is the source code for archive purposes:
import { useState } from "react";
import { AnimatePresence, AnimateSharedLayout, motion } from "framer-motion";
function SomeItem() {
return (
<div
style={{
border: "1px solid blue",
padding: "12px",
height: 60,
width: "100%",
margin: "12px"
}}
>
{new Date().toISOString()}
</div>
);
}
export default function App() {
const [items, setItems] = useState<typeof SomeItem[]>([SomeItem, SomeItem]);
return (
<div className="App">
<button
onClick={() =>
setItems((prev) => prev.filter((_p, i) => i !== prev.length - 1))
}
>
Delete
</button>
<button onClick={() => setItems((prev) => [...prev, SomeItem])}>
Add
</button>
<section>
<h3>What&apos;s not working?</h3>
<p>
When there are only two items initially, clicking Add will add new
item (3rd item). The 2nd item will go below and show a heading. Going
below is not animated.
</p>
<p>
In the same way, when there are 3 items and clicked Delete, the last
movement is not animated.
</p>
</section>
<AnimateSharedLayout>
<ul style={{ listStyle: "none", margin: 0, padding: 0 }}>
<AnimatePresence initial>
{items.map((Item, i) => (
<motion.li
key={i}
layout
animate={{ transition: { damping: 1000 }, y: 0 }}
exit={{ opacity: 0, transition: { damping: 1000 }, y: "-110%" }}
initial={{ y: "-110%" }}
>
{i !== 0 && items.length > 2 && (
<motion.h3
animate={{ transition: { damping: 0 }, x: 0 }}
exit={{ x: "-100%" }}
initial={{ x: "-100%" }}
>
Item {i}
</motion.h3>
)}
<Item />
</motion.li>
))}
</AnimatePresence>
</ul>
</AnimateSharedLayout>
</div>
);
}
The problem seems to be at this conditional i !== 0 && items.length > 2, when the items.length is greater than two it's rendering two headers at same time because you specified to only render the header after the item of index 0, thus when you delete the item it's also deleting the two headers at same time and just animating the last one. To fix it you can change the conditional to i !== 0 && items.length > 1, this way it will render item 1, item 2 and so on. see the working example

Resources